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

With multipart/form-data and swagger-ui unable to uplaod a file from Swagger UI #151

Open
2 tasks done
giuseppe229 opened this issue Jun 3, 2024 · 2 comments
Open
2 tasks done

Comments

@giuseppe229
Copy link

giuseppe229 commented Jun 3, 2024

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.26.1

Plugin version

3.0.0

Node.js version

21.7.3

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

11

Description

I'm trying to upload multiple files in a post API call with multipart/form-data
I'm not able to do it neither in the swagger-ui or remotely (also with postman)
I'm getting the error:
Error: body/files must be string\n

If I change my route schema definition from
files: { type: 'array', items: { type: 'string', format: 'binary' }, },
to
files: { type: 'array', items: { format: 'binary' }, },

it works fine with postman but i'm not able to upload the file directly from the swagger UI

This is my app.js file:


const path = require('node:path')
const AutoLoad = require('@fastify/autoload')

// Pass --options via CLI arguments in command to enable these options.
const options = {}

module.exports = async function (fastify, opts) {

  fastify.register(require("@fastify/multipart"), { addToBody: true })

  // Register @fastify/swagger plugin
  await fastify.register(require('@fastify/swagger'), {
    openapi: {
      openapi: '3.0.0',
      info: {
        title: 'Local File Loader',
        description: 'Uploading one or more file on ***',
        version: '0.1.0'
      },
      servers: [
        {
          url: 'http://localhost:3000',
          description: 'Development server'
        }
      ],
      tags: [
        { name: 'load', description: 'Endpoint to uplaod files' }
      ],
      consumes: ['multipart/form-data'],
      externalDocs: {
        url: 'https://swagger.io',
        description: 'Find more info here'
      }
    }
  })

  // Register @fastify/swagger-ui plugin
  await fastify.register(require('@fastify/swagger-ui'), {
    routePrefix: '/documentation',
    uiConfig: {
      docExpansion: 'full',
      deepLinking: false
    },
    uiHooks: {
      onRequest: function (request, reply, next) { next() },
      preHandler: function (request, reply, next) { next() }
    },
    staticCSP: true,
    transformStaticCSP: (header) => header,
    transformSpecification: (swaggerObject, request, reply) => { return swaggerObject },
    transformSpecificationClone: true
  })

  // Do not touch the following lines

  // This loads all plugins defined in plugins
  // those should be support plugins that are reused
  // through your application
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'plugins'),
    options: Object.assign({}, opts)
  })

  // This loads all plugins defined in routes
  // define your routes in one of these
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'routes'),
    options: Object.assign({}, opts)
  })

  // Ensure the swagger documentation is generated before the server starts
  // await fastify.ready()
  // fastify.swagger()
}

module.exports.options = options

This is my route to upload files:


const { uplaodFile } = require('../../controllers/load');


module.exports = async function (fastify, opts) {
    // File upload route
    fastify.post('/', {
        schema: {
            summary: 'Upload multiple files',
            description: 'Endpoint to upload multiple files',
            consumes: ['multipart/form-data'],
            tags: ['load'],
            body: {
                type: 'object',
                properties: {
                    files: {
                        type: 'array',
                        items: { type: 'string', format: 'binary' },
                    },
                    filesPaths: {
                        type: 'array',
                        items: {
                            properties: {
                                fileName: { type: "string" },
                                filePath: { type: "string" },
                            }
                        }
                    }
                },
            },
            response: {
                200: {
                    description: 'Successful response',
                    type: 'object',
                    properties: {
                        status: { type: 'string' },
                    },
                },
            },
        },
        handler: uplaodFile
    });
}

Link to code that reproduces the bug

No response

Expected Behavior

No response

@alfonzeta
Copy link

alfonzeta commented Jul 25, 2024

same here with 3.0, in order for swagger ui to display the "choose file" button, some changes in schema need to be done and errors start to show. (I'm not used to post here in github so sorry for format mistakes).

If

consumes: ["multipart/form-data"],
    body: {
        type: "object"
}

error:

{
  "statusCode": 400,
  "code": "FST_ERR_VALIDATION",
  "error": "Bad Request",
  "message": "body must be object"
}

If

consumes: ["application/octet-stream"],
    body: {
        // type: "object
}

visual studio code thunder client extension uploads image.
curl request upload image, but swagger ui error:

{
  "statusCode": 415,
  "code": "FST_ERR_CTP_INVALID_MEDIA_TYPE",
  "error": "Unsupported Media Type",
  "message": "Unsupported Media Type: application/octet-stream"
}

And so many other tries left, following doc, overstack solutions... none of them seem to work with swagger ui upload. Some changes remove the upload file button, some other make it appear but return type error ("must be object", must be string") etc.
It has been some days trying to solve this but have not managed to do it. Maybe its an error on my side but im pretty sure im following doc.

@khokm
Copy link

khokm commented Sep 26, 2024

UPD: There is probably a better solution.

Old answer:
You can use validatorCompiler prop to modify how Fastify will validate the route.
The simplest way to disable validation for specific route is:

fastify.post(
  '/the/url',
  {
    schema: {
      body: {
        type: 'object',
        properties: {
          files: {
            type: 'array',
            items: { type: 'string', format: 'binary' },
          },
          filesPaths: {
            type: 'array',
            items: {
              properties: {
                fileName: { type: 'string' },
                filePath: { type: 'string' },
              },
            },
          },
        },
      },
      validatorCompiler: ({ schema, method, url, httpPart }) => {
        return () => true;
      },
    },
  },
  handler,
);

The problem here is that this will also disable validation of filesPaths property, and validation of other http parts (query params, response). You should not simple return true. The validatorCompiler should probably look something like this:

 validatorCompiler: ({ schema, method, url, httpPart }) => {
  const {body:_body} = schema;
  const body = {..._body, properties:{..._body.properties,files: undefined}}; //omitting  files property from validation
 return ajv.compile({...schema,body});
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants