Skip to content

Commit 37df58f

Browse files
committed
simplify error responses
Signed-off-by: Jens Langhammer <[email protected]>
1 parent cce12e6 commit 37df58f

File tree

7 files changed

+2270
-10351
lines changed

7 files changed

+2270
-10351
lines changed

authentik/api/pagination.py

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,10 @@
11
"""Pagination which includes total pages and current page"""
22

3+
from drf_spectacular.plumbing import build_object_type
34
from rest_framework import pagination
45
from rest_framework.response import Response
56

6-
PAGINATION_COMPONENT_NAME = "Pagination"
7-
PAGINATION_SCHEMA = {
8-
"type": "object",
9-
"properties": {
10-
"next": {
11-
"type": "number",
12-
},
13-
"previous": {
14-
"type": "number",
15-
},
16-
"count": {
17-
"type": "number",
18-
},
19-
"current": {
20-
"type": "number",
21-
},
22-
"total_pages": {
23-
"type": "number",
24-
},
25-
"start_index": {
26-
"type": "number",
27-
},
28-
"end_index": {
29-
"type": "number",
30-
},
31-
},
32-
"required": [
33-
"next",
34-
"previous",
35-
"count",
36-
"current",
37-
"total_pages",
38-
"start_index",
39-
"end_index",
40-
],
41-
}
7+
from authentik.api.v3.schema import PAGINATION
428

439

4410
class Pagination(pagination.PageNumberPagination):
@@ -70,14 +36,13 @@ def get_paginated_response(self, data):
7036
)
7137

