asterisk_processor.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #
  2. # Asterisk -- An open source telephony toolkit.
  3. #
  4. # Copyright (C) 2013, Digium, Inc.
  5. #
  6. # David M. Lee, II <dlee@digium.com>
  7. #
  8. # See http://www.asterisk.org for more information about
  9. # the Asterisk project. Please do not directly contact
  10. # any of the maintainers of this project for assistance;
  11. # the project provides a web site, mailing lists and IRC
  12. # channels for your use.
  13. #
  14. # This program is free software, distributed under the terms of
  15. # the GNU General Public License Version 2. See the LICENSE file
  16. # at the top of the source tree.
  17. #
  18. """Implementation of SwaggerPostProcessor which adds fields needed to generate
  19. Asterisk RESTful HTTP binding code.
  20. """
  21. import os
  22. import re
  23. from swagger_model import Stringify, SwaggerError, SwaggerPostProcessor
  24. try:
  25. from collections import OrderedDict
  26. except ImportError:
  27. from odict import OrderedDict
  28. def simple_name(name):
  29. """Removes the {markers} from a path segement.
  30. @param name: Swagger path segement, with {pathVar} markers.
  31. """
  32. if name.startswith('{') and name.endswith('}'):
  33. return name[1:-1]
  34. return name
  35. def wikify(str):
  36. """Escapes a string for the wiki.
  37. @param str: String to escape
  38. """
  39. return re.sub(r'([{}\[\]])', r'\\\1', str)
  40. def snakify(name):
  41. """Helper to take a camelCase or dash-seperated name and make it
  42. snake_case.
  43. """
  44. r = ''
  45. prior_lower = False
  46. for c in name:
  47. if c.isupper() and prior_lower:
  48. r += "_"
  49. if c is '-':
  50. c = '_'
  51. prior_lower = c.islower()
  52. r += c.lower()
  53. return r
  54. class PathSegment(Stringify):
  55. """Tree representation of a Swagger API declaration.
  56. """
  57. def __init__(self, name, parent):
  58. """Ctor.
  59. @param name: Name of this path segment. May have {pathVar} markers.
  60. @param parent: Parent PathSegment.
  61. """
  62. #: Segment name, with {pathVar} markers removed
  63. self.name = simple_name(name)
  64. #: True if segment is a {pathVar}, else None.
  65. self.is_wildcard = None
  66. #: Underscore seperated name all ancestor segments
  67. self.full_name = None
  68. #: Dictionary of child PathSegements
  69. self.__children = OrderedDict()
  70. #: List of operations on this segement
  71. self.operations = []
  72. if self.name != name:
  73. self.is_wildcard = True
  74. if not self.name:
  75. assert(not parent)
  76. self.full_name = ''
  77. if not parent or not parent.name:
  78. self.full_name = name
  79. else:
  80. self.full_name = "%s_%s" % (parent.full_name, self.name)
  81. def get_child(self, path):
  82. """Walks decendents to get path, creating it if necessary.
  83. @param path: List of path names.
  84. @return: PageSegment corresponding to path.
  85. """
  86. assert simple_name(path[0]) == self.name
  87. if (len(path) == 1):
  88. return self
  89. child = self.__children.get(path[1])
  90. if not child:
  91. child = PathSegment(path[1], self)
  92. self.__children[path[1]] = child
  93. return child.get_child(path[1:])
  94. def children(self):
  95. """Gets list of children.
  96. """
  97. return self.__children.values()
  98. def num_children(self):
  99. """Gets count of children.
  100. """
  101. return len(self.__children)
  102. class AsteriskProcessor(SwaggerPostProcessor):
  103. """A SwaggerPostProcessor which adds fields needed to generate Asterisk
  104. RESTful HTTP binding code.
  105. """
  106. #: How Swagger types map to C.
  107. type_mapping = {
  108. 'string': 'const char *',
  109. 'boolean': 'int',
  110. 'number': 'int',
  111. 'int': 'int',
  112. 'long': 'long',
  113. 'double': 'double',
  114. 'float': 'float',
  115. }
  116. #: String conversion functions for string to C type.
  117. convert_mapping = {
  118. 'string': '',
  119. 'int': 'atoi',
  120. 'long': 'atol',
  121. 'double': 'atof',
  122. 'boolean': 'ast_true',
  123. }
  124. #: JSON conversion functions
  125. json_convert_mapping = {
  126. 'string': 'ast_json_string_get',
  127. 'int': 'ast_json_integer_get',
  128. 'long': 'ast_json_integer_get',
  129. 'double': 'ast_json_real_get',
  130. 'boolean': 'ast_json_is_true',
  131. }
  132. def __init__(self, wiki_prefix):
  133. self.wiki_prefix = wiki_prefix
  134. def process_resource_api(self, resource_api, context):
  135. resource_api.wiki_prefix = self.wiki_prefix
  136. # Derive a resource name from the API declaration's filename
  137. resource_api.name = re.sub('\..*', '',
  138. os.path.basename(resource_api.path))
  139. # Now in all caps, for include guard
  140. resource_api.name_caps = resource_api.name.upper()
  141. resource_api.name_title = resource_api.name.capitalize()
  142. resource_api.c_name = snakify(resource_api.name)
  143. # Construct the PathSegement tree for the API.
  144. if resource_api.api_declaration:
  145. resource_api.root_path = PathSegment('', None)
  146. for api in resource_api.api_declaration.apis:
  147. segment = resource_api.root_path.get_child(api.path.split('/'))
  148. for operation in api.operations:
  149. segment.operations.append(operation)
  150. api.full_name = segment.full_name
  151. # Since every API path should start with /[resource], root should
  152. # have exactly one child.
  153. if resource_api.root_path.num_children() != 1:
  154. raise SwaggerError(
  155. "Should not mix resources in one API declaration", context)
  156. # root_path isn't needed any more
  157. resource_api.root_path = list(resource_api.root_path.children())[0]
  158. if resource_api.name != resource_api.root_path.name:
  159. raise SwaggerError(
  160. "API declaration name should match", context)
  161. resource_api.root_full_name = resource_api.root_path.full_name
  162. def process_api(self, api, context):
  163. api.wiki_path = wikify(api.path)
  164. def process_operation(self, operation, context):
  165. # Nicknames are camelCase, Asterisk coding is snake case
  166. operation.c_nickname = snakify(operation.nickname)
  167. operation.c_http_method = 'AST_HTTP_' + operation.http_method
  168. if not operation.summary.endswith("."):
  169. raise SwaggerError("Summary should end with .", context)
  170. operation.wiki_summary = wikify(operation.summary or "")
  171. operation.wiki_notes = wikify(operation.notes or "")
  172. for error_response in operation.error_responses:
  173. error_response.wiki_reason = wikify(error_response.reason or "")
  174. operation.parse_body = (operation.body_parameter or operation.has_query_parameters) and True
  175. def process_parameter(self, parameter, context):
  176. if parameter.param_type == 'body':
  177. parameter.is_body_parameter = True;
  178. parameter.c_data_type = 'struct ast_json *'
  179. else:
  180. parameter.is_body_parameter = False;
  181. if not parameter.data_type in self.type_mapping:
  182. raise SwaggerError(
  183. "Invalid parameter type %s" % parameter.data_type, context)
  184. # Type conversions
  185. parameter.c_data_type = self.type_mapping[parameter.data_type]
  186. parameter.c_convert = self.convert_mapping[parameter.data_type]
  187. parameter.json_convert = self.json_convert_mapping[parameter.data_type]
  188. # Parameter names are camelcase, Asterisk convention is snake case
  189. parameter.c_name = snakify(parameter.name)
  190. # You shouldn't put a space between 'char *' and the variable
  191. if parameter.c_data_type.endswith('*'):
  192. parameter.c_space = ''
  193. else:
  194. parameter.c_space = ' '
  195. parameter.wiki_description = wikify(parameter.description)
  196. if parameter.allowable_values:
  197. parameter.wiki_allowable_values = parameter.allowable_values.to_wiki()
  198. else:
  199. parameter.wiki_allowable_values = None
  200. def process_model(self, model, context):
  201. model.description_dox = model.description.replace('\n', '\n * ')
  202. model.description_dox = re.sub(' *\n', '\n', model.description_dox)
  203. model.wiki_description = wikify(model.description)
  204. model.c_id = snakify(model.id)
  205. return model
  206. def process_property(self, prop, context):
  207. if "-" in prop.name:
  208. raise SwaggerError("Property names cannot have dashes", context)
  209. if prop.name != prop.name.lower():
  210. raise SwaggerError("Property name should be all lowercase",
  211. context)
  212. prop.wiki_description = wikify(prop.description)
  213. def process_type(self, swagger_type, context):
  214. swagger_type.c_name = snakify(swagger_type.name)
  215. swagger_type.c_singular_name = snakify(swagger_type.singular_name)
  216. swagger_type.wiki_name = wikify(swagger_type.name)