asterisk_processor.py 8.6 KB

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