7238
def get_paginated_response_schema(self, schema):
73-
return {
74-
"type": "object",
75-
"properties": {
76-
"pagination": {"$ref": f"#/components/schemas/{PAGINATION_COMPONENT_NAME}"},
39+
return build_object_type(
40+
properties={
41+
"pagination": PAGINATION.ref,
7742
"results": schema,
7843
},
79-
"required": ["pagination", "results"],
80-
}
44+
required=["pagination", "results"],
45+
)
8146

8247

8348
class SmallerPagination(Pagination):

authentik/api/schema.py

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,17 @@
33
from collections.abc import Callable
44
from typing import Any
55

6-
from django.utils.translation import gettext_lazy as _
76
from drf_spectacular.generators import SchemaGenerator
87
from drf_spectacular.plumbing import (
98
ResolvedComponent,
109
build_array_type,
11-
build_basic_type,
1210
build_object_type,
1311
)
1412
from drf_spectacular.renderers import OpenApiJsonRenderer
1513
from drf_spectacular.settings import spectacular_settings
16-
from drf_spectacular.types import OpenApiTypes
17-
from rest_framework.settings import api_settings
1814

1915
from authentik.api.apps import AuthentikAPIConfig
20-
from authentik.api.pagination import PAGINATION_COMPONENT_NAME, PAGINATION_SCHEMA
21-
22-
GENERIC_ERROR = build_object_type(
23-
description=_("Generic API Error"),
24-
properties={
25-
"detail": build_basic_type(OpenApiTypes.STR),
26-
"code": build_basic_type(OpenApiTypes.STR),
27-
},
28-
required=["detail"],
29-
)
30-
VALIDATION_ERROR = build_object_type(
31-
description=_("Validation Error"),
32-
properties={
33-
api_settings.NON_FIELD_ERRORS_KEY: build_array_type(build_basic_type(OpenApiTypes.STR)),
34-
"code": build_basic_type(OpenApiTypes.STR),
35-
},
36-
required=[],
37-
additionalProperties={},
38-
)
16+
from authentik.api.v3.schema import GENERIC_ERROR, PAGINATION, VALIDATION_ERROR
3917

4018

4119
def create_component(
@@ -71,35 +49,14 @@ def postprocess_schema_responses(
7149
<https://github.com/tfranzel/drf-spectacular/issues/101>.
7250
"""
7351

74-
create_component(generator, PAGINATION_COMPONENT_NAME, PAGINATION_SCHEMA)
75-
76-
generic_error = create_component(generator, "GenericError", GENERIC_ERROR)
77-
validation_error = create_component(generator, "ValidationError", VALIDATION_ERROR)
52+
generator.registry.register_on_missing(PAGINATION)
53+
generator.registry.register_on_missing(GENERIC_ERROR)
54+
generator.registry.register_on_missing(VALIDATION_ERROR)
7855

7956
for path in result["paths"].values():
8057
for method in path.values():
81-
method["responses"].setdefault(
82-
"400",
83-
{
84-
"content": {
85-
"application/json": {
86-
"schema": validation_error.ref,
87-
}
88-
},
89-
"description": "",
90-
},
91-
)
92-
method["responses"].setdefault(
93-
"403",
94-
{
95-
"content": {
96-
"application/json": {
97-
"schema": generic_error.ref,
98-
}
99-
},
100-
"description": "",
101-
},
102-
)
58+
method["responses"].setdefault("400", VALIDATION_ERROR.ref)
59+
method["responses"].setdefault("403", GENERIC_ERROR.ref)
10360

10461
result["components"] = generator.registry.build(spectacular_settings.APPEND_COMPONENTS)
10562

@@ -226,11 +183,9 @@ def postprocess_schema_simplify_paginated(
226183
]
227184
content_response["schema"] = build_object_type(
228185
properties={
229-
"pagination": {
230-
"$ref": f"#/components/schemas/{PAGINATION_COMPONENT_NAME}"
231-
},
186+
"pagination": PAGINATION.ref,
232187
"autocomplete": {"$ref": "#/components/schemas/Autocomplete"},
233-
"results": build_array_type(schema={"$ref": actual_component.ref}),
188+
"results": build_array_type(schema=actual_component.ref),
234189
},
235190
required=["pagination", "results", "autocomplete"],
236191
)

authentik/api/v3/schema.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from django.utils.translation import gettext_lazy as _
2+
from drf_spectacular.plumbing import (
3+
ResolvedComponent,
4+
build_array_type,
5+
build_basic_type,
6+
build_object_type,
7+
)
8+
from drf_spectacular.types import OpenApiTypes
9+
from rest_framework.settings import api_settings
10+
11+
GENERIC_ERROR = ResolvedComponent(
12+
name="GenericErrorResponse",
13+
type=ResolvedComponent.RESPONSE,
14+
object="GenericErrorResponse",
15+
schema={
16+
"content": {
17+
"application/json": {
18+
"schema": build_object_type(
19+
description=_("Generic API Error"),
20+
properties={
21+
"detail": build_basic_type(OpenApiTypes.STR),
22+
"code": build_basic_type(OpenApiTypes.STR),
23+
},
24+
required=["detail"],
25+
)
26+
}
27+
},
28+
"description": "",
29+
},
30+
)
31+
VALIDATION_ERROR = ResolvedComponent(
32+
name="ValidationErrorResponse",
33+
type=ResolvedComponent.RESPONSE,
34+
object="ValidationErrorResponse",
35+
schema={
36+
"content": {
37+
"application/json": {
38+
"schema": build_object_type(
39+
description=_("Validation Error"),
40+
properties={
41+
api_settings.NON_FIELD_ERRORS_KEY: build_array_type(
42+
build_basic_type(OpenApiTypes.STR)
43+
),
44+
"code": build_basic_type(OpenApiTypes.STR),
45+
},
46+
required=[],
47+
additionalProperties={},
48+
),
49+
}
50+
},
51+
"description": "",
52+
},
53+
)
54+
PAGINATION = ResolvedComponent(
55+
name="Pagination",
56+
type=ResolvedComponent.SCHEMA,
57+
object="Pagination",
58+
schema=build_object_type(
59+
properties={
60+
"next": build_basic_type(OpenApiTypes.NUMBER),
61+
"previous": build_basic_type(OpenApiTypes.NUMBER),
62+
"count": build_basic_type(OpenApiTypes.NUMBER),
63+
"current": build_basic_type(OpenApiTypes.NUMBER),
64+
"total_pages": build_basic_type(OpenApiTypes.NUMBER),
65+
"start_index": build_basic_type(OpenApiTypes.NUMBER),
66+
"end_index": build_basic_type(OpenApiTypes.NUMBER),
67+
},
68+
required=[
69+
"next",
70+
"previous",
71+
"count",
72+
"current",
73+
"total_pages",
74+
"start_index",
75+
"end_index",
76+
],
77+
),
78+
)

authentik/enterprise/search/pagination.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from rest_framework.response import Response
22

33
from authentik.api.pagination import Pagination
4-
from authentik.enterprise.search.ql import AUTOCOMPLETE_COMPONENT_NAME, QLSearch
4+
from authentik.enterprise.search.ql import AUTOCOMPLETE_SCHEMA, QLSearch
55

66

77
class AutocompletePagination(Pagination):
@@ -46,8 +46,6 @@ def get_paginated_response(self, data):
4646

4747
def get_paginated_response_schema(self, schema):
4848
final_schema = super().get_paginated_response_schema(schema)
49-
final_schema["properties"]["autocomplete"] = {
50-
"$ref": f"#/components/schemas/{AUTOCOMPLETE_COMPONENT_NAME}"
51-
}
49+
final_schema["properties"]["autocomplete"] = AUTOCOMPLETE_SCHEMA.ref
5250
final_schema["required"].append("autocomplete")
5351
return final_schema

authentik/enterprise/search/ql.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@
66
from djangoql.exceptions import DjangoQLError
77
from djangoql.queryset import apply_search
88
from djangoql.schema import DjangoQLSchema
9+
from drf_spectacular.plumbing import ResolvedComponent, build_object_type
910
from rest_framework.filters import SearchFilter
1011
from rest_framework.request import Request
1112
from structlog.stdlib import get_logger
1213

1314
from authentik.enterprise.search.fields import JSONSearchField
1415

1516
LOGGER = get_logger()
16-
AUTOCOMPLETE_COMPONENT_NAME = "Autocomplete"
17-
AUTOCOMPLETE_SCHEMA = {
18-
"type": "object",
19-
"additionalProperties": {},
20-
}
17+
AUTOCOMPLETE_SCHEMA = ResolvedComponent(
18+
name="Autocomplete",
19+
object="Autocomplete",
20+
type=ResolvedComponent.SCHEMA,
21+
schema=build_object_type(additionalProperties={}),
22+
)
2123

2224

2325
class BaseSchema(DjangoQLSchema):

authentik/enterprise/search/schema.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from djangoql.serializers import DjangoQLSchemaSerializer
22
from drf_spectacular.generators import SchemaGenerator
33

4-
from authentik.api.schema import create_component
54
from authentik.enterprise.search.fields import JSONSearchField
6-
from authentik.enterprise.search.ql import AUTOCOMPLETE_COMPONENT_NAME, AUTOCOMPLETE_SCHEMA
5+
from authentik.enterprise.search.ql import AUTOCOMPLETE_SCHEMA
76

87

98
class AKQLSchemaSerializer(DjangoQLSchemaSerializer):
@@ -24,6 +23,6 @@ def serialize_field(self, field):
2423

2524

2625
def postprocess_schema_search_autocomplete(result, generator: SchemaGenerator, **kwargs):
27-
create_component(generator, AUTOCOMPLETE_COMPONENT_NAME, AUTOCOMPLETE_SCHEMA)
26+
generator.registry.register_on_missing(AUTOCOMPLETE_SCHEMA)
2827

2928
return result

0 commit comments

Comments
 (0)