255 lines
8.9 KiB
Python
255 lines
8.9 KiB
Python
|
#
|
||
|
# Asterisk -- An open source telephony toolkit.
|
||
|
#
|
||
|
# Copyright (C) 2013, Digium, Inc.
|
||
|
#
|
||
|
# David M. Lee, II <dlee@digium.com>
|
||
|
#
|
||
|
# See http://www.asterisk.org for more information about
|
||
|
# the Asterisk project. Please do not directly contact
|
||
|
# any of the maintainers of this project for assistance;
|
||
|
# the project provides a web site, mailing lists and IRC
|
||
|
# channels for your use.
|
||
|
#
|
||
|
# This program is free software, distributed under the terms of
|
||
|
# the GNU General Public License Version 2. See the LICENSE file
|
||
|
# at the top of the source tree.
|
||
|
#
|
||
|
|
||
|
"""Implementation of SwaggerPostProcessor which adds fields needed to generate
|
||
|
Asterisk RESTful HTTP binding code.
|
||
|
"""
|
||
|
|
||
|
import os
|
||
|
import re
|
||
|
|
||
|
from swagger_model import Stringify, SwaggerError, SwaggerPostProcessor
|
||
|
|
||
|
try:
|
||
|
from collections import OrderedDict
|
||
|
except ImportError:
|
||
|
from odict import OrderedDict
|
||
|
|
||
|
|
||
|
def simple_name(name):
|
||
|
"""Removes the {markers} from a path segement.
|
||
|
|
||
|
@param name: Swagger path segement, with {pathVar} markers.
|
||
|
"""
|
||
|
if name.startswith('{') and name.endswith('}'):
|
||
|
return name[1:-1]
|
||
|
return name
|
||
|
|
||
|
|
||
|
def wikify(str):
|
||
|
"""Escapes a string for the wiki.
|
||
|
|
||
|
@param str: String to escape
|
||
|
"""
|
||
|
# Replace all line breaks with line feeds
|
||
|
str = re.sub(r'<br\s*/?>', '\n', str)
|
||
|
return re.sub(r'([{}\[\]])', r'\\\1', str)
|
||
|
|
||
|
|
||
|
def snakify(name):
|
||
|
"""Helper to take a camelCase or dash-seperated name and make it
|
||
|
snake_case.
|
||
|
"""
|
||
|
r = ''
|
||
|
prior_lower = False
|
||
|
for c in name:
|
||
|
if c.isupper() and prior_lower:
|
||
|
r += "_"
|
||
|
if c == '-':
|
||
|
c = '_'
|
||
|
prior_lower = c.islower()
|
||
|
r += c.lower()
|
||
|
return r
|
||
|
|
||
|
|
||
|
class PathSegment(Stringify):
|
||
|
"""Tree representation of a Swagger API declaration.
|
||
|
"""
|
||
|
def __init__(self, name, parent):
|
||
|
"""Ctor.
|
||
|
|
||
|
@param name: Name of this path segment. May have {pathVar} markers.
|
||
|
@param parent: Parent PathSegment.
|
||
|
"""
|
||
|
#: Segment name, with {pathVar} markers removed
|
||
|
self.name = simple_name(name)
|
||
|
#: True if segment is a {pathVar}, else None.
|
||
|
self.is_wildcard = None
|
||
|
#: Underscore seperated name all ancestor segments
|
||
|
self.full_name = None
|
||
|
#: Dictionary of child PathSegements
|
||
|
self.__children = OrderedDict()
|
||
|
#: List of operations on this segement
|
||
|
self.operations = []
|
||
|
|
||
|
if self.name != name:
|
||
|
self.is_wildcard = True
|
||
|
|
||
|
if not self.name:
|
||
|
assert(not parent)
|
||
|
self.full_name = ''
|
||
|
if not parent or not parent.name:
|
||
|
self.full_name = name
|
||
|
else:
|
||
|
self.full_name = "%s_%s" % (parent.full_name, self.name)
|
||
|
|
||
|
def get_child(self, path):
|
||
|
"""Walks descendants to get path, creating it if necessary.
|
||
|
|
||
|
@param path: List of path names.
|
||
|
@return: PageSegment corresponding to path.
|
||
|
"""
|
||
|
assert simple_name(path[0]) == self.name
|
||
|
if (len(path) == 1):
|
||
|
return self
|
||
|
child = self.__children.get(path[1])
|
||
|
if not child:
|
||
|
child = PathSegment(path[1], self)
|
||
|
self.__children[path[1]] = child
|
||
|
return child.get_child(path[1:])
|
||
|
|
||
|
def children(self):
|
||
|
"""Gets list of children.
|
||
|
"""
|
||
|
return self.__children.values()
|
||
|
|
||
|
def num_children(self):
|
||
|
"""Gets count of children.
|
||
|
"""
|
||
|
return len(self.__children)
|
||
|
|
||
|
|
||
|
class AsteriskProcessor(SwaggerPostProcessor):
|
||
|
"""A SwaggerPostProcessor which adds fields needed to generate Asterisk
|
||
|
RESTful HTTP binding code.
|
||
|
"""
|
||
|
|
||
|
#: How Swagger types map to C.
|
||
|
type_mapping = {
|
||
|
'string': 'const char *',
|
||
|
'boolean': 'int',
|
||
|
'number': 'int',
|
||
|
'int': 'int',
|
||
|
'long': 'long',
|
||
|
'double': 'double',
|
||
|
'float': 'float',
|
||
|
}
|
||
|
|
||
|
#: String conversion functions for string to C type.
|
||
|
convert_mapping = {
|
||
|
'string': '',
|
||
|
'int': 'atoi',
|
||
|
'long': 'atol',
|
||
|
'double': 'atof',
|
||
|
'boolean': 'ast_true',
|
||
|
}
|
||
|
|
||
|
#: JSON conversion functions
|
||
|
json_convert_mapping = {
|
||
|
'string': 'ast_json_string_get',
|
||
|
'int': 'ast_json_integer_get',
|
||
|
'long': 'ast_json_integer_get',
|
||
|
'double': 'ast_json_real_get',
|
||
|
'boolean': 'ast_json_is_true',
|
||
|
}
|
||
|
|
||
|
def __init__(self, wiki_prefix):
|
||
|
self.wiki_prefix = wiki_prefix
|
||
|
|
||
|
def process_resource_api(self, resource_api, context):
|
||
|
resource_api.wiki_prefix = self.wiki_prefix
|
||
|
# Derive a resource name from the API declaration's filename
|
||
|
resource_api.name = re.sub('\..*', '',
|
||
|
os.path.basename(resource_api.path))
|
||
|
# Now in all caps, for include guard
|
||
|
resource_api.name_caps = resource_api.name.upper()
|
||
|
resource_api.name_title = resource_api.name.capitalize()
|
||
|
resource_api.c_name = snakify(resource_api.name)
|
||
|
# Construct the PathSegement tree for the API.
|
||
|
if resource_api.api_declaration:
|
||
|
resource_api.root_path = PathSegment('', None)
|
||
|
for api in resource_api.api_declaration.apis:
|
||
|
segment = resource_api.root_path.get_child(api.path.split('/'))
|
||
|
for operation in api.operations:
|
||
|
segment.operations.append(operation)
|
||
|
api.full_name = segment.full_name
|
||
|
|
||
|
# Since every API path should start with /[resource], root should
|
||
|
# have exactly one child.
|
||
|
if resource_api.root_path.num_children() != 1:
|
||
|
raise SwaggerError(
|
||
|
"Should not mix resources in one API declaration", context)
|
||
|
# root_path isn't needed any more
|
||
|
resource_api.root_path = list(resource_api.root_path.children())[0]
|
||
|
if resource_api.name != resource_api.root_path.name:
|
||
|
raise SwaggerError(
|
||
|
"API declaration name should match", context)
|
||
|
resource_api.root_full_name = resource_api.root_path.full_name
|
||
|
|
||
|
def process_api(self, api, context):
|
||
|
api.wiki_path = wikify(api.path)
|
||
|
|
||
|
def process_operation(self, operation, context):
|
||
|
# Nicknames are camelCase, Asterisk coding is snake case
|
||
|
operation.c_nickname = snakify(operation.nickname)
|
||
|
operation.c_http_method = 'AST_HTTP_' + operation.http_method
|
||
|
if not operation.summary.endswith("."):
|
||
|
raise SwaggerError("Summary should end with .", context)
|
||
|
operation.wiki_summary = wikify(operation.summary or "")
|
||
|
operation.wiki_notes = wikify(operation.notes or "")
|
||
|
for error_response in operation.error_responses:
|
||
|
error_response.wiki_reason = wikify(error_response.reason or "")
|
||
|
operation.parse_body = (operation.body_parameter or operation.has_query_parameters) and True
|
||
|
|
||
|
def process_parameter(self, parameter, context):
|
||
|
if parameter.param_type == 'body':
|
||
|
parameter.is_body_parameter = True;
|
||
|
parameter.c_data_type = 'struct ast_json *'
|
||
|
else:
|
||
|
parameter.is_body_parameter = False;
|
||
|
if not parameter.data_type in self.type_mapping:
|
||
|
raise SwaggerError(
|
||
|
"Invalid parameter type %s" % parameter.data_type, context)
|
||
|
# Type conversions
|
||
|
parameter.c_data_type = self.type_mapping[parameter.data_type]
|
||
|
parameter.c_convert = self.convert_mapping[parameter.data_type]
|
||
|
parameter.json_convert = self.json_convert_mapping[parameter.data_type]
|
||
|
|
||
|
# Parameter names are camelcase, Asterisk convention is snake case
|
||
|
parameter.c_name = snakify(parameter.name)
|
||
|
# You shouldn't put a space between 'char *' and the variable
|
||
|
if parameter.c_data_type.endswith('*'):
|
||
|
parameter.c_space = ''
|
||
|
else:
|
||
|
parameter.c_space = ' '
|
||
|
parameter.wiki_description = wikify(parameter.description)
|
||
|
if parameter.allowable_values:
|
||
|
parameter.wiki_allowable_values = parameter.allowable_values.to_wiki()
|
||
|
else:
|
||
|
parameter.wiki_allowable_values = None
|
||
|
|
||
|
def process_model(self, model, context):
|
||
|
model.description_dox = model.description.replace('\n', '\n * ')
|
||
|
model.description_dox = re.sub(' *\n', '\n', model.description_dox)
|
||
|
model.wiki_description = wikify(model.description)
|
||
|
model.c_id = snakify(model.id)
|
||
|
return model
|
||
|
|
||
|
def process_property(self, prop, context):
|
||
|
if "-" in prop.name:
|
||
|
raise SwaggerError("Property names cannot have dashes", context)
|
||
|
if prop.name != prop.name.lower():
|
||
|
raise SwaggerError("Property name should be all lowercase",
|
||
|
context)
|
||
|
prop.wiki_description = wikify(prop.description)
|
||
|
|
||
|
def process_type(self, swagger_type, context):
|
||
|
swagger_type.c_name = snakify(swagger_type.name)
|
||
|
swagger_type.c_singular_name = snakify(swagger_type.singular_name)
|
||
|
swagger_type.wiki_name = wikify(swagger_type.name)
|