diff options
Diffstat (limited to 'libs/flask_restx/schemas/__init__.py')
-rw-r--r-- | libs/flask_restx/schemas/__init__.py | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/libs/flask_restx/schemas/__init__.py b/libs/flask_restx/schemas/__init__.py new file mode 100644 index 000000000..d6dc2ac0f --- /dev/null +++ b/libs/flask_restx/schemas/__init__.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +""" +This module give access to OpenAPI specifications schemas +and allows to validate specs against them. + +.. versionadded:: 0.12.1 +""" +from __future__ import unicode_literals + +import io +import json +import pkg_resources + +try: + from collections.abc import Mapping +except ImportError: + # TODO Remove this to drop Python2 support + from collections import Mapping +from jsonschema import Draft4Validator + +from flask_restx import errors + + +class SchemaValidationError(errors.ValidationError): + """ + Raised when specification is not valid + + .. versionadded:: 0.12.1 + """ + + def __init__(self, msg, errors=None): + super(SchemaValidationError, self).__init__(msg) + self.errors = errors + + def __str__(self): + msg = [self.msg] + for error in sorted(self.errors, key=lambda e: e.path): + path = ".".join(error.path) + msg.append("- {}: {}".format(path, error.message)) + for suberror in sorted(error.context, key=lambda e: e.schema_path): + path = ".".join(suberror.schema_path) + msg.append(" - {}: {}".format(path, suberror.message)) + return "\n".join(msg) + + __unicode__ = __str__ + + +class LazySchema(Mapping): + """ + A thin wrapper around schema file lazy loading the data on first access + + :param filename str: The package relative json schema filename + :param validator: The jsonschema validator class version + + .. versionadded:: 0.12.1 + """ + + def __init__(self, filename, validator=Draft4Validator): + super(LazySchema, self).__init__() + self.filename = filename + self._schema = None + self._validator = validator + + def _load(self): + if not self._schema: + filename = pkg_resources.resource_filename(__name__, self.filename) + with io.open(filename) as infile: + self._schema = json.load(infile) + + def __getitem__(self, key): + self._load() + return self._schema.__getitem__(key) + + def __iter__(self): + self._load() + return self._schema.__iter__() + + def __len__(self): + self._load() + return self._schema.__len__() + + @property + def validator(self): + """The jsonschema validator to validate against""" + return self._validator(self) + + +#: OpenAPI 2.0 specification schema +OAS_20 = LazySchema("oas-2.0.json") + +#: Map supported OpenAPI versions to their JSON schema +VERSIONS = { + "2.0": OAS_20, +} + + +def validate(data): + """ + Validate an OpenAPI specification. + + Supported OpenAPI versions: 2.0 + + :param data dict: The specification to validate + :returns boolean: True if the specification is valid + :raises SchemaValidationError: when the specification is invalid + :raises flask_restx.errors.SpecsError: when it's not possible to determinate + the schema to validate against + + .. versionadded:: 0.12.1 + """ + if "swagger" not in data: + raise errors.SpecsError("Unable to determinate OpenAPI schema version") + + version = data["swagger"] + if version not in VERSIONS: + raise errors.SpecsError('Unknown OpenAPI schema version "{}"'.format(version)) + + validator = VERSIONS[version].validator + + validation_errors = list(validator.iter_errors(data)) + if validation_errors: + raise SchemaValidationError( + "OpenAPI {} validation failed".format(version), errors=validation_errors + ) + return True |