openapi-typescript icon indicating copy to clipboard operation
openapi-typescript copied to clipboard

Content-Type of type multipart must include a boundary parameter

Open gpichot opened this issue 1 year ago • 2 comments

Description

Hello, First, thanks a lot for this great library, it's very useful in one of the projects I am working on. I spent some to find the reason of the bug below so I thought to put it here for reference. Feel free to close the issue. => PR #1826 was a breaking change on our side as we migrated from 0.8.x to 0.12.x

POST requests with FormData were failing with Content-Type of type multipart must include a boundary parameter.

Reason is:

We had some code that was setting headers.set('Content-Type', 'multipart/form-data') (no idea why 🤔). This header was removed in openapi-fetch prior to the PR. So the browser was setting the correct Content-Type.

After this PR, the header is now left in headers and therefore the browser does not set the correct Content-Type. Hence the issue. We fixed it by removing the line above.

Best! And again feel free to close this issue.

gpichot avatar Oct 22 '24 08:10 gpichot

Hello, How are you doing 😊?

I ran into the same issue where setting 'Content-Type': 'multipart/form-data' in request headers caused the FormData payload to be sent as an empty object { }. Initially, I set the request header like this in both the middleware and the service:

const { data, error } = await client.POST('/api/media/upload', {
  body: file as any,
  headers: {
    'content-type': 'multipart/form-data',
  },
});

But the payload was empty. When I tried using native fetch in Next.js, everything worked fine, and the FormData was correctly sent with the full payload. I thought maybe I shouldn’t set the Content-Type manually, so I modified the service like this:

export const createMediaServices = (client: Client) => ({
  upload: async (file: File) => {
    try {
      const { data, error } = await client.POST('/api/media/upload', {
        body: file as any,
        bodySerializer: (body) => {
          const formData = new FormData();
          formData.set('file', body!.file!);
          console.log(formData.get('file'));
          return formData;
        },
      });
      return error ? R.Error(mapError(error)) : R.Ok(mappers.upload(data));
    } catch {
      return R.Error(mapError(undefined));
    }
  },
});

In the middleware, I’ve tried this approach:

export const requestMiddleware: Middleware['onRequest'] = async ({
  request,
  schemaPath,
}) => {
  const isMedia = schemaPath === '/api/media/upload';
  if (isMedia) {
    return undefined;
  }
  
  const contentType = request.method.toUpperCase() === 'PATCH'
    ? 'application/merge-patch+json'
    : 'application/ld+json';
  
  request.headers.set('content-type', contentType);
  return request;
};

However, it seems like openapi-fetch overrides the Content-Type and defaults to application/json, preventing the FormData from being processed correctly.

BTW I'm using version 0.10 of openapi-fetch

@gpichot

xGooddevilx avatar Feb 13 '25 07:02 xGooddevilx

TY very much for reporting this.

However, I'd propose to close this as wontfix:

  • It is expected to have breaking changes from 0.8.x to 0.12.x
  • IMHO the new behavior implemented (in #1826) is a better API: if the caller explicitly requests a header, openapi-fetch will not override it anymore. Looking at the commit that introduced the mutable removal (https://github.com/salsita/openapi-typescript/commit/fd44bd28d881715e30f5a71435f05f6bae13859d#diff-97a780daf3f8f3dadada08409ebf769d5c22a115fb999160ce651ae0e93fb916), I'm pretty sure the intention was not to override explicit user provided headers, but just an oversight.

gzm0 avatar Feb 19 '25 10:02 gzm0