|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +''' |
| 3 | +This module give access to OpenAPI specifications schemas |
| 4 | +and allows to validate specs against them. |
| 5 | +
|
| 6 | +.. versionadded:: 0.12.1 |
| 7 | +''' |
| 8 | +from __future__ import unicode_literals |
| 9 | + |
| 10 | +import json |
| 11 | +import pkg_resources |
| 12 | + |
| 13 | +from collections import Mapping |
| 14 | + |
| 15 | +from jsonschema import Draft4Validator |
| 16 | + |
| 17 | +from flask_restplus import errors |
| 18 | + |
| 19 | + |
| 20 | +class SchemaValidationError(errors.ValidationError): |
| 21 | + ''' |
| 22 | + Raised when specification is not valid |
| 23 | +
|
| 24 | + .. versionadded:: 0.12.1 |
| 25 | + ''' |
| 26 | + def __init__(self, msg, errors=None): |
| 27 | + super(SchemaValidationError, self).__init__(msg) |
| 28 | + self.errors = errors |
| 29 | + |
| 30 | + def __str__(self): |
| 31 | + msg = [self.msg] |
| 32 | + for error in sorted(self.errors, key=lambda e: e.path): |
| 33 | + path = '.'.join(error.path) |
| 34 | + msg.append('- {}: {}'.format(path, error.message)) |
| 35 | + for suberror in sorted(error.context, key=lambda e: e.schema_path): |
| 36 | + path = '.'.join(suberror.schema_path) |
| 37 | + msg.append(' - {}: {}'.format(path, suberror.message)) |
| 38 | + return '\n'.join(msg) |
| 39 | + |
| 40 | + __unicode__ = __str__ |
| 41 | + |
| 42 | + |
| 43 | +class LazySchema(Mapping): |
| 44 | + ''' |
| 45 | + A thin wrapper around schema file lazy loading the data on first access |
| 46 | +
|
| 47 | + :param filename str: The package relative json schema filename |
| 48 | + :param validator: The jsonschema validator class version |
| 49 | +
|
| 50 | + .. versionadded:: 0.12.1 |
| 51 | + ''' |
| 52 | + def __init__(self, filename, validator=Draft4Validator): |
| 53 | + super(LazySchema, self).__init__() |
| 54 | + self.filename = filename |
| 55 | + self._schema = None |
| 56 | + self._validator = validator |
| 57 | + |
| 58 | + def _load(self): |
| 59 | + if not self._schema: |
| 60 | + data = pkg_resources.resource_string(__name__, self.filename) |
| 61 | + self._schema = json.loads(data) |
| 62 | + |
| 63 | + def __getitem__(self, key): |
| 64 | + self._load() |
| 65 | + return self._schema.__getitem__(key) |
| 66 | + |
| 67 | + def __iter__(self): |
| 68 | + self._load() |
| 69 | + return self._schema.__iter__() |
| 70 | + |
| 71 | + def __len__(self): |
| 72 | + self._load() |
| 73 | + return self._schema.__len__() |
| 74 | + |
| 75 | + @property |
| 76 | + def validator(self): |
| 77 | + '''The jsonschema validator to validate against''' |
| 78 | + return self._validator(self) |
| 79 | + |
| 80 | + |
| 81 | +#: OpenAPI 2.0 specification schema |
| 82 | +OAS_20 = LazySchema('oas-2.0.json') |
| 83 | + |
| 84 | +#: Map supported OpenAPI versions to their JSON schema |
| 85 | +VERSIONS = { |
| 86 | + '2.0': OAS_20, |
| 87 | +} |
| 88 | + |
| 89 | + |
| 90 | +def validate(data): |
| 91 | + ''' |
| 92 | + Validate an OpenAPI specification. |
| 93 | +
|
| 94 | + Supported OpenAPI versions: 2.0 |
| 95 | +
|
| 96 | + :param data dict: The specification to validate |
| 97 | + :returns boolean: True if the specification is valid |
| 98 | + :raises SchemaValidationError: when the specification is invalid |
| 99 | + :raises flask_restplus.errors.SpecsError: when it's not possible to determinate |
| 100 | + the schema to validate against |
| 101 | +
|
| 102 | + .. versionadded:: 0.12.1 |
| 103 | + ''' |
| 104 | + if 'swagger' not in data: |
| 105 | + raise errors.SpecsError('Unable to determinate OpenAPI schema version') |
| 106 | + |
| 107 | + version = data['swagger'] |
| 108 | + if version not in VERSIONS: |
| 109 | + raise errors.SpecsError('Unknown OpenAPI schema version "{}"'.format(version)) |
| 110 | + |
| 111 | + validator = VERSIONS[version].validator |
| 112 | + |
| 113 | + validation_errors = list(validator.iter_errors(data)) |
| 114 | + if validation_errors: |
| 115 | + raise SchemaValidationError('OpenAPI {} validation failed'.format(version), |
| 116 | + errors=validation_errors) |
| 117 | + return True |
0 commit comments