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