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

MultipleChoiceField not exploding choices in Swagger UI #965

Closed
sparktx-adam-gleason opened this issue Mar 23, 2023 · 7 comments
Closed
Labels
enhancement New feature or request fix confirmation pending issue has been fixed and confirmation from issue reporter is pending

Comments

@sparktx-adam-gleason
Copy link

Describe the bug
drf-spectacular version 0.26.1

serializers.MultipleChoiceField in a request Serializer results in invalid choice 400 error when selecting more than one option using the Swagger UI view.

To Reproduce

# serializers.py
choices = [
    "option1",
    "option2",
    "option3",
]

class MyCreateSerializer(serializers.Serializer):
    """Create serializer for saving my choices""""
    my_choices = serializers.MultipleChoiceField(choices=choices)

# views.py
@extend_schema(
    methods=["post"],
    request=serializers.MyCreateSerializer,
)
def save_choices(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            my_choices = serializer.validated_data.get("my_choices", [])
            services.save_choices(choices=my_choices)
            return Response({"success": True }, status=status.HTTP_200_OK)

In the Swagger UI, I see the multiple select widget with my choice options. Selecting one choice works but selecting more than one produces this error

status code: 400 Bad Request

{
  "my_choices": [
    "\"option1,option2,option3\" is not a valid choice."
  ]
}

Here is a cURL example

curl -X 'POST' \
  '<MY ENDPOINT URL>' \
  -H 'accept: application/json' \
  -H 'Authorization: Basic REDACTED' \
  -H 'Content-Type: multipart/form-data' \
  -H 'X-CSRFTOKEN: REDACTED' \
  -F 'my_choices=option1,option2,option3'

Expected behavior
Selecting more than one option for MultipleChoiceField would explode the choices by default

curl -X 'POST' \
  '<MY ENDPOINT URL>' \
  -H 'accept: application/json' \
  -H 'Authorization: Basic REDACTED' \
  -H 'Content-Type: multipart/form-data' \
  -H 'X-CSRFTOKEN: REDACTED' \
  -F 'my_choices=option1' \
  -F 'my_choices=option2' \
  -F 'my_choices=option3'
@tfranzel
Copy link
Owner

Hi @sparktx-adam-gleason, on first glace this looks like a SwaggerUI issue to me. I don't even think we could do anything different. Afais OpenAPI only provides those explode/style switches on query parameters.

The way this is translated into a curl command is outside of our reach. The schema itself seems correct and I suppose you can only provoke this if the content type is form-data.

@sparktx-adam-gleason
Copy link
Author

sparktx-adam-gleason commented Mar 23, 2023

Hi @tfranzel

I was able to tweak the Swagger yaml file to produce the desired result.

/ENDPOINT/:
    post:
      operationId: operationId
      description: "description"
      requestBody:
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/MyCreateSerializer'
            encoding:
              'my_choices':
                style: form
                explode: true

I added this manually and it produced the desired result

encoding:
    'my_choices':
        style: form
        explode: true

Ref swagger-api/swagger-ui#5106

Minimal example of the desired behavior working

openapi: 3.0.3
info:
  title: API Documentation
  version: 0.1.0 (v1)
paths:
  /options/save-options:
    post:
      operationId: multiple_select_create
      requestBody:
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/CreateRequest'
            encoding:
              my_options:
                style: form
                explode: true
        required: true
      security:
      - basicAuth: []
      responses:
        '202':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
          description: ''
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: ''
 
components:
  schemas:
    CreateRequest:
      type: object
      properties:
        my_options:
          type: array
          items:
            $ref: '#/components/schemas/MyOptions'
      required:
      - my_options
      
    Success:
      type: object
      properties:
        status:
          type: boolean
      required:
      - status

    Error:
      type: object
      properties:
        errors:
          type: array
          items:
            type: string
      required:
      - errors
    
    MyOptions:
      enum:
      - option1
      - option2
      - option3
      type: string
      
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
servers:
- url: /api/v1/

Tested here https://editor.swagger.io/

Is there some way to add the encoding information to @extend_schema?

@extend_schema(
...other_fields,
request={
    "multipart/form-data": "serializer.MyCreateSerializer",
    "encoding": {"my_options": { "style":"form","explode":True }}
  }
)

@tfranzel
Copy link
Owner

Ahh right.... sry I totally forgot about this. This was actually raised a year ago in #714

I thought about it for a while and rejected the request in #714 (comment) because I was unable to find a elegant way of introducing this.

Is there some way to add the encoding information to @extend_schema?

That was exactly the problem before. Where to put it? It can't be a regular param as it applies only to specific content-types. Then it is detached from the actual component, which would be only a minor issue.

@tfranzel
Copy link
Owner

Your example has a point there. it has a syntax error but I think I get it. Something to that effect is probably the only reasonable solution.

@sparktx-adam-gleason
Copy link
Author

I agree a global param for @extend_schema seems like overkill since this generally related to requestBody.

There is functionality to define a custom dict for request param in @extend_schema. The issue I'm seeing is the dict value always maps to the schema key.
https://github.com/tfranzel/drf-spectacular/blob/master/drf_spectacular/plumbing.py#L311-L315

There would need to be functionality to add this extra content and map to content directly

@extend_schema(
...,
request={
   "multipart/form-data": {
        "schema": serializers.MyCreateSerializer
        # NOTE: Other custom keys can go after "schema" and would be included in the path `content`
        "encoding": { "field_name": { "explode": True }}  # User defined additional data
    }  
}
)

@tfranzel
Copy link
Owner

this breaks existing functionality. for {"multipart/form-data": XXX} XXX can either be a dict which means raw schema or the heap of other choices.

# A
@extend_schema(request={"multipart/form-data": (xxx, {"field_name": {...}})
# B
@extend_schema(request={"multipart/form-data": OpenApiRequest(xxx, encoding={"field_name": {...}})})

A would be a less problematic solution. So a tuple instance, which does not collide with existing functionality. However I'm leaning more towards B with OpenApiRequest() because we already have an equivalent OpenApiResponse() and it would be awkward to break symmetry.

@sparktx-adam-gleason
Copy link
Author

I like the idea of having OpenApiRequest functionality to be able to define the custom responseBody in a structured and consistent manner

tfranzel added a commit that referenced this issue Mar 26, 2023
Add OpenApiRequest for encoding options #714 #965
@tfranzel tfranzel added enhancement New feature or request fix confirmation pending issue has been fixed and confirmation from issue reporter is pending labels Mar 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request fix confirmation pending issue has been fixed and confirmation from issue reporter is pending
Projects
None yet
Development

No branches or pull requests

2 participants