Skip to content

Commit 6d78075

Browse files
authored
Merge pull request #167 from p1c2u/feature/detect-spec-schema-version
detect spec schema version
2 parents dfe4ccb + 54e901b commit 6d78075

File tree

8 files changed

+115
-37
lines changed

8 files changed

+115
-37
lines changed

README.rst

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,21 @@ By default, OpenAPI v3.1 syntax is expected. To validate an OpenAPI v3.1 spec:
8484
# If no exception is raised by validate_spec(), the spec is valid.
8585
validate_spec(spec_dict)
8686
87-
validate_spec({})
87+
validate_spec({'openapi': '3.1.0'})
8888
8989
Traceback (most recent call last):
9090
...
91-
OpenAPIValidationError: 'openapi' is a required property
91+
OpenAPIValidationError: 'info' is a required property
9292
93-
In order to validate a Swagger / OpenAPI 2.0 spec file, import ``validate_v2_spec`` instead of ``validate_spec``.
94-
In order to validate a OpenAPI 3.0 spec file, import ``validate_v30_spec`` instead of ``validate_spec``.
93+
In order to explicitly validate a:
9594

96-
You can also explicitly import ``validate_v31_spec`` if you want to disambiguate the expected version or
97-
explicitly import ``validate_v3_spec`` which is a shortcut to the latest v3 release.
95+
* Swagger / OpenAPI 2.0 spec file, import ``validate_v2_spec``
96+
* OpenAPI 3.0 spec file, import ``validate_v30_spec``
97+
* OpenAPI 3.1 spec file, import ``validate_v31_spec``
98+
99+
instead of ``validate_spec``.
100+
101+
You can also explicitly import ``validate_v3_spec`` which is a shortcut to the latest v3 release.
98102

99103
Add ``spec_url`` to validate spec with relative files:
100104

@@ -111,6 +115,16 @@ You can also validate spec from url:
111115
# If no exception is raised by validate_spec_url(), the spec is valid.
112116
validate_spec_url('http://example.com/openapi.json')
113117
118+
In order to explicitly validate a:
119+
120+
* Swagger / OpenAPI 2.0 spec file, import ``validate_v2_spec_url``
121+
* OpenAPI 3.0 spec file, import ``validate_v30_spec_url``
122+
* OpenAPI 3.1 spec file, import ``validate_v31_spec_url``
123+
124+
instead of ``validate_spec_url``.
125+
126+
You can also explicitly import ``validate_v3_spec_url`` which is a shortcut to the latest v3 release.
127+
114128
If you want to iterate through validation errors:
115129

116130
.. code:: python
@@ -130,4 +144,4 @@ Related projects
130144
License
131145
#######
132146

133-
Copyright (c) 2017-2021, Artur Maciag, All rights reserved. Apache v2
147+
Copyright (c) 2017-2022, Artur Maciag, All rights reserved. Apache v2

openapi_spec_validator/__init__.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# -*- coding: utf-8 -*-
22
from jsonschema_spec.handlers import default_handlers
33

4-
from openapi_spec_validator.shortcuts import (
5-
validate_spec_factory, validate_spec_url_factory,
6-
)
4+
from openapi_spec_validator.shortcuts import validate_spec_detect_factory
5+
from openapi_spec_validator.shortcuts import validate_spec_url_detect_factory
6+
from openapi_spec_validator.shortcuts import validate_spec_factory
7+
from openapi_spec_validator.shortcuts import validate_spec_url_factory
78
from openapi_spec_validator.validation import openapi_v2_spec_validator
89
from openapi_spec_validator.validation import openapi_v3_spec_validator
910
from openapi_spec_validator.validation import openapi_v30_spec_validator
@@ -33,22 +34,30 @@
3334
]
3435

