-
Notifications
You must be signed in to change notification settings - Fork 270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Example for list response is either a list containing only the first element or a list inside a list in redoc #1310
Comments
Looking at the output, it seems as if swagger is trying to construct examples from the string format + regex. Still that example is wrong since the full list of names (all of |
Even if I correct the schema to be an array with I swagger-ui the example becomes (This is the changed code. It's somewhat convoluted since I didn't want to rewrite the whole schema and examples and I wouldn't keep it that way. Just to illustrate: INFO_ARRAY_SCHEMA = dict(
type='array',
prefixItems=[
dict(
type='object',
properties=OrderedDict({
'name': dict(
type='string',
enum=(f'sis.{n}',),
description='Name des Checks (beginnt immer mit `"sis."`)'
),
'value': STATUS_INFO_VALUE_SCHEMA,
'type': STATUS_INFO_TYPE_SCHEMA,
'description': STATUS_INFO_DESCRIPTION_SCHEMA,
'nagios': dict(
type='string',
pattern=(
f'({n})'
f' ({" | ".join(STATUS_INFO_TYPE_SCHEMA["enum"])})'
f' ({" | ".join(STATUS_INFO_VALUE_SCHEMA["enum"])})'
r' - .+'),
description='Vollständige Ausgabe des Nagios/Icinga-Checks'
)
}),
example=[e for e in INFO_EXAMPLE if e['name'] == f'sis.{n}'][0]
) for n in INFO_NAMES
]
)
class InfoViewSet(viewsets.ViewSet):
"""[Allgemeine Info über den SIS-Server](/doc/#tag/info)
Erlaubt zu Debugging-Zwecken eine Untermenge der
Statusinformationen ohne Autorisierung.
"""
resource = "info"
permission_classes = [permissions.IsAuthenticated]
@extend_schema(
responses={200: INFO_ARRAY_SCHEMA})
def list(self, request):
"""List SIS Information
[/api/info/](/api/info/)
Liefert einen Status von einigen Selbsttest aus.
"""
return Response([c.as_dict() for c in check.info()]) The result in {
"type": "array",
"prefixItems": [
{
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"sis.info"
],
"description": "Name des Checks (beginnt immer mit `\"sis.\"`)"
},
"value": {
"type": "string",
"enum": [
"ok",
"warning",
"error",
"critical"
],
"description": "Wert oder Schwere (\"Severity\") des Status."
},
"type": {
"type": "string",
"enum": [
"direct"
],
"description": "Nagios/Icinga Typ (immer `\"direct\"`)"
},
"description": {
"type": "string",
"description": "Menschenlesbare Beschreibung des Resultats"
},
"nagios": {
"type": "string",
"pattern": "(info) (direct) (ok | warning | error | critical) - .+",
"description": "Vollst\u00e4ndige Ausgabe des Nagios/Icinga-Checks"
}
},
"example": {
"name": "sis.info",
"value": "ok",
"type": "direct",
"description": "SIS version devel",
"nagios": "sis.info direct ok - SIS version devel"
}
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"sis.configuration"
],
"description": "Name des Checks (beginnt immer mit `\"sis.\"`)"
},
"value": {
"type": "string",
"enum": [
"ok",
"warning",
"error",
"critical"
],
"description": "Wert oder Schwere (\"Severity\") des Status."
},
"type": {
"type": "string",
"enum": [
"direct"
],
"description": "Nagios/Icinga Typ (immer `\"direct\"`)"
},
"description": {
"type": "string",
"description": "Menschenlesbare Beschreibung des Resultats"
},
"nagios": {
"type": "string",
"pattern": "(configuration) (direct) (ok | warning | error | critical) - .+",
"description": "Vollst\u00e4ndige Ausgabe des Nagios/Icinga-Checks"
}
},
"example": {
"name": "sis.configuration",
"value": "critical",
"type": "direct",
"description": "Config option 'errors-to' missing",
"nagios": "sis.configuration direct critical - Config option 'errors-to' missing"
}
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"sis.database"
],
"description": "Name des Checks (beginnt immer mit `\"sis.\"`)"
},
"value": {
"type": "string",
"enum": [
"ok",
"warning",
"error",
"critical"
],
"description": "Wert oder Schwere (\"Severity\") des Status."
},
"type": {
"type": "string",
"enum": [
"direct"
],
"description": "Nagios/Icinga Typ (immer `\"direct\"`)"
},
"description": {
"type": "string",
"description": "Menschenlesbare Beschreibung des Resultats"
},
"nagios": {
"type": "string",
"pattern": "(database) (direct) (ok | warning | error | critical) - .+",
"description": "Vollst\u00e4ndige Ausgabe des Nagios/Icinga-Checks"
}
},
"example": {
"name": "sis.database",
"value": "ok",
"type": "direct",
"description": "Database readable",
"nagios": "sis.database direct ok - Database readable"
}
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"sis.fingerprints"
],
"description": "Name des Checks (beginnt immer mit `\"sis.\"`)"
},
"value": {
"type": "string",
"enum": [
"ok",
"warning",
"error",
"critical"
],
"description": "Wert oder Schwere (\"Severity\") des Status."
},
"type": {
"type": "string",
"enum": [
"direct"
],
"description": "Nagios/Icinga Typ (immer `\"direct\"`)"
},
"description": {
"type": "string",
"description": "Menschenlesbare Beschreibung des Resultats"
},
"nagios": {
"type": "string",
"pattern": "(fingerprints) (direct) (ok | warning | error | critical) - .+",
"description": "Vollst\u00e4ndige Ausgabe des Nagios/Icinga-Checks"
}
},
"example": {
"name": "sis.fingerprints",
"value": "ok",
"type": "direct",
"description": "No duplicate fingerprints",
"nagios": "sis.fingerprints direct ok - No duplicate fingerprints"
}
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"sis.mail_host"
],
"description": "Name des Checks (beginnt immer mit `\"sis.\"`)"
},
"value": {
"type": "string",
"enum": [
"ok",
"warning",
"error",
"critical"
],
"description": "Wert oder Schwere (\"Severity\") des Status."
},
"type": {
"type": "string",
"enum": [
"direct"
],
"description": "Nagios/Icinga Typ (immer `\"direct\"`)"
},
"description": {
"type": "string",
"description": "Menschenlesbare Beschreibung des Resultats"
},
"nagios": {
"type": "string",
"pattern": "(mail_host) (direct) (ok | warning | error | critical) - .+",
"description": "Vollst\u00e4ndige Ausgabe des Nagios/Icinga-Checks"
}
},
"example": {
"name": "sis.mail_host",
"value": "ok",
"type": "direct",
"description": "SMTP server reachable",
"nagios": "sis.mail_host direct ok - SMTP server reachable"
}
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"sis.django_compatibility"
],
"description": "Name des Checks (beginnt immer mit `\"sis.\"`)"
},
"value": {
"type": "string",
"enum": [
"ok",
"warning",
"error",
"critical"
],
"description": "Wert oder Schwere (\"Severity\") des Status."
},
"type": {
"type": "string",
"enum": [
"direct"
],
"description": "Nagios/Icinga Typ (immer `\"direct\"`)"
},
"description": {
"type": "string",
"description": "Menschenlesbare Beschreibung des Resultats"
},
"nagios": {
"type": "string",
"pattern": "(django_compatibility) (direct) (ok | warning | error | critical) - .+",
"description": "Vollst\u00e4ndige Ausgabe des Nagios/Icinga-Checks"
}
},
"example": {
"name": "sis.django_compatibility",
"value": "ok",
"type": "direct",
"description": "SIS is compatible with the current version of Django",
"nagios": "sis.django_compatibility direct ok - SIS is compatible with the current version of Django"
}
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"sis.request_stats"
],
"description": "Name des Checks (beginnt immer mit `\"sis.\"`)"
},
"value": {
"type": "string",
"enum": [
"ok",
"warning",
"error",
"critical"
],
"description": "Wert oder Schwere (\"Severity\") des Status."
},
"type": {
"type": "string",
"enum": [
"direct"
],
"description": "Nagios/Icinga Typ (immer `\"direct\"`)"
},
"description": {
"type": "string",
"description": "Menschenlesbare Beschreibung des Resultats"
},
"nagios": {
"type": "string",
"pattern": "(request_stats) (direct) (ok | warning | error | critical) - .+",
"description": "Vollst\u00e4ndige Ausgabe des Nagios/Icinga-Checks"
}
},
"example": {
"name": "sis.request_stats",
"value": "ok",
"type": "direct",
"description": "No request durations over 100 seconds in the last 5 days",
"nagios": "sis.request_stats direct ok - No request durations over 100 seconds in the last 5 days"
}
}
]
} ) (The schema displays correctly in redoc, as well as in swagger, although without indentation and somewhat unhelpful in the latter and I will eventually go with that schema, but it doesn't help with the broken example.) |
I've managed successfully create a serializer with I've noticed that there are other usecases where I have a list serializer and only the first item in the example list is displayed and putting that list into a list causes a nested list. I guess my point is that there are usecases where I want more than one item in an example list response. The behaviour that the first item is used if the example list contains multiple object, but the full nested list is used if it's a nested list strikes me as inconsistent and is most certainly a bug. (Here's the code that results in the correct example (full list of items) with wrong schema (object instead of list): @extend_schema_field(INFO_NAME_SCHEMA)
class InfoNameField(CharField):
pass
@extend_schema_field(STATUS_INFO_VALUE_SCHEMA)
class StatusInfoValueField(CharField):
pass
@extend_schema_field(STATUS_INFO_TYPE_SCHEMA)
class StatusInfoTypeField(CharField):
pass
@extend_schema_field(STATUS_INFO_DESCRIPTION_SCHEMA)
class StatusInfoDescriptionField(CharField):
pass
@extend_schema_field(INFO_NAGIOS_SCHEMA)
class InfoNagiosField(CharField):
pass
@extend_schema_serializer(many=False,
examples=[
OpenApiExample(
'default',
value=INFO_EXAMPLE
)])
class InfoListSerializer(Serializer):
name = InfoNameField()
value = StatusInfoValueField()
type = StatusInfoTypeField()
description = StatusInfoDescriptionField()
nagios = InfoNagiosField()
class InfoViewSet(viewsets.ViewSet):
"""[Allgemeine Info über den SIS-Server](/doc/#tag/info)
Liefert einen Status von einigen Selbsttest aus.
[/api/info/](/api/info/)
Erlaubt zu Debugging-Zwecken eine Untermenge der
Statusinformationen ohne Autorisierung.
"""
resource = "info"
permission_classes = [permissions.IsAuthenticated]
@extend_schema(
summary='List SIS Information',
responses={200: InfoListSerializer})
def list(self, request):
return Response([c.as_dict() for c in check.info()]) ) |
This comment was marked as outdated.
This comment was marked as outdated.
Uhm sorry for the noise, but it appears that this is not redoc's fault after all. If I upload the following example to https://redocly.github.io/redoc/: openapi: 3.0.3
info:
title: Example REST API
paths:
/api/fixed_list/:
get:
operationId: fixed_list
responses:
200:
content:
application/json:
schema:
type: array
items:
type: object
examples:
- - name: info
value: ok
type: info
description: all ok
- name: database
value: critical
type: database
description: database not reachable i.e. I attach the example to the array schema instead of the object and give a list However if I attach my INFO_EXAMPLE list to the list view directly @extend_schema(
summary='List SIS Information',
responses={200: INFO_ITEM_SCHEMA},
examples=[OpenApiExample('default', value=INFO_EXAMPLE)])
def list(self, request):
return Response([c.as_dict() for c in check.info()]) I still get a nested list as example and I have to use the following define sis_postprocessing_hook(result, generator, request, public):
# [...] code that adds tag groups [...]
# Attach examples to the `array` not to the `item`!
# Workaround for https://github.com/tfranzel/drf-spectacular/issues/1310
paths = result['paths']
for path in ['/api/info/', '/api/status/']:
content_200 = result['paths'][path]['get']['responses']['200']['content']
for media_type in content_200.keys():
example_value = content_200[media_type]['examples']['Default']['value']
content_200[media_type]['examples']['Default']['value'] = example_value[0]
return result With this, examples are shown correctly in both redoc and swagger-ui as bundled in the current master of drf-spectacular-sidecar. This looks a little more like a bug in drf-spectacular to me now, unless |
Describe the bug
When I specify a list as an example (in a raw schema) in redoc only the first item in the given list is displayed as the only item in the example.
If I wrap the example list into a list, the example is displayed as a list of list.
In swagger I always get a single element list with garbled data.
To Reproduce
Note that this endpoint returns a list of (subset of) icinga (nagios) check items which are converted into the format expected by icinga checks. The data is provided by code that runs these checks and returns the appropriate status code and description.
So this is my application code (I elided some earlier definitions, but you get the idea, I think):
In this form, the example data will be given in redoc as a list of list.
In swagger, the example looks garbled and only has the first item:
If I change the raw schema to
The example in redoc changes to:
i.e. just the first item. And swagger displays:
Expected behavior
I expect to be able to give a full list as an example to a list response and see exactly that list as an example.
I've seen https://drf-spectacular.readthedocs.io/en/stable/faq.html#my-viewset-list-does-not-return-a-list-but-a-single-object as well as #990 and I've tried to come up with a way to apply the hints in https://drf-spectacular.readthedocs.io/en/stable/faq.html#how-do-i-wrap-my-responses-my-endpoints-are-wrapped-in-a-generic-envelope to an on-the-fly serializer via https://drf-spectacular.readthedocs.io/en/stable/drf_spectacular.html#drf_spectacular.utils.inline_serializer but it seems easier to write a serializer for the data returned by
check.info()
.That seems wasteful since it already does return an array and I feel it should not be that hard.
Also why does something (redoc?) apparently wrap the first element of a list into a list if it's not a list AND keep a nested list as is? That garbled swagger output is also very weird.
The text was updated successfully, but these errors were encountered: