mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-20 12:20:12 +00:00
This patch adds a RESTful HTTP interface to Asterisk.
The API itself is documented using Swagger, a lightweight mechanism for documenting RESTful API's using JSON. This allows us to use swagger-ui to provide executable documentation for the API, generate client bindings in different languages, and generate a lot of the boilerplate code for implementing the RESTful bindings. The API docs live in the rest-api/ directory. The RESTful bindings are generated from the Swagger API docs using a set of Mustache templates. The code generator is written in Python, and uses Pystache. Pystache has no dependencies, and be installed easily using pip. Code generation code lives in rest-api-templates/. The generated code reduces a lot of boilerplate when it comes to handling HTTP requests. It also helps us have greater consistency in the REST API. (closes issue ASTERISK-20891) Review: https://reviewboard.asterisk.org/r/2376/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@386232 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
179
rest-api-templates/asterisk_processor.py
Normal file
179
rest-api-templates/asterisk_processor.py
Normal file
@@ -0,0 +1,179 @@
|
||||
#
|
||||
# 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 re
|
||||
|
||||
from swagger_model import *
|
||||
|
||||
|
||||
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 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 is '-':
|
||||
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 decendents 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 = {
|
||||
'const char *': '',
|
||||
'int': 'atoi',
|
||||
'long': 'atol',
|
||||
'double': 'atof',
|
||||
}
|
||||
|
||||
def process_api(self, resource_api, context):
|
||||
# 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, from include guard
|
||||
resource_api.name_caps = resource_api.name.upper()
|
||||
# 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)
|
||||
# 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 = 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_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)
|
||||
|
||||
def process_parameter(self, parameter, context):
|
||||
if not parameter.data_type in self.type_mapping:
|
||||
raise SwaggerError(
|
||||
"Invalid parameter type %s" % paramter.data_type, context)
|
||||
# Parameter names are camelcase, Asterisk convention is snake case
|
||||
parameter.c_name = snakify(parameter.name)
|
||||
parameter.c_data_type = self.type_mapping[parameter.data_type]
|
||||
parameter.c_convert = self.convert_mapping[parameter.c_data_type]
|
||||
# You shouldn't put a space between 'char *' and the variable
|
||||
if parameter.c_data_type.endswith('*'):
|
||||
parameter.c_space = ''
|
||||
else:
|
||||
parameter.c_space = ' '
|
Reference in New Issue
Block a user