3536
# shortcuts
36-
validate_v2_spec = validate_spec_factory(openapi_v2_spec_validator.validate)
37+
validate_spec = validate_spec_detect_factory({
38+
("swagger", "2.0"): openapi_v2_spec_validator,
39+
("openapi", "3.0"): openapi_v30_spec_validator,
40+
("openapi", "3.1"): openapi_v31_spec_validator,
41+
},
42+
)
43+
validate_spec_url = validate_spec_url_detect_factory({
44+
("swagger", "2.0"): openapi_v2_spec_validator,
45+
("openapi", "3.0"): openapi_v30_spec_validator,
46+
("openapi", "3.1"): openapi_v31_spec_validator,
47+
},
48+
)
49+
validate_v2_spec = validate_spec_factory(openapi_v2_spec_validator)
3750
validate_v2_spec_url = validate_spec_url_factory(
38-
openapi_v2_spec_validator.validate, default_handlers)
51+
openapi_v2_spec_validator)
3952

40-
validate_v30_spec = validate_spec_factory(openapi_v30_spec_validator.validate)
53+
validate_v30_spec = validate_spec_factory(openapi_v30_spec_validator)
4154
validate_v30_spec_url = validate_spec_url_factory(
42-
openapi_v30_spec_validator.validate, default_handlers)
55+
openapi_v30_spec_validator)
4356

44-
validate_v31_spec = validate_spec_factory(openapi_v31_spec_validator.validate)
57+
validate_v31_spec = validate_spec_factory(openapi_v31_spec_validator)
4558
validate_v31_spec_url = validate_spec_url_factory(
46-
openapi_v31_spec_validator.validate, default_handlers)
59+
openapi_v31_spec_validator)
4760

4861
# aliases to the latest v3 version
4962
validate_v3_spec = validate_v31_spec
5063
validate_v3_spec_url = validate_v31_spec_url
51-
52-
# aliases to the latest version
53-
validate_spec = validate_v3_spec
54-
validate_spec_url = validate_v3_spec_url

openapi_spec_validator/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class OpenAPISpecValidatorError(Exception):
2+
pass
3+
4+
5+
class ValidatorDetectError(OpenAPISpecValidatorError):
6+
pass

openapi_spec_validator/shortcuts.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
"""OpenAPI spec validator shortcuts module."""
22
import urllib.parse
33

4+
from jsonschema_spec.handlers import all_urls_handler
45

5-
def validate_spec_factory(validator_callable):
6+
from openapi_spec_validator.exceptions import ValidatorDetectError
7+
8+
9+
def detect_validator(choices, spec):
10+
for (key, value), validator in choices.items():
11+
if key in spec and spec[key].startswith(value):
12+
return validator
13+
raise ValidatorDetectError("Spec schema version not detected")
14+
15+
16+
def validate_spec_detect_factory(choices):
617
def validate(spec, spec_url=''):
7-
return validator_callable(spec, spec_url=spec_url)
18+
validator_class = detect_validator(choices, spec)
19+
return validator_class.validate(spec, spec_url=spec_url)
20+
return validate
21+
22+
23+
def validate_spec_factory(validator_class):
24+
def validate(spec, spec_url=''):
25+
return validator_class.validate(spec, spec_url=spec_url)
26+
return validate
27+
28+
29+
def validate_spec_url_detect_factory(choices):
30+
def validate(spec_url):
31+
spec = all_urls_handler(spec_url)
32+
validator_class = detect_validator(choices, spec)
33+
return validator_class.validate(spec, spec_url=spec_url)
834
return validate
935

1036

11-
def validate_spec_url_factory(validator_callable, handlers):
12-
def validate(url):
13-
result = urllib.parse.urlparse(url)
14-
handler = handlers[result.scheme]
15-
spec = handler(url)
16-
return validator_callable(spec, spec_url=url)
37+
def validate_spec_url_factory(validator_class):
38+
def validate(spec_url):
39+
spec = all_urls_handler(spec_url)
40+
return validator_class.validate(spec, spec_url=spec_url)
1741
return validate

tests/integration/data/empty.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

tests/integration/test_shortcuts.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,36 @@
11
import pytest
2-
from jsonschema_spec.handlers import default_handlers
32

