Skip to content

Commit

Permalink
Merge pull request #825 from tfranzel/oas3.1
Browse files Browse the repository at this point in the history
OAS 3.1
  • Loading branch information
tfranzel committed Nov 30, 2023
2 parents e022b4e + 28c0bc6 commit f31238e
Show file tree
Hide file tree
Showing 12 changed files with 2,285 additions and 18 deletions.
9 changes: 8 additions & 1 deletion drf_spectacular/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,14 @@ def extract_hash(schema):
if '' in prop_enum_original_list:
components.append(create_enum_component('BlankEnum', schema={'enum': ['']}))
if None in prop_enum_original_list:
components.append(create_enum_component('NullEnum', schema={'enum': [None]}))
if spectacular_settings.OAS_VERSION.startswith('3.1'):
components.append(create_enum_component('NullEnum', schema={'type': 'null'}))
else:
components.append(create_enum_component('NullEnum', schema={'enum': [None]}))

# undo OAS 3.1 type list NULL construction as we cover this in a separate component already
if spectacular_settings.OAS_VERSION.startswith('3.1') and isinstance(enum_schema['type'], list):
enum_schema['type'] = [t for t in enum_schema['type'] if t != 'null'][0]

if len(components) == 1:
prop_schema.update(components[0].ref)
Expand Down
1 change: 1 addition & 0 deletions drf_spectacular/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,7 @@ def _get_serializer_field_meta(self, field, direction):
if field.write_only:
meta['writeOnly'] = True
if field.allow_null:
# this will be converted later in case of OAS 3.1
meta['nullable'] = True
if isinstance(field, serializers.CharField) and not field.allow_blank:
# blank check only applies to inbound requests
Expand Down
20 changes: 19 additions & 1 deletion drf_spectacular/plumbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def build_root_object(paths, components, version):
else:
version = settings.VERSION or version or ''
root = {
'openapi': '3.0.3',
'openapi': settings.OAS_VERSION,
'info': {
'title': settings.TITLE,
'version': version,
Expand Down Expand Up @@ -511,6 +511,24 @@ def safe_ref(schema):


def append_meta(schema, meta):
if spectacular_settings.OAS_VERSION.startswith('3.1'):
schema_nullable = meta.pop('nullable', None)
meta_nullable = schema.pop('nullable', None)

if schema_nullable or meta_nullable:
if 'type' in schema:
schema['type'] = [schema['type'], 'null']
elif '$ref' in schema:
schema = {'oneOf': [schema, {'type': 'null'}]}
else:
assert False, 'Invalid nullable case' # pragma: no cover

# these two aspects were merged in OpenAPI 3.1
if "exclusiveMinimum" in schema and "minimum" in schema:
schema["exclusiveMinimum"] = schema.pop("minimum")
if "exclusiveMaximum" in schema and "maximum" in schema:
schema["exclusiveMaximum"] = schema.pop("maximum")

return safe_ref({**schema, **meta})


Expand Down
5 changes: 5 additions & 0 deletions drf_spectacular/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
# accurately modeled when request and response components are separated.
'ENFORCE_NON_BLANK_FIELDS': False,

# This version string will end up the in schema header. The default OpenAPI
# version is 3.0.3, which is heavily tested. We now also support 3.1.0,
# which contains the same features and a few mandatory, but minor changes.
'OAS_VERSION': '3.0.3',

# Configuration for serving a schema subset with SpectacularAPIView
'SERVE_URLCONF': None,
# complete public schema or a subset based on the requesting user
Expand Down
17 changes: 13 additions & 4 deletions drf_spectacular/validation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,28 @@

import jsonschema

JSON_SCHEMA_SPEC_PATH = os.path.join(os.path.dirname(__file__), 'openapi3_schema.json')


def validate_schema(api_schema):
"""
Validate generated API schema against OpenAPI 3.0.X json schema specification.
Note: On conflict, the written specification always wins over the json schema.
OpenApi3 schema specification taken from:
https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v3.0/schema.json
https://github.com/OAI/OpenAPI-Specification/blob/6d17b631fff35186c495b9e7d340222e19d60a71/schemas/v3.0/schema.json
https://github.com/OAI/OpenAPI-Specification/blob/9dff244e5708fbe16e768738f4f17cf3fddf4066/schemas/v3.0/schema.json
https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.1/schema.json
https://github.com/OAI/OpenAPI-Specification/blob/9dff244e5708fbe16e768738f4f17cf3fddf4066/schemas/v3.1/schema.json
"""
with open(JSON_SCHEMA_SPEC_PATH) as fh:
if api_schema['openapi'].startswith("3.0"):
schema_spec_path = os.path.join(os.path.dirname(__file__), 'openapi_3_0_schema.json')
elif api_schema['openapi'].startswith("3.1"):
schema_spec_path = os.path.join(os.path.dirname(__file__), 'openapi_3_1_schema.json')
else:
raise RuntimeError('No validation specification available') # pragma: no cover

with open(schema_spec_path) as fh:
openapi3_schema_spec = json.load(fh)

# coerce any remnants of objects to basic types
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "https://spec.openapis.org/oas/3.0/schema/2019-04-02",
"id": "https://spec.openapis.org/oas/3.0/schema/2021-09-28",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Validation schema for OpenAPI Specification 3.0.X.",
"description": "The description of OpenAPI v3.0.x documents, as defined by https://spec.openapis.org/oas/v3.0.3",
"type": "object",
"required": [
"openapi",
Expand Down Expand Up @@ -1358,9 +1358,8 @@
"description": "Bearer",
"properties": {
"scheme": {
"enum": [
"bearer"
]
"type": "string",
"pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$"
}
}
},
Expand All @@ -1374,9 +1373,8 @@
"properties": {
"scheme": {
"not": {
"enum": [
"bearer"
]
"type": "string",
"pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$"
}
}
}
Expand Down Expand Up @@ -1489,7 +1487,8 @@
"PasswordOAuthFlow": {
"type": "object",
"required": [
"tokenUrl"
"tokenUrl",
"scopes"
],
"properties": {
"tokenUrl": {
Expand All @@ -1516,7 +1515,8 @@
"ClientCredentialsFlow": {
"type": "object",
"required": [
"tokenUrl"
"tokenUrl",
"scopes"
],
"properties": {
"tokenUrl": {
Expand Down Expand Up @@ -1544,7 +1544,8 @@
"type": "object",
"required": [
"authorizationUrl",
"tokenUrl"
"tokenUrl",
"scopes"
],
"properties": {
"authorizationUrl": {
Expand Down Expand Up @@ -1628,7 +1629,14 @@
"headers": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Header"
"oneOf": [
{
"$ref": "#/definitions/Header"
},
{
"$ref": "#/definitions/Reference"
}
]
}
},
"style": {
Expand All @@ -1648,6 +1656,10 @@
"default": false
}
},
"patternProperties": {
"^x-": {
}
},
"additionalProperties": false
}
}
Expand Down
Loading

0 comments on commit f31238e

Please sign in to comment.