drf-standardized-errors icon indicating copy to clipboard operation
drf-standardized-errors copied to clipboard

Any hints on how to use the library together with djangorestframework-camel-case?

Open xSAVIKx opened this issue 1 year ago • 2 comments

Thx a lot for working on the library, it really simplifies things a lot and gives us a great boost in documenting everything 👍 🙏

I wanted to wonder if someone has any hints regarding how to make it work together with https://github.com/vbabiy/djangorestframework-camel-case?

The problem I see is that the auto-generated serializers are not camelized. Curious if there's something we can do to make it work together?

I'm using latest versions of all the libraries:

drf-spectacular = "0.27.0"
drf-standardized-errors = {extras = ["openapi"], version = "0.12.6"}
djangorestframework-camel-case = "1.4.2"

xSAVIKx avatar Jan 12 '24 15:01 xSAVIKx

If you're looking to customize the output of the exception handler then a custom exception formatter is what you're looking for.

Once you copy that example as is and cause a validation error, you will notice that "field_name" has become "fieldName". That shows that djangorestframework_camel_case runs as usual and highlights the assumption that it makes about the expected output of the API: field names must be keys in a dict. However, drf-standardized-errors intentionally moves away from having field names as keys to make it easier to document errors. So, the 2 do not play well together.

Still, you can always force it in an custom exception formatter class with a workaround like this

from drf_standardized_errors.formatter import ExceptionFormatter
from drf_standardized_errors.types import ErrorResponse, ErrorType
from djangorestframework_camel_case.util import camelize


class MyExceptionFormatter(ExceptionFormatter):
    def format_error_response(self, error_response: ErrorResponse):
        response = super().format_error_response(error_response)
        if error_response.type == ErrorType.VALIDATION_ERROR:
            errors = []
            for e in response["errors"]:
                # I have to pass a dict to camelize to get it to work
                # also, the package settings are not passed to camelize so you might want to do that
                # https://github.com/vbabiy/djangorestframework-camel-case/blob/7d4da4c800252c814d0eae21890f77a91ba04c3f/djangorestframework_camel_case/render.py#L19
                camel_case_value = camelize({e["attr"]: "dummyvalue"})
                error = {**e, "attr": list(camel_case_value)[0]}
                errors.append(error)
            response["errors"] = errors

        return response

For customizing the API schema generated, you might want to start by reading the code of the AutoSchema class provided by drf-standardized-errors to figure out where to make the necessary changes (that might be under AutoSchema._get_serializer_for_validation_error_response)

ghazi-git avatar Jan 12 '24 19:01 ghazi-git

@ghazi-git thanks for feedback here and :1st_place_medal: for this package :heart: :muscle:

Working & tested solution for customizing the auto generated API schema:

from drf_standardized_errors.openapi import AutoSchema as DrfAutoSchema
from drf_standardized_errors.openapi_utils import InputDataField


class AutoSchema(DrfAutoSchema):
    """Apply camel case for serializer field names in AutoSchema generated docs"""

    def _get_validation_error_codes_by_field(
        self, data_fields: list[InputDataField]
    ) -> dict[str, set[str]]:
        error_codes_by_field = super()._get_validation_error_codes_by_field(data_fields)
        return {
            to_camelcase(field): error_codes_by_field[field]
            for field in error_codes_by_field
        }
Output in swagger (drf-spectacular)
class InputSerializer(serializers.Serializer[Any]):
    old_password = serializers.CharField()
    password1 = serializers.CharField()
    password2 = serializers.CharField()

image

mikucz avatar Feb 06 '24 11:02 mikucz