43
from openapi_spec_validator import (
4+
validate_spec, validate_spec_url,
55
validate_v2_spec, validate_v2_spec_url,
66
validate_spec_factory, validate_spec_url_factory,
77
openapi_v2_spec_validator, openapi_v30_spec_validator,
88
validate_v30_spec_url, validate_v30_spec,
99
)
10+
from openapi_spec_validator.exceptions import ValidatorDetectError
1011
from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
1112

1213

13-
class TestLocalOpenAPIv20Validator:
14+
class TestValidateSpec:
15+
16+
def test_spec_schema_version_not_detected(self):
17+
spec = {}
18+
19+
with pytest.raises(ValidatorDetectError):
20+
validate_spec(spec)
21+
22+
23+
class TestValidateSpecUrl:
24+
25+
def test_spec_schema_version_not_detected(self, factory):
26+
spec_path = "data/empty.yaml"
27+
spec_url = factory.spec_file_url(spec_path)
28+
29+
with pytest.raises(ValidatorDetectError):
30+
validate_spec_url(spec_url)
31+
32+
33+
class TestValidatev2Spec:
1434

1535
LOCAL_SOURCE_DIRECTORY = "data/v2.0/"
1636

@@ -25,10 +45,11 @@ def test_valid(self, factory, spec_file):
2545
spec = factory.spec_from_file(spec_path)
2646
spec_url = factory.spec_file_url(spec_path)
2747

48+
validate_spec(spec)
2849
validate_v2_spec(spec)
2950

3051
validate_spec_factory(
31-
openapi_v2_spec_validator.validate)(spec, spec_url)
52+
openapi_v2_spec_validator)(spec, spec_url)
3253

3354
@pytest.mark.parametrize('spec_file', [
3455
"empty.yaml",
@@ -41,7 +62,7 @@ def test_falied(self, factory, spec_file):
4162
validate_v2_spec(spec)
4263

4364

44-
class TestLocalOpenAPIv30Validator:
65+
class TestValidatev30Spec:
4566

4667
LOCAL_SOURCE_DIRECTORY = "data/v3.0/"
4768

@@ -56,10 +77,11 @@ def test_valid(self, factory, spec_file):
5677
spec = factory.spec_from_file(spec_path)
5778
spec_url = factory.spec_file_url(spec_path)
5879

80+
validate_spec(spec)
5981
validate_v30_spec(spec)
6082

6183
validate_spec_factory(
62-
openapi_v30_spec_validator.validate)(spec, spec_url)
84+
openapi_v30_spec_validator)(spec, spec_url)
6385

6486
@pytest.mark.parametrize('spec_file', [
6587
"empty.yaml",
@@ -72,7 +94,7 @@ def test_falied(self, factory, spec_file):
7294
validate_v30_spec(spec)
7395

7496

75-
class TestRemoteValidateV20:
97+
class TestValidatev2SpecUrl:
7698

7799
REMOTE_SOURCE_URL = (
78100
"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/"
@@ -92,13 +114,14 @@ def remote_test_suite_file_path(self, test_file):
92114
def test_valid(self, spec_file):
93115
spec_url = self.remote_test_suite_file_path(spec_file)
94116

117+
validate_spec_url(spec_url)
95118
validate_v2_spec_url(spec_url)
96119

97120
validate_spec_url_factory(
98-
openapi_v2_spec_validator.validate, default_handlers)(spec_url)
121+
openapi_v2_spec_validator)(spec_url)
99122

100123

101-
class TestRemoteValidateV30:
124+
class TestValidatev30SpecUrl:
102125

103126
REMOTE_SOURCE_URL = (
104127
"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/"
@@ -118,7 +141,8 @@ def remote_test_suite_file_path(self, test_file):
118141
def test_valid(self, spec_file):
119142
spec_url = self.remote_test_suite_file_path(spec_file)
120143

144+
validate_spec_url(spec_url)
121145
validate_v30_spec_url(spec_url)
122146

123147
validate_spec_url_factory(
124-
openapi_v30_spec_validator.validate, default_handlers)(spec_url)
148+
openapi_v30_spec_validator)(spec_url)

0 commit comments

Comments
 (0)