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

Cannot use headers AND content in responses

Open ricardojosegomezulmke opened this issue 4 years ago • 49 comments

Currently generated code returns either 1 (one) response header or the body as defined in spec. But it cannot return BOTH, headers AND body.

Example from sample spec v3: GET /user/login responses 200: "responses": { "200": { "description": "successful operation", "headers": { "X-Rate-Limit": { "description": "calls per hour allowed by the user", "schema": { "type": "integer", "format": "int32" } }, "X-Expires-After": { "description": "date in UTC when toekn expires", "schema": { "type": "string", "format": "date-time" } } }, "content": { "application/xml": { "schema": { "type": "string" } }, "application/json": { "schema": { "type": "string" } } } },

In the example, the generated user login service will only return the content. There is no mechanism to get the headers X-Rate-Limit or X-Expires-After. The only way to achieve this today is to specify 3 services, e.g.: GET /user/login => returns the content GET /user/login/x-rate-limit: specify ONLY the header X-Rate-Limit GET /user/login/x-expires-after: specify ONLY the header X-Expires-After

and make 3 calls.

ricardojosegomezulmke avatar Sep 16 '21 07:09 ricardojosegomezulmke

@ricardojosegomezulmke That is correct, this is by design for this client generator.

ferdikoomen avatar Sep 27 '21 19:09 ferdikoomen

Why this design?

They are various case where it's useful to get body & headers. I'm trying to find any benefits to access only one or the other, but I'm confused.

In my case, I want to handle pagination with the link headers and this particular client design force me to make the same (costly) request twice!

Are you open to a PR? I'm willing to provide the following changes that update this behaviour to:

I'm asking because this will introduce 2 breaking changes (for users that consume headers with the current clients).

adriengibrat avatar Nov 18 '21 11:11 adriengibrat

In the meantime, I'll try a workaround without breaking changes: bring my own implementation with the CLI option --request Path to custom request file

adriengibrat avatar Nov 19 '21 10:11 adriengibrat

@ferdikoomen is there any hope for this to change in the future, as it seems to me that this is a must have. I have the same use case as @adriengibrat and I can't imagine to double all my calls to the api only to be able to retrieve the links header. Can you please reopen this issue ?

malko avatar Dec 15 '21 23:12 malko

at least giving a way to access the response object would be fine

malko avatar Dec 15 '21 23:12 malko

@malko I have reopened the ticket, to keep track of the status.

ferdikoomen avatar Dec 20 '21 18:12 ferdikoomen

@adriengibrat how does that workaround works?

We have the same problem. We have some Link headers for pagination and we want to access that from the generated client.

How did you pass your own request template?

nicolasparada avatar Mar 07 '22 21:03 nicolasparada

Looking for a way to access response headers in addition to the body, as well.

Seems like a good solution would be to pass an HTTPResponse or similar as a second argument to the generated methods, e.g. getSomething([args]).then((MyThing, HTTPResponse) => {}) where HTTPResponse is the raw http response (not sure if that's possible with different implementations, but given they all should use XHR or Fetch, feel like it should be something that could be supported). Also not sure of backwards compatibility.

Seems like using the request override wouldn't work well for people looking to get the responses along the response path, though it may work for my usecase (rate limit headers -> global state).

lrstanley avatar Aug 26 '22 02:08 lrstanley

I found a workaround that keeps the original API intact. After generating your API, add the following line to your core/request.ts file:

export const HEADERS = Symbol("HEADERS")

Then, in the request function, replace the following:

- resolve(result.body);
+ resolve({ ...result.body, [HEADERS]: response.headers });

You can then write a function that extracts the headers from your results object:

import { HEADERS } from "./openapi/core/request"

/**
 * Returns the headers of a given response object
 */
export const getHeaders = (response: any): Record<string, string> => {
  if (!(HEADERS in response)) {
    throw new Error("Response does not have headers");
  }

  return response[HEADERS];
}

Using a symbol means you can still stringify the object without seeing the headers field, and there is no chance it will collide with any other field on the response.

leolabs avatar Aug 26 '22 08:08 leolabs

I have a similar need -

We interact with a series of apis that allow pagination. They surface a response header with the total amount of pages along with any page of results. I need to grab that response header as well as the response body so I know how many pages to run through (they'll send back a 4xx if I run over the allowable pages).

The Symbol approach doesn't work so well for a list result.

lucasgray-alloy avatar Sep 23 '22 18:09 lucasgray-alloy

Same need here. API that we work with returns total items for pagination in headers. Unfortunately, we had to get rid of this code generation feature just because of no possibility to work with response and headers at the same time.

Must have feature, please reconsider existing behavior.

maks-rafalko avatar Sep 24 '22 11:09 maks-rafalko

any update on this, we are running in the same limitation as we have API that puts pagination in headers. thanks!

ysael avatar Nov 25 '22 13:11 ysael