18044f57eb90a7c46626661014826a43dec7b1c9
[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     def __init__(self, wiki_prefix):
150         self.wiki_prefix = wiki_prefix
151
152     def process_resource_api(self, resource_api, context):
153         resource_api.wiki_prefix = self.wiki_prefix
154         # Derive a resource name from the API declaration's filename
155         resource_api.name = re.sub('\..*', '',
156                                    os.path.basename(resource_api.path))
157         # Now in all caps, for include guard
158         resource_api.name_caps = resource_api.name.upper()
159         resource_api.name_title = resource_api.name.capitalize()
160         resource_api.c_name = snakify(resource_api.name)
161         # Construct the PathSegement tree for the API.
162         if resource_api.api_declaration:
163             resource_api.root_path = PathSegment('', None)
164             for api in resource_api.api_declaration.apis:
165                 segment = resource_api.root_path.get_child(api.path.split('/'))
166                 for operation in api.operations:
167                     segment.operations.append(operation)
168                 api.full_name = segment.full_name
169
170             # Since every API path should start with /[resource], root should
171             # have exactly one child.
172             if resource_api.root_path.num_children() != 1:
173                 raise SwaggerError(
174                     "Should not mix resources in one API declaration", context)
175             # root_path isn't needed any more
176             resource_api.root_path = resource_api.root_path.children()[0]
177             if resource_api.name != resource_api.root_path.name:
178                 raise SwaggerError(
179                     "API declaration name should match", context)
180             resource_api.root_full_name = resource_api.root_path.full_name
181
182     def process_api(self, api, context):
183         api.wiki_path = wikify(api.path)
184
185     def process_operation(self, operation, context):
186         # Nicknames are camelCase, Asterisk coding is snake case
187         operation.c_nickname = snakify(operation.nickname)
188         operation.c_http_method = 'AST_HTTP_' + operation.http_method
189         if not operation.summary.endswith("."):
190             raise SwaggerError("Summary should end with .", context)
191         operation.wiki_summary = wikify(operation.summary or "")
192         operation.wiki_notes = wikify(operation.notes or "")
193
194     def process_parameter(self, parameter, context):
195         if not parameter.data_type in self.type_mapping:
196             raise SwaggerError(
197                 "Invalid parameter type %s" % parameter.data_type, context)
198         # Parameter names are camelcase, Asterisk convention is snake case
199         parameter.c_name = snakify(parameter.name)
200         parameter.c_data_type = self.type_mapping[parameter.data_type]
201         parameter.c_convert = self.convert_mapping[parameter.data_type]
202         # You shouldn't put a space between 'char *' and the variable
203         if parameter.c_data_type.endswith('*'):
204             parameter.c_space = ''
205         else:
206             parameter.c_space = ' '
207         parameter.wiki_description = wikify(parameter.description)
208
209     def process_model(self, model, context):
210         model.description_dox = model.description.replace('\n', '\n * ')
211         model.description_dox = re.sub(' *\n', '\n', model.description_dox)
212         model.wiki_description = wikify(model.description)
213         model.c_id = snakify(model.id)
214         return model
215
216     def process_property(self, prop, context):
217         if "-" in prop.name:
218             raise SwaggerError("Property names cannot have dashes", context)
219         if prop.name != prop.name.lower():
220             raise SwaggerError("Property name should be all lowercase",
221                                context)
222         prop.wiki_description = wikify(prop.description)
223
224     def process_type(self, swagger_type, context):
225         swagger_type.c_name = snakify(swagger_type.name)
226         swagger_type.c_singular_name = snakify(swagger_type.singular_name)
227         swagger_type.wiki_name = wikify(swagger_type.name)