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

accurate-validation-error-response-with-drf-spectacular #5

Conversation

ghazi-git
Copy link
Owner

@ghazi-git ghazi-git commented Jun 6, 2022

The goal of this PR: describe validation error response accurately. That means API consumers should be able to know which error codes are returned for each field when checking the API schema.

Here's how validation errors look in swagger UI:
validation_error_example
validation_error_schema

Usage

If you're not overridding the postprocessing hook setting from drf-spectacular, set it to

"POSTPROCESSING_HOOKS": ["drf_standardized_errors.openapi_hooks.postprocess_schema_enums"]

But if you're already overriding it, make sure to replace the enums postprocessing hook from drf-spectacular with the one from this package. The hook will avoid raising warnings for dynamically created error code enums per field.


Currently, that is taken care of while accounting for nested and list serializers, composite serializer fields (like ListField and DictField) as well as validation errors raised by the django filter backend.

Custom error codes are also supported as long as you follow the DRF way of doing that. That means adding the error code as key in the serializer default_error_messages (or directly to self.error_messages) and then you can call self.fail to raise the validation error:

from rest_framework import serializers

class TestSerializer(serializers.Serializer):
    default_error_messages = {"custom_code": "You can't do this."}
    field1 = serializers.CharField(required=False)
    field2 = serializers.CharField(required=False)

    def validate(self, attrs):
        field1 = attrs.get("field1")
        field2 = attrs.get("field2")
        if not field1 and not field2:
            self.fail("custom_code")

        return attrs

However, using this with drf-spectacular can result in many warnings like these:

Warning #46: enum naming encountered a non-optimally resolvable collision for fields named "attr". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "AttrBb3Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.
Warning #71: enum naming encountered a non-optimally resolvable collision for fields named "code". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "CodeDaaEnum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.

After raising the above issue in drf-spectacular, the recommendation was to simply deal with this special use case, so I went ahead and created a postprocessing hook that excludes error component enums from being processed (the same hook with a minor change in which enums are collected). Package users will be encouraged to use this hook instead of the one provided by drf-spectacular.

One more item to think about: Ability to add custom error codes to specific fields. Currently, if the package user defines a custom error the "drf way" i.e. add it to Field.default_error_messages or Field.error_messages (same for forms with django-filters), the error code will show up automatically in the error response schema. Should another way be added to easily add error codes at the AutoSchema class level?

That's because the list of errors relative to each serializer is represented by a PolymorphicProxySerializer which allows specifying multiple serializers and each of them describes the error response for one field. A sample serializer would be like this:

from rest_framework import serializers

class Field1Serializer(serializers.Serializer):
    attr = serializers.ChoiceField(choices=[("field1", "field1")])
    code = serializers.ChoiceField(choices=[("required", "required"), ("invalid_choice", "invalid_choice")])
    detail = serializers.CharField()

The field-specific serializers are generated dynamically based on the operation. So, it is very likely that the "attr" or "code" choices are the same for different operations in the API. When that happens, the postprocessing hook from drf-spectacular sees that and raises the warnings above.

As for solutions, current options are:

  • disable the postprocessing hook from drf-spectacular: means losing its benefits
  • rewrite it to account for dynamically generated enums: probably involves too much copy-paste from the original postprocessing hook with minor changes.
  • create a new postprocessing hook that calls the drf-spectacular hook after wrapping it inside a GENERATOR_STATS.silence(): risks losing the good-to-hear-about warnings
  • reach out to drf-spectacular maintainer to see if they're open to adding a feature to drf-spectacular that fixes this issue: maybe disable generating enums for certain fields, ...

…d the possible error codes taking into account nested and list serializers, composite serializer fields and errors raised by django filter backend
@codecov
Copy link

codecov bot commented Jun 6, 2022

Codecov Report

Merging #5 (84d34cd) into error-responses-with-drf-spectacular (e452fa6) will decrease coverage by 20.84%.
The diff coverage is 0.00%.

@@                            Coverage Diff                            @@
##           error-responses-with-drf-spectacular       #5       +/-   ##
=========================================================================
- Coverage                                 44.77%   23.92%   -20.85%     
=========================================================================
  Files                                         8       10        +2     
  Lines                                       373      698      +325     
  Branches                                     82      183      +101     
=========================================================================
  Hits                                        167      167               
- Misses                                      206      531      +325     
Impacted Files Coverage Δ
drf_standardized_errors/openapi.py 0.00% <0.00%> (ø)
drf_standardized_errors/openapi_hooks.py 0.00% <0.00%> (ø)
drf_standardized_errors/openapi_utils.py 0.00% <0.00%> (ø)
drf_standardized_errors/settings.py 95.23% <ø> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e452fa6...84d34cd. Read the comment docs.

…g hook. This version excludes error components from postprocessing to avoid raising warnings about multiple enums having the same choices. Package users will be encouraged to use this version instead of the one provided by drf-spectacular.
…to a separate method to make it easy to add extra error codes per field if necessary (by overriding the method and manipulating the returned list of fields)
@ghazi-git ghazi-git marked this pull request as ready for review June 26, 2022 14:02
@ghazi-git ghazi-git merged commit 84d34cd into error-responses-with-drf-spectacular Jun 26, 2022
@ghazi-git ghazi-git deleted the accurate-validation-error-response-with-drf-spectacular branch June 26, 2022 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant