Drf-yasg: DRF Error Responses

Created on 29 Sep 2018  路  5Comments  路  Source: axnsan12/drf-yasg

I was wondering if there is planned support for adding the common errors that DRF returns for common errors as documented here:
http://www.django-rest-framework.org/api-guide/exceptions/#generic-error-views

The one I'm most in interested in is ValidationError.

Thank you for your wonderful work!

enhancement

Most helpful comment

I have adapted this slightly to match the exact output of DRF

from drf_yasg import openapi
from drf_yasg.inspectors.view import SwaggerAutoSchema
from drf_yasg.utils import force_real_str, is_list_view
from rest_framework import exceptions
from rest_framework.settings import api_settings
from rest_framework import status

class ErrorResponseAutoSchema(SwaggerAutoSchema):
  def get_generic_error_schema(self):
    return openapi.Schema(
      'Generic API Error',
      type=openapi.TYPE_OBJECT,
      properties={
        'errors': openapi.Schema(type=openapi.TYPE_OBJECT, properties={
          'detail': openapi.Schema(type=openapi.TYPE_STRING, description='Error details'),
          'code': openapi.Schema(type=openapi.TYPE_STRING, description='Error code'),
        })
      },
      required=['detail']
    )

  def get_validation_error_schema(self):
    return openapi.Schema(
      'Validation Error',
      type=openapi.TYPE_OBJECT,
      properties={
        'errors': openapi.Schema(
          type=openapi.TYPE_OBJECT,
          description='error messages for each field that triggered a validation error',
          additional_properties=openapi.Schema(
            description='A list of error messages for the field',
            type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)
        )),
        api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema(
          description='List of validation errors not related to any field',
          type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)
        ),
      }
    )

  def get_response_serializers(self):
    responses = super().get_response_serializers()
    definitions = self.components.with_scope(
      openapi.SCHEMA_DEFINITIONS)  # type: openapi.ReferenceResolver

    definitions.setdefault('GenericError', self.get_generic_error_schema)
    definitions.setdefault('ValidationError', self.get_validation_error_schema)
    definitions.setdefault('APIException', self.get_generic_error_schema)

    if self.get_request_serializer() or self.get_query_serializer():
      responses.setdefault(exceptions.ValidationError.status_code, openapi.Response(
        description=force_real_str(exceptions.ValidationError.default_detail),
        schema=openapi.SchemaRef(definitions, 'ValidationError')
      ))

    security = self.get_security()
    if security is None or len(security) > 0:
      # Note: 401 error codes are coerced  into 403 see rest_framework/views.py:433:handle_exception
      # This is b/c the API uses token auth which doesn't have WWW-Authenticate header
      responses.setdefault(status.HTTP_403_FORBIDDEN, openapi.Response(
        description="Authentication credentials were invalid, absent or insufficient.",
        schema=openapi.SchemaRef(definitions, 'GenericError')
      ))
    if not is_list_view(self.path, self.method, self.view):
      responses.setdefault(exceptions.PermissionDenied.status_code, openapi.Response(
        description="Permission denied.",
        schema=openapi.SchemaRef(definitions, 'APIException')
      ))
      responses.setdefault(exceptions.NotFound.status_code, openapi.Response(
        description="Object does not exist or caller has insufficient permissions to access it.",
        schema=openapi.SchemaRef(definitions, 'APIException')
      ))

    return responses

All 5 comments

This would be a good addition but I don't plan on doing much work on new features in the near future.

Pull requests are always welcome, of course.

You could implement this as follows:

