Skip to content
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

Invalid nullable case using OpenAPI 3.1.0 support #1133

Closed
koksalkapucuoglu opened this issue Dec 22, 2023 · 16 comments
Closed

Invalid nullable case using OpenAPI 3.1.0 support #1133

koksalkapucuoglu opened this issue Dec 22, 2023 · 16 comments
Labels
bug Something isn't working

Comments

@koksalkapucuoglu
Copy link

koksalkapucuoglu commented Dec 22, 2023

Describe the bug
I change to drf-spectacular version from 0.26.5 to 0.27.0 for OpenAPI 3.1.0 support. I also changed OAS_VERSION in the settings. Afterwards, when I want to open swagger, I get the AssertionError: Invalid nullable case error.

web_1 | File "/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
web_1 | response = get_response(request)
web_1 | File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
web_1 | response = wrapped_callback(request, *callback_args, **callback_kwargs)
web_1 | File "/usr/local/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
web_1 | return view_func(*args, **kwargs)
web_1 | File "/usr/local/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
web_1 | return self.dispatch(request, *args, **kwargs)
web_1 | File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 509, in dispatch
web_1 | response = self.handle_exception(exc)
web_1 | File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 469, in handle_exception
web_1 | self.raise_uncaught_exception(exc)
web_1 | File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
web_1 | raise exc
web_1 | File "/usr/local/lib/python3.9/site-packages/rest_framework/views.py", line 506, in dispatch
web_1 | response = handler(request, *args, **kwargs)
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/views.py", line 84, in get
web_1 | return self._get_schema_response(request)
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/views.py", line 92, in _get_schema_response
web_1 | data=generator.get_schema(request=request, public=self.serve_public),
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/generators.py", line 281, in get_schema
web_1 | paths=self.parse(request, public),
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/generators.py", line 252, in parse
web_1 | operation = view.schema.get_operation(
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 111, in get_operation
web_1 | operation['responses'] = self._get_response_bodies()
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 1367, in _get_response_bodies
web_1 | return {'200': self._get_response_for_code(response_serializers, '200', direction=direction)}
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 1423, in _get_response_for_code
web_1 | component = self.resolve_serializer(serializer, direction)
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 1605, in resolve_serializer
web_1 | component.schema = self._map_serializer(serializer, direction, bypass_extensions)
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 927, in _map_serializer
web_1 | schema = self._map_basic_serializer(serializer, direction)
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 1026, in _map_basic_serializer
web_1 | schema = self._map_serializer_field(field, direction)
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 863, in _map_serializer_field
web_1 | return append_meta(build_basic_type(OpenApiTypes.ANY), meta)
web_1 | File "/usr/local/lib/python3.9/site-packages/drf_spectacular/plumbing.py", line 537, in append_meta
web_1 | assert False, 'Invalid nullable case' # pragma: no cover
web_1 | AssertionError: Invalid nullable case

@tfranzel tfranzel added the bug Something isn't working label Dec 22, 2023
@tfranzel
Copy link
Owner

hi,

yes OAS 3.1 is a new feature, and there are probably 1 or 2 more flaws we have to hammer out.

I fixed a missing case in d78a21c. However, I cannot be certain this addresses your specific assertion warning.

Can you test the master branch an see if your issue is gone?

@egorgam
Copy link

egorgam commented Dec 23, 2023

I see this too. In my case It happens because some JSONField in library has not label attrubute.

@EverardoCamacho
Copy link

OAS 3.1.0 + JSONField(null=True) on a model attribute will trigger AssertionError: Invalid nullable case

@koksalkapucuoglu
Copy link
Author

hi,

yes OAS 3.1 is a new feature, and there are probably 1 or 2 more flaws we have to hammer out.

I fixed a missing case in d78a21c. However, I cannot be certain this addresses your specific assertion warning.

Can you test the master branch an see if your issue is gone?

I tested, unfortunately I get the same error.

@koksalkapucuoglu koksalkapucuoglu closed this as not planned Won't fix, can't repro, duplicate, stale Dec 26, 2023
@koksalkapucuoglu
Copy link
Author

As written above, serializers.JSONField(allow_null=True) causes this error. The d78a21c solution in master branch does not solve this.

The reason I wanted to use OpenAPI 3.1.0 was that I needed the request body for delete. I fixed the JSONFields for testing purposes. Afterwards, I defined a request body for delete with different attempts, but I was not successful in any of them.

TEST_BULK_DELETE_PARAMS = {
    "bulk_delete": extend_schema(
        methods=["DELETE"],
        request={
            "type": "array",
            "items": {"type": "integer"}
        },
        responses={
            204: None
        }
    )
}

or

TEST_BULK_DELETE_PARAMS = {
    "bulk_delete": extend_schema(
        methods=["DELETE"],
        request={
            "application/json": TestSerializer
        },
        responses={
            204: None
        }
    )
}

or

TEST_BULK_DELETE_PARAMS = {
    "bulk_delete": extend_schema(
        methods=["DELETE"],
        request=TestSerializer,
        responses={
            204: None
        }
    )
}

tfranzel added a commit that referenced this issue Dec 26, 2023
@tfranzel
Copy link
Owner

This commit should fix you JSONField issue. added a proper test for it.

Regarding OAS 3.1 and DELETE, pleases read #809 as a primer. We have not implemented any changes there yet. At this point, 3.1 is just a slightly patched version of 3.0.x. I also have not checked whether schema validation for 3.1 passes with a delete body.

@koksalkapucuoglu
Copy link
Author

Thanks for the fix. If JSONField has attribute write_only=True, it still causes error but there is no problem for other cases.
I will look at configuration the request body for delete.

@tfranzel
Copy link
Owner

tfranzel commented Jan 6, 2024

@koksalkapucuoglu , please check again against current master. @Viicos fixed another case.

@anyidea
Copy link

anyidea commented Jan 16, 2024

same issue with serializers.ListSerializer(allow_null=True)

@tfranzel
Copy link
Owner

@anyidea can't reproduce your issue on current master. I think this was solved in one of the the prior fixes.

@tfranzel
Copy link
Owner

tfranzel commented Jan 18, 2024

just released 0.27.1

let me know if there is still cases missing.

@koksalkapucuoglu
Copy link
Author

@tfranzel first of all, I'm sorry for replying a little late.
I tried the following cases with 0.27.1 and didn't get any errors.

serializers.JSONField(allow_null=True, required=False, write_only=True)
serializers.ListSerializer(child=serializers.CharField(), write_only=True, allow_null=True)

But I noticed something:
The schema for serializers.JSONField(allow_null=True, required=False, write_only=True) looks like "write-only(any | null)"
and it is shown with the value "string" in the automatically generated example_value at the swagger ui.

@tfranzel
Copy link
Owner

The schema for serializers.JSONField(allow_null=True, required=False, write_only=True) looks like "write-only(any | null)"

I have no idea what that is supposed to represent. Please provide a OpenAPI schema snippet for discussion.

and it is shown with the value "string" in the automatically generated example_value at the swagger ui.

That is likely a Swagger UI thing independent of us or this issue in particular.

@koksalkapucuoglu
Copy link
Author

serializers.py
x = serializers.JSONField(allow_null=True, required=False, write_only=True)

schema.yml

x:
    oneOf:
    - {}
    - type: 'null'
    writeOnly: true

x seems like string at example_value at swagger ui, and it seems like "write-only(any | null)" at schema section at swagger ui.

@tfranzel
Copy link
Owner

x seems like string at example_value at swagger ui, and it seems like "write-only(any | null)" at schema section at swagger ui.

that is the correct schema. {} means it can be pretty much anything, including a string. SwaggerUI usually chooses a string as default example. Providing example values via decorator will override that choice.

@koksalkapucuoglu
Copy link
Author

ok, then we can close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants