hey-api / openapi-ts

✨ Turn your OpenAPI specification into a beautiful TypeScript client
https://heyapi.vercel.app
MIT License
635 stars 44 forks source link

Default content-type of application/json applied to requests even when no body #679

Open frdwhite24 opened 1 week ago

frdwhite24 commented 1 week ago

Description

Thanks for all the hard work on this library, it's an incredible feat and has saved me so much time in my personal projects. I make sure to recommend it to everyone who has a similar problem. Also loving the type safety and flexibility of the latest lib version! I've not felt restricted when used it at all.

I've recently upgraded to the latest version of your lib (0.46.0), and have switched from using axios as the client to @hey-api/client-fetch (0.1.3) because I needed to add custom headers in an interceptor. Previously all requests worked fine, but after carrying out the appropriate refactoring for the new library version and config format, the requests without a body are failing with a FastifyError from my backend of:

Body cannot be empty when content-type is set to 'application/json'

Inspecting the requests shows there is indeed a header with content-type: "application/json" being applied. If I write the following code in a request interceptor, then the requests (namely POST and DELETE) go through just fine as before. It seems there is a default content-type of application/json being applied?

client.interceptors.request.use(async (request) => {
  const token = await getToken()
  request.headers.set('Authorization', `Bearer ${token}`)

  if (some condition here) {
    request.headers.delete('content-type')

  return request
})

I'm not sure of the best way to handle this, but feel free to ask any more questions if you need to so we can figure it out together.

OpenAPI specification (optional)

{
  "openapi":"3.0.3",
  "info":{
    "title":"MyApp",
    "description":"REST API for MyApp",
    "version":"1.0.0"
  },
  "components":{

  },
  "paths":{
    "/api/flights/{flightId}":{
      "delete":{
        "operationId":"deleteOneFlight",
        "tags":[
          "Flight"
        ],
        "parameters":[
          {
            "schema":{
              "type":"string"
            },
            "in":"path",
            "name":"flightId",
            "required":true
          }
        ],
        "responses":{
          "204":{
            "description":"Default Response",
            "content":{
              "application/json":{
                "schema":{
                  "type":"object",
                  "properties":{

                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Configuration

// openapi-ts.config.ts
import { defineConfig } from '@hey-api/openapi-ts'

export default defineConfig({
  client: '@hey-api/client-fetch',
  input: 'openapi.json',
  output: {
    format: 'prettier',
    path: 'src/api/generated'
  },
  types: {
    enums: 'javascript',
  },
})

System information (optional)

frdwhite24 commented 1 week ago

The same thing seems to happen with a POST request that doesn't have any body as well, have updated the original issue text/title and solution

frdwhite24 commented 1 week ago

For anyone else experiencing this, you can use the temporary workaround below. I think this is definitely something that should be addressed with the new @hey-api/client-fetch lib. I've had to go through all my endpoints and manually pick out the ones that: are not GET, and don't send a body, and remove their content-type header.

client.interceptors.request.use(async (request) => {
  const method = request.method
  if (method === 'DELETE' || method === 'POST') { // or any other methods where you're sending something without a body (GET isn't required)
    ENDPOINTS_TO_REMOVE[method].forEach((endpoint) => {
      if (endpoint.regex.test(request.url)) {
        request.headers.delete('content-type')
      }
    })
  }
  return request
})

where:

export const ENDPOINTS_TO_REMOVE = {
  DELETE: [
    {
      url: '/api/my-endpoint',
      regex: /\/api\/my-endpoint/, // using regex just in case you have url parameters
    }
]