19 """Swagger data model objects.
21 These objects should map directly to the Swagger api-docs, without a lot of
22 additional fields. In the process of translation, it should also validate the
23 model for consistency against the Swagger spec (i.e., fail if fields are
24 missing, or have incorrect values).
26 See https://github.com/wordnik/swagger-core/wiki/API-Declaration for the spec.
29 from __future__
import print_function
38 SWAGGER_VERSIONS = [
"1.1",
"1.2"]
40 SWAGGER_PRIMITIVES = [
54 """Simple mix-in to make the repr of the model classes more meaningful.
57 return "%s(%s)" % (self.__class__, pprint.saferepr(self.__dict__))
60 def compare_versions(lhs, rhs):
61 '''Performs a lexicographical comparison between two version numbers.
63 This properly handles simple major.minor.whatever.sure.why.not version
64 numbers, but fails miserably if there's any letters in there.
71 @param lhs Left hand side of the comparison
72 @param rhs Right hand side of the comparison
73 @return < 0 if lhs < rhs
74 @return == 0 if lhs == rhs
75 @return > 0 if lhs > rhs
77 lhs = [int(v)
for v
in lhs.split(
'.')]
78 rhs = [int(v)
for v
in rhs.split(
'.')]
79 return (lhs > rhs) - (lhs < rhs)
83 """Context information for parsing.
85 This object is immutable. To change contexts (like adding an item to the
86 stack), use the next() and next_stack() functions to build a new one.
89 def __init__(self, swagger_version, stack):
94 return "ParsingContext(swagger_version=%s, stack=%s)" % (
97 def get_swagger_version(self):
103 swagger_version = property(get_swagger_version)
105 stack = property(get_stack)
107 def version_less_than(self, ver):
111 """Returns a new item pushed to the stack.
113 @param json: Current JSON object.
114 @param id_field: Field identifying this object.
115 @return New context with additional item in the stack.
117 if not id_field
in json:
118 raise SwaggerError(
"Missing id_field: %s" % id_field, self)
119 new_stack = self.
stack + [
'%s=%s' % (id_field, str(json[id_field]))]
122 def next(self, version=None, stack=None):
124 version = self.version
131 """Raised when an error is encountered mapping the JSON objects into the
138 @param msg: String message for the error.
139 @param context: ParsingContext object
140 @param cause: Optional exception that caused this one.
142 super(Exception, self).
__init__(msg, context, cause)
146 """Post processing interface for model objects. This processor can add
147 fields to model objects for additional information to use in the
151 """Post process a ResourceApi object.
153 @param resource_api: ResourceApi object.
154 @param context: Current context in the API.
159 """Post process an Api object.
161 @param api: Api object.
162 @param context: Current context in the API.
167 """Post process a Operation object.
169 @param operation: Operation object.
170 @param context: Current context in the API.
175 """Post process a Parameter object.
177 @param parameter: Parameter object.
178 @param context: Current context in the API.
183 """Post process a Model object.
185 @param model: Model object.
186 @param context: Current context in the API.
191 """Post process a Property object.
193 @param property: Property object.
194 @param context: Current context in the API.
199 """Post process a SwaggerType object.
201 @param swagger_type: ResourceListing object.
202 @param context: Current context in the API.
207 """Post process the overall ResourceListing object.
209 @param resource_listing: ResourceListing object.
210 @param context: Current context in the API.
215 class AllowableRange(Stringify):
216 """Model of a allowableValues of type RANGE
218 See https://github.com/wordnik/swagger-core/wiki/datatypes#complex-types
220 def __init__(self, min_value, max_value):
221 self.min_value = min_value
222 self.max_value = max_value
225 return "Allowed range: Min: {0}; Max: {1}".format(self.min_value, self.max_value)
229 """Model of a allowableValues of type LIST
231 See https://github.com/wordnik/swagger-core/wiki/datatypes#complex-types
233 def __init__(self, values):
237 return "Allowed values: {0}".format(
", ".join(self.
values))
240 def load_allowable_values(json, context):
241 """Parse a JSON allowableValues object.
243 This returns None, AllowableList or AllowableRange, depending on the
244 valueType in the JSON. If the valueType is not recognized, a SwaggerError
250 if not 'valueType' in json:
253 value_type = json[
'valueType']
255 if value_type ==
'RANGE':
256 if not 'min' in json
and not 'max' in json:
259 if value_type ==
'LIST':
260 if not 'values' in json:
263 raise SwaggerError(
"Unkown valueType %s" % value_type, context)
267 """Model of an operation's parameter.
269 See https://github.com/wordnik/swagger-core/wiki/parameters
272 required_fields = [
'name',
'paramType',
'dataType']
283 def load(self, parameter_json, processor, context):
284 context = context.next_stack(parameter_json,
'name')
285 validate_required_fields(parameter_json, self.
required_fields, context)
286 self.
name = parameter_json.get(
'name')
287 self.
param_type = parameter_json.get(
'paramType')
288 self.
description = parameter_json.get(
'description')
or ''
289 self.
data_type = parameter_json.get(
'dataType')
290 self.
required = parameter_json.get(
'required')
or False
293 parameter_json.get(
'allowableValues'), context)
294 self.
allow_multiple = parameter_json.get(
'allowMultiple')
or False
295 processor.process_parameter(self, context)
296 if parameter_json.get(
'allowedValues'):
298 "Field 'allowedValues' invalid; use 'allowableValues'",
302 def is_type(self, other_type):
307 """Model of an error response.
309 See https://github.com/wordnik/swagger-core/wiki/errors
312 required_fields = [
'code',
'reason']
318 def load(self, err_json, processor, context):
319 context = context.next_stack(err_json,
'code')
321 self.
code = err_json.get(
'code')
322 self.
reason = err_json.get(
'reason')
327 """Model of a data type.
339 def load(self, type_name, processor, context):
341 if type_name ==
'integer':
342 raise SwaggerError(
"The type for integer should be 'int'", context)
344 self.
name = type_name
345 type_param = get_list_parameter_type(self.
name)
346 self.
is_list = type_param
is not None
355 processor.process_type(self, context)
360 """Model of an operation on an API
362 See https://github.com/wordnik/swagger-core/wiki/API-Declaration#apis
365 required_fields = [
'httpMethod',
'nickname',
'responseClass',
'summary']
377 def load(self, op_json, processor, context):
378 context = context.next_stack(op_json,
'nickname')
381 self.
nickname = op_json.get(
'nickname')
383 response_class = op_json.get(
'responseClass')
385 response_class, processor, context)
388 self.
is_websocket = op_json.get(
'upgrade') ==
'websocket'
395 "upgrade: websocket is only valid on GET operations",
398 params_json = op_json.get(
'parameters')
or []
400 Parameter().load(j, processor, context)
for j
in params_json]
402 p
for p
in self.
parameters if p.is_type(
'query')]
405 p
for p
in self.
parameters if p.is_type(
'path')]
408 p
for p
in self.
parameters if p.is_type(
'header')]
416 p
for p
in self.
parameters if p.is_type(
'body')]
418 raise SwaggerError(
"Cannot have more than one body param", context)
422 self.
summary = op_json.get(
'summary')
423 self.
notes = op_json.get(
'notes')
424 err_json = op_json.get(
'errorResponses')
or []
426 ErrorResponse().load(j, processor, context)
for j
in err_json]
428 processor.process_operation(self, context)
433 """Model of a single API in an API declaration.
435 See https://github.com/wordnik/swagger-core/wiki/API-Declaration
438 required_fields = [
'path',
'operations']
445 def load(self, api_json, processor, context):
446 context = context.next_stack(api_json,
'path')
448 self.
path = api_json.get(
'path')
450 op_json = api_json.get(
'operations')
452 Operation().load(j, processor, context)
for j
in op_json]
454 processor.process_api(self, context)
458 def get_list_parameter_type(type_string):
459 """Returns the type parameter if the given type_string is List[].
461 @param type_string: Type string to parse
462 @returns Type parameter of the list, or None if not a List.
464 list_match = re.match(
'^List\[(.*)\]$', type_string)
465 return list_match
and list_match.group(1)
469 """Model of a Swagger property.
471 See https://github.com/wordnik/swagger-core/wiki/datatypes
474 required_fields = [
'type']
476 def __init__(self, name):
482 def load(self, property_json, processor, context):
485 context = context.next_stack({
'name': self.
name},
'name')
486 self.
description = property_json.get(
'description')
or ''
487 self.
required = property_json.get(
'required')
or False
489 type = property_json.get(
'type')
492 processor.process_property(self, context)
497 """Model of a Swagger model.
499 See https://github.com/wordnik/swagger-core/wiki/datatypes
502 required_fields = [
'description',
'properties']
515 def load(self, id, model_json, processor, context):
516 context = context.next_stack(model_json,
'id')
519 self.
id = model_json.get(
'id')
520 self.
id_lc = self.id.lower()
522 raise SwaggerError(
"Model id doesn't match name", context)
523 self.
subtypes = model_json.get(
'subTypes')
or []
524 if self.
subtypes and context.version_less_than(
"1.2"):
525 raise SwaggerError(
"Type extension support added in Swagger 1.2",
528 props = model_json.get(
'properties').items()
or []
530 Property(k).load(j, processor, context)
for (k, j)
in props]
533 discriminator = model_json.get(
'discriminator')
536 if context.version_less_than(
"1.2"):
537 raise SwaggerError(
"Discriminator support added in Swagger 1.2",
540 discr_props = [p
for p
in self.
__properties if p.name == discriminator]
543 "Discriminator '%s' does not name a property of '%s'" % (
544 discriminator, self.
id),
550 indent=2, separators=(
',',
': '))
552 processor.process_model(self, context)
558 def extends_lc(self):
561 def set_extends_type(self, extends_type):
564 def set_subtype_types(self, subtype_types):
568 """Returns the discriminator, digging through base types if needed.
573 def properties(self):
576 base_props = self.__extends_type.properties()
579 def has_properties(self):
583 """Returns the full list of all subtypes, including sub-subtypes.
587 for subsubtypes
in subtype.all_subtypes()]
588 return sorted(res, key=
lambda m: m.id)
591 """Returns True if type has any subtypes.
597 """Model class for an API Declaration.
599 See https://github.com/wordnik/swagger-core/wiki/API-Declaration
603 'swaggerVersion',
'_author',
'_copyright',
'apiVersion',
'basePath',
604 'resourcePath',
'apis',
'models'
617 def load_file(self, api_declaration_file, processor):
620 return self.
__load_file(api_declaration_file, processor, context)
623 except Exception
as e:
624 print(
"Error: ", traceback.format_exc(), file=sys.stderr)
626 "Error loading %s" % api_declaration_file, context, e)
628 def __load_file(self, api_declaration_file, processor, context):
629 with open(api_declaration_file)
as fp:
630 self.
load(json.load(fp), processor, context)
632 expected_resource_path =
'/api-docs/' + \
633 os.path.basename(api_declaration_file) \
634 .replace(
".json",
".{format}")
637 print(
"%s != %s" % (self.
resource_path, expected_resource_path),
639 raise SwaggerError(
"resourcePath has incorrect value", context)
643 def load(self, api_decl_json, processor, context):
644 """Loads a resource from a single Swagger resource.json file.
655 self.
author = api_decl_json.get(
'_author')
656 self.
copyright = api_decl_json.get(
'_copyright')
658 self.
base_path = api_decl_json.get(
'basePath')
661 api_json = api_decl_json.get(
'apis')
or []
663 Api().
load(j, processor, context)
for j
in api_json]
665 for api
in self.
apis:
666 if api.path
in paths:
667 raise SwaggerError(
"API with duplicated path: %s" % api.path, context)
670 models = api_decl_json.get(
'models').items()
or []
672 for (id, json)
in models]
675 model_dict = dict((m.id, m)
for m
in self.
models)
677 def link_subtype(name):
678 res = model_dict.get(name)
682 res.set_extends_type(m)
685 m.set_subtype_types([
686 link_subtype(subtype)
for subtype
in m.subtypes])
691 """Model of an API listing in the resources.json file.
694 required_fields = [
'path',
'description']
701 def load(self, api_json, processor, context):
702 context = context.next_stack(api_json,
'path')
704 self.
path = api_json[
'path'].replace(
'{format}',
'json')
707 if not self.
path or self.
path[0] !=
'/':
709 processor.process_resource_api(self, context)
712 def load_api_declaration(self, base_dir, processor):
715 processor.process_resource_api(self, [self.
file])
719 """Model of Swagger's resources.json file.
722 required_fields = [
'apiVersion',
'basePath',
'apis']
730 def load_file(self, resource_file, processor):
733 return self.
__load_file(resource_file, processor, context)
736 except Exception
as e:
737 print(
"Error: ", traceback.format_exc(), file=sys.stderr)
739 "Error loading %s" % resource_file, context, e)
741 def __load_file(self, resource_file, processor, context):
742 with open(resource_file)
as fp:
743 return self.
load(json.load(fp), processor, context)
745 def load(self, resources_json, processor, context):
752 validate_required_fields(resources_json, self.
required_fields, context)
754 self.
base_path = resources_json[
'basePath']
755 apis_json = resources_json[
'apis']
757 ResourceApi().load(j, processor, context)
for j
in apis_json]
758 processor.process_resource_listing(self, context)
762 def validate_required_fields(json, required_fields, context):
763 """Checks a JSON object for a set of required fields.
765 If any required field is missing, a SwaggerError is raised.
767 @param json: JSON object to check.
768 @param required_fields: List of required fields.
769 @param context: Current context in the API.
771 missing_fields = [f
for f
in required_fields
if not f
in json]
775 "Missing fields: %s" %
', '.join(missing_fields), context)
def next_stack(self, json, id_field)
def __load_file(self, resource_file, processor, context)
def process_model(self, model, context)
def process_api(self, api, context)
def process_operation(self, operation, context)
def load(self, resources_json, processor, context)
def process_property(self, property, context)
def process_resource_api(self, resource_api, context)
static int load_file(const char *filename, char **ret)
Read a TEXT file into a string and return the length.
def process_type(self, swagger_type, context)
def __load_file(self, api_declaration_file, processor, context)
def load(self, api_decl_json, processor, context)
def process_parameter(self, parameter, context)
def process_resource_listing(self, resource_listing, context)