Skip to content

Nested file property in requestBody results in unexpected errors #324

@ajmanlove

Description

@ajmanlove

I'm experiencing some issues when using file properties nested within a requestBody. I'm new to this library, so feel free to point out if I'm just neglecting to consider something basic or obvious -- it's entirely possible this is my own openapi-fu lacking here.

Using:
Ruby: 3.4.1
Rails: 8.0.1
Rspec: 3.13, Rspec Rails: 7.1.1
openapi_first: 2.2.3

OpenAPI 3.0

Use Case 1: No Nesting

openapi.yaml

openapi: 3.0.3
# ...
paths:
    '/api/v1/signup':
      $ref: 'paths/signup.yaml'

paths/signup.yaml

patch:
  requestBody:
    content:
      multipart/form-data:
        schema:
          type: object
          properties:
            # ...
            avatar:
              type: string
              format: binary
                
  responses:
    # ...

Spec:

it 'should PATCH with avatar file' do
    body = {
      avatar: fixture_file_upload('image.jpg')
    }
    patch user_registration_path,
        params: body,
        headers: {
          'Content-Type': 'multipart/form-data'
        }
end

No middleware error, works fine.

Use Case 2: Nested file property

paths/signup.yaml

patch:
  requestBody:
    content:
      multipart/form-data:
        schema:
          type: object
          properties:
            user:
              type: object
              properties:
                # ...
                avatar:
                  type: string
                  format: binary
  responses:
    # ...

Spec:

it 'should PATCH with avatar file' do
    body = {
      user: {
        avatar: fixture_file_upload('image.jpg')
      }
    }
    patch user_registration_path,
      params: body,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
end

Results in error response:

{
    errors: [
        {
            code: "string",
            source: {
                pointer: "/user/avatar"
            },
            status: "400",
            title: "value at `/user/avatar` is not a string"
        },
        {
            code: "format",
            source: {
                pointer: "/user/avatar"
            },
            status: "400",
            title: "value at `/user/avatar` does not match format: binary"
        }
    ]
}

Use Case 3: Array of Files

paths/signup.yaml

patch:
  requestBody:
    content:
      multipart/form-data:
        schema:
          type: object
          properties:
            # ...
            filesArray:
                type: array
                items:
                  type: string
                  format: binary
                
  responses:
    # ...

Spec:

it 'should PATCH with avatar file' do
    body = {
      filesArray: [
        fixture_file_upload('image.jpg')
      ]
    }
    patch user_registration_path,
      params: body,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
end

Results in error:

{
    errors: [
        {
            code: "string",
            source: {
                pointer: "/filesArray/0"
            },
            status: "400",
            title: "value at `/filesArray/0` is not a string"
        },
        {
            code: "format",
            source: {
                pointer: "/filesArray/0"
            },
            status: "400",
            title: "value at `/filesArray/0` does not match format: binary"
        }
    ]
}

Use Case Four, Array of Objects with with file property

paths/signup.yaml

patch:
  requestBody:
    content:
      multipart/form-data:
        schema:
          type: object
          properties:
            # ...
            filesObjArray:
              type: array
              items:
                type: object
                properties:
                  file:
                    type: string
                    format: binary
                
  responses:
    # ...

Spec:

it 'should PATCH with avatar file' do
    body = {
      filesObjArray: [
        { avatar: fixture_file_upload('image.jpg') }
      ]
    }
    patch user_registration_path,
      params: body,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
end

Results in error response:

{
    errors: [
        {
            code: "string",
            source: {
                pointer: "/filesObjArray/0/file"
            },
            status: "400",
            title: "value at `/filesObjArray/0/file` is not a string"
        },
        {
            code: "format",
            source: {
                pointer: "/filesObjArray/0/file"
            },
            status: "400",
            title: "value at `/filesObjArray/0/file` does not match format: binary"
        }
    ]
}

OpenAPI 3.1

If I keep using format: binary, I'll get mostly the same errors, minus the does not match format: binary error, which makes some sense.

If I replace the use of format: binary with something like contentMediaType: application/octet-stream or image/jpeg, I get a different thrown error:

JSONSchemer::UnknownContentMediaType:
  application/octet-stream

I can also use the empty {} for typing to avoid the errors, but this seems incorrect since it's tacitly an unknown/any type.

Considerations

In all of the above, if I remove the OpenapiFirst::Middlewares::RequestValidation middleware and the underlying controller code supports the body structure, actual functionality works fine in spec, postman, javascript client, etc

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions