__init__.py 3.4 KB

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