tfranzel / drf-spectacular

Sane and flexible OpenAPI 3 schema generation for Django REST framework.
https://drf-spectacular.readthedocs.io
BSD 3-Clause "New" or "Revised" License
2.41k stars 266 forks source link

Polymorphic serializer - type field example instead of "string" #1322

Open avacaru opened 3 weeks ago

avacaru commented 3 weeks ago

Currently the rest_polymorphic extension generates OAS components looking like this:

components:
  schemas:
    MyComponentTyped:
      allOf:
      - type: object
        properties:
          type:
            type: string
        required:
        - type
      - $ref: '#/components/schemas/MyComponent'

When viewing this with Swagger UI or Redoc, the type field's example is just "string", which is quite confusing if you don't know what the actual type should be.

I've looked over the rest_polymorphic extension source code and I think that a potential change would be to update the resource_type_schema to something like:

resource_type_schema = build_object_type(
    properties={resource_type_field_name: {**build_basic_type(OpenApiTypes.STR), "example": resource_type}},
    required=None if patched else [resource_type_field_name]
)

Where resource_type is a new argument to the build_typed_component() method.

I'm not sure if this is the right approach, I've also tried fixing this using the @extend_schema_serializer decorator, but as mentioned in https://github.com/tfranzel/drf-spectacular/issues/649 it is not straight forward and I didn't get too far.

I'd appreciate any insight on how to go about this, thanks!

tfranzel commented 2 weeks ago

Hi, I think I get your point but you leave out an important detail. The Typed components are not used in isolation. They are always paired with a discriminator, which kind of indirectly specifies what is supposed to be used:

    NomadicPerson:
      type: object
      properties:
        id:
          type: integer
          readOnly: true
        address:
          type: string
          readOnly: true
          maxLength: 30
      required:
      - address
      - id
    NomadicPersonTyped:
      allOf:
      - type: object
        properties:
          resourcetype:
            type: string
        required:
        - resourcetype
      - $ref: '#/components/schemas/NomadicPerson'
    Person:
      oneOf:
      - $ref: '#/components/schemas/LegalPersonTyped'
      - $ref: '#/components/schemas/NaturalPersonTyped'
      - $ref: '#/components/schemas/NomadicPersonTyped'
      discriminator:
        propertyName: resourcetype
        mapping:
          legal: '#/components/schemas/LegalPersonTyped'
          natural: '#/components/schemas/NaturalPersonTyped'
          nomadic: '#/components/schemas/NomadicPersonTyped'

This is the recommended way of modelling the discriminator and works quite well with code generators: https://swagger.io/docs/specification/v3_0/data-models/inheritance-and-polymorphism/

Also, there is not really a good (and portable) way to describe a "fixed" values in OpenAPI 3.0. I think this was made easier in 3.1. This would be the basic requirement to ever consider changing anything.

Describing this as an example value might sneak it into the SwaggerUI but this does not seem to be too dependable.