class ErrorResponseAutoSchema(SwaggerAutoSchema):
    def get_generic_error_schema(self):
        return openapi.Schema(
            'Generic API Error',
            type=openapi.TYPE_OBJECT,
            properties={
                'detail': openapi.Schema(type=openapi.TYPE_STRING, description='Error details'),
                'code': openapi.Schema(type=openapi.TYPE_STRING, description='Error code'),
            },
            required=['detail']
        )

    def get_validation_error_schema(self):
        return openapi.Schema(
            'Validation Error',
            type=openapi.TYPE_OBJECT,
            properties={
                api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema(
                    description='List of validation errors not related to any field',
                    type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)
                ),
            },
            additional_properties=openapi.Schema(
                description='A list of error messages for each field that triggered a validation error',
                type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)
            )
        )

    def get_response_serializers(self):
        responses = super().get_response_serializers()
        definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)  # type: openapi.ReferenceResolver

        definitions.setdefault('ValidationError', self.get_validation_error_schema)
        definitions.setdefault('APIException', self.get_generic_error_schema)

        if self.get_request_serializer() or self.get_query_serializer():
            responses.setdefault(exceptions.ValidationError.status_code, openapi.Response(
                description=force_real_str(exceptions.ValidationError.default_detail),
                schema=openapi.SchemaRef(definitions, 'ValidationError')
            ))

        security = self.get_security()
        if security is None or len(security) > 0:
            responses.setdefault(exceptions.AuthenticationFailed.status_code, openapi.Response(
                description="Authentication credentials were invalid, absent or insufficient.",
                schema=openapi.SchemaRef(definitions, 'GenericError')
            ))
        if not is_list_view(self.path, self.method, self.view):
            responses.setdefault(exceptions.PermissionDenied.status_code, openapi.Response(
                description="Permission denied.",
                schema=openapi.SchemaRef(definitions, 'APIException')
            ))
            responses.setdefault(exceptions.NotFound.status_code, openapi.Response(
                description="Object does not exist or caller has insufficient permissions to access it.",
                schema=openapi.SchemaRef(definitions, 'APIException')
            ))

        return responses

For now I'd like to avoid including this directly in the code base.

I have adapted this slightly to match the exact output of DRF

from drf_yasg import openapi
from drf_yasg.inspectors.view import SwaggerAutoSchema
from drf_yasg.utils import force_real_str, is_list_view
from rest_framework import exceptions
from rest_framework.settings import api_settings
from rest_framework import status

class ErrorResponseAutoSchema(SwaggerAutoSchema):
  def get_generic_error_schema(self):
    return openapi.Schema(
      'Generic API Error',
      type=openapi.TYPE_OBJECT,
      properties={
        'errors': openapi.Schema(type=openapi.TYPE_OBJECT, properties={
          'detail': openapi.Schema(type=openapi.TYPE_STRING, description='Error details'),
          'code': openapi.Schema(type=openapi.TYPE_STRING, description='Error code'),
        })
      },
      required=['detail']
    )

  def get_validation_error_schema(self):
    return openapi.Schema(
      'Validation Error',
      type=openapi.TYPE_OBJECT,
      properties={
        'errors': openapi.Schema(
          type=openapi.TYPE_OBJECT,
          description='error messages for each field that triggered a validation error',
          additional_properties=openapi.Schema(
            description='A list of error messages for the field',
            type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)
        )),
        api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema(
          description='List of validation errors not related to any field',
          type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)
        ),
      }
    )

  def get_response_serializers(self):
    responses = super().get_response_serializers()
    definitions = self.components.with_scope(
      openapi.SCHEMA_DEFINITIONS)  # type: openapi.ReferenceResolver

    definitions.setdefault('GenericError', self.get_generic_error_schema)
    definitions.setdefault('ValidationError', self.get_validation_error_schema)
    definitions.setdefault('APIException', self.get_generic_error_schema)

    if self.get_request_serializer() or self.get_query_serializer():
      responses.setdefault(exceptions.ValidationError.status_code, openapi.Response(
        description=force_real_str(exceptions.ValidationError.default_detail),
        schema=openapi.SchemaRef(definitions, 'ValidationError')
      ))

    security = self.get_security()
    if security is None or len(security) > 0:
      # Note: 401 error codes are coerced  into 403 see rest_framework/views.py:433:handle_exception
      # This is b/c the API uses token auth which doesn't have WWW-Authenticate header
      responses.setdefault(status.HTTP_403_FORBIDDEN, openapi.Response(
        description="Authentication credentials were invalid, absent or insufficient.",
        schema=openapi.SchemaRef(definitions, 'GenericError')
      ))
    if not is_list_view(self.path, self.method, self.view):
      responses.setdefault(exceptions.PermissionDenied.status_code, openapi.Response(
        description="Permission denied.",
        schema=openapi.SchemaRef(definitions, 'APIException')
      ))
      responses.setdefault(exceptions.NotFound.status_code, openapi.Response(
        description="Object does not exist or caller has insufficient permissions to access it.",
        schema=openapi.SchemaRef(definitions, 'APIException')
      ))

    return responses

It would be awesome to document the error responses you can get!

Thanks for the code. It helped much more, than the documentation.

How such a schema could be deduced from an error serializer? Any helpers for that out of the box?

Was this page helpful?
0 / 5 - 0 ratings