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