Skip to content

Commit 0a0cb88

Browse files
committed
feat(parser): Detect OpenAPI documents of incorrect versions (closes #281)
1 parent aede9be commit 0a0cb88

37 files changed

+186
-69
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
8+
## 0.8.0 - Unreleased
99

1010
### Additions
1111

@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- `setup` will generate a pyproject.toml with no Poetry information, and instead create a `setup.py` with the
1515
project info.
1616
- `none` will not create a project folder at all, only the inner package folder (which won't be inner anymore)
17+
- Attempt to detect and alert users if they are using an unsupported version of OpenAPI (#281).
1718

1819
### Changes
1920

openapi_python_client/parser/openapi.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,22 @@ class GeneratorData:
260260
enums: Dict[str, EnumProperty]
261261

262262
@staticmethod
263-
def from_dict(d: Dict[str, Dict[str, Any]]) -> Union["GeneratorData", GeneratorError]:
263+
def from_dict(d: Dict[str, Any]) -> Union["GeneratorData", GeneratorError]:
264264
""" Create an OpenAPI from dict """
265265
try:
266266
openapi = oai.OpenAPI.parse_obj(d)
267267
except ValidationError as e:
268-
return GeneratorError(header="Failed to parse OpenAPI document", detail=str(e))
268+
detail = str(e)
269+
if "swagger" in d:
270+
detail = (
271+
"You may be trying to use a Swagger document; this is not supported by this project.\n\n" + detail
272+
)
273+
return GeneratorError(header="Failed to parse OpenAPI document", detail=detail)
274+
if openapi.openapi.major != 3:
275+
return GeneratorError(
276+
header="openapi-python-client only supports OpenAPI 3.x",
277+
detail=f"The version of the provided document was {openapi.openapi}",
278+
)
269279
if openapi.components is None or openapi.components.schemas is None:
270280
schemas = Schemas()
271281
else:
Lines changed: 37 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,50 @@
1-
"""
2-
OpenAPI v3.0.3 schema types, created according to the specification:
3-
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
4-
5-
The type orders are according to the contents of the specification:
6-
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#table-of-contents
7-
"""
8-
91
__all__ = [
10-
"Components",
11-
"Contact",
12-
"Discriminator",
13-
"Encoding",
14-
"Example",
15-
"ExternalDocumentation",
16-
"Header",
17-
"Info",
18-
"License",
19-
"Link",
202
"MediaType",
21-
"OAuthFlow",
22-
"OAuthFlows",
233
"OpenAPI",
244
"Operation",
255
"Parameter",
266
"PathItem",
27-
"Paths",
287
"Reference",
298
"RequestBody",
309
"Response",
3110
"Responses",
3211
"Schema",
33-
"SecurityRequirement",
34-
"SecurityScheme",
35-
"Server",
36-
"ServerVariable",
37-
"Tag",
38-
"XML",
3912
]
4013

41-
from .components import Components
42-
from .contact import Contact
43-
from .discriminator import Discriminator
44-
from .encoding import Encoding
45-
from .example import Example
46-
from .external_documentation import ExternalDocumentation
47-
from .header import Header
48-
from .info import Info
49-
from .license import License
50-
from .link import Link
51-
from .media_type import MediaType
52-
from .oauth_flow import OAuthFlow
53-
from .oauth_flows import OAuthFlows
54-
from .open_api import OpenAPI
55-
from .operation import Operation
56-
from .parameter import Parameter
57-
from .path_item import PathItem
58-
from .paths import Paths
59-
from .reference import Reference
60-
from .request_body import RequestBody
61-
from .response import Response
62-
from .responses import Responses
63-
from .schema import Schema
64-
from .security_requirement import SecurityRequirement
65-
from .security_scheme import SecurityScheme
66-
from .server import Server
67-
from .server_variable import ServerVariable
68-
from .tag import Tag
69-
from .xml import XML
14+
15+
import re
16+
from typing import Callable, Iterator
17+
18+
from .openapi_schema_pydantic import MediaType
19+
from .openapi_schema_pydantic import OpenAPI as _OpenAPI
20+
from .openapi_schema_pydantic import Operation, Parameter, PathItem, Reference, RequestBody, Response, Responses, Schema
21+
22+
regex = re.compile(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)")
23+
24+
25+
class SemVer:
26+
def __init__(self, str_value: str) -> None:
27+
self.str_value = str_value
28+
if not isinstance(str_value, str):
29+
raise TypeError("string required")
30+
m = regex.fullmatch(str_value)
31+
if not m:
32+
raise ValueError("invalid semantic versioning format")
33+
self.major = int(m.group(1))
34+
self.minor = int(m.group(2))
35+
self.patch = int(m.group(3))
36+
37+
@classmethod
38+
def __get_validators__(cls) -> Iterator[Callable[[str], "SemVer"]]:
39+
yield cls.validate
40+
41+
@classmethod
42+
def validate(cls, v: str) -> "SemVer":
43+
return cls(v)
44+
45+
def __str__(self) -> str:
46+
return self.str_value
47+
48+
49+
class OpenAPI(_OpenAPI):
50+
openapi: SemVer

openapi_python_client/schema/README.md renamed to openapi_python_client/schema/openapi_schema_pydantic/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# OpenAPI v3.0.3 schema classes
1+
Everything in this directory (including the rest of this file after this paragraph) is a vendored copy of [openapi-schem-pydantic](https://github.com/kuimono/openapi-schema-pydantic) and is licensed under the LICENSE file in this directory.
22

33
## Alias
44

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
OpenAPI v3.0.3 schema types, created according to the specification:
3+
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
4+
5+
The type orders are according to the contents of the specification:
6+
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#table-of-contents
7+
"""
8+
9+
__all__ = [
10+
"Components",
11+
"Contact",
12+
"Discriminator",
13+
"Encoding",
14+
"Example",
15+
"ExternalDocumentation",
16+
"Header",
17+
"Info",
18+
"License",
19+
"Link",
20+
"MediaType",
21+
"OAuthFlow",
22+
"OAuthFlows",
23+
"OpenAPI",
24+
"Operation",
25+
"Parameter",
26+
"PathItem",
27+
"Paths",
28+
"Reference",
29+
"RequestBody",
30+
"Response",
31+
"Responses",
32+
"Schema",
33+
"SecurityRequirement",
34+
"SecurityScheme",
35+
"Server",
36+
"ServerVariable",
37+
"Tag",
38+
"XML",
39+
]
40+
41+
from .components import Components
42+
from .contact import Contact
43+
from .discriminator import Discriminator
44+
from .encoding import Encoding
45+
from .example import Example
46+
from .external_documentation import ExternalDocumentation
47+
from .header import Header
48+
from .info import Info
49+
from .license import License
50+
from .link import Link
51+
from .media_type import MediaType
52+
from .oauth_flow import OAuthFlow
53+
from .oauth_flows import OAuthFlows
54+
from .open_api import OpenAPI
55+
from .operation import Operation
56+
from .parameter import Parameter
57+
from .path_item import PathItem
58+
from .paths import Paths
59+
from .reference import Reference
60+
from .request_body import RequestBody
61+
from .response import Response
62+
from .responses import Responses
63+
from .schema import Schema
64+
from .security_requirement import SecurityRequirement
65+
from .security_scheme import SecurityScheme
66+
from .server import Server
67+
from .server_variable import ServerVariable
68+
from .tag import Tag
69+
from .xml import XML

0 commit comments

Comments
 (0)