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