ferdikoomen / openapi-typescript-codegen

NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification
MIT License
2.95k stars 525 forks source link

Ability to send different headers for every request/api call #1599

Open JohnMusleh opened 1 year ago

JohnMusleh commented 1 year ago

Use case:

I have server A that exposes "api A" to the UI (I develop/control server A) I have server B that exposes "api B" (I do not develop/control server B) "api A" calls "api B" and must send the user id in the header when calling "api B"

I use this open api generator in server A to call api B

The problem:

I cannot send user id in the header when calling api B, I thought of changing the global OpenAPI config before every request, this won't work because I can get multiple requests at the same time , a global static object will not work here

Currently the only way to do this is to create a new client instance on every api call, this solution will work but might cause memory issues, especially because I have many client instances

Suggested solution:

if every generated function/api call had an optional header parameter, I would be able send specific header on every call and won't have to create a new client instance

SCasarotto commented 1 year ago

I have also run into this exact use case. I am using the generated code server side and I need to pass along a user based Authorization header with each request. I think each function accepting optional headers would be a good solution for this.

ansonallard commented 1 year ago

You can add headers to your openapi spec (in the parameters section of the remote endpoint), and in doing so, this library will allow you to pass headers to api B's endpoint.

Openapi's docs:

An example:

  /endpoint:
    get:
      operationId: myEndpoint
      tags:
        - Endpoint
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
            example: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
      responses:
        200:
          description: Okay
          content:
            application/json:
              schema:
                type: string

Generated code:

/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CancelablePromise } from "../core/CancelablePromise";
import type { BaseHttpRequest } from "../core/BaseHttpRequest";

export class EndpointService {
  constructor(public readonly httpRequest: BaseHttpRequest) {}

  /**
   * @param authorization
   * @returns string Okay
   * @throws ApiError
   */
  public myEndpoint(authorization?: string): CancelablePromise<string> {
    return this.httpRequest.request({
      method: "GET",
      url: "/endpoint",
      headers: {
        Authorization: authorization,
      },
    });
  }
}
JohnMusleh commented 1 year ago

@ansonallard that works but I do not have control over the remote endpoint, I do not maintain it or have access to it so I can't change anything

in my issue I wrote: "I have server B that exposes "api B" (I do not develop/control server B)"

optikalefx commented 1 year ago

I agree this is a problem. It ends up making very verbose API calls when you have to pass all the headers as parameters to function calls.

For example, this is Amazon Marketing Cloud generated from their OpenAPI Spec

 public static listWorkflows(
        amazonAdvertisingApiClientId: string,
        amazonAdvertisingApiAdvertiserId: string,
        amazonAdvertisingApiMarketplaceId: string,
        instanceId: string,
        nextToken?: string,
        limit?: string,
    ): CancelablePromise<ListWorkflowsResponse> {
        return __request(OpenAPI, {
            method: 'GET',
            url: '/amc/reporting/{instanceId}/workflows',
            path: {
                'instanceId': instanceId,
            },
            headers: {
                'Amazon-Advertising-API-ClientId': amazonAdvertisingApiClientId,
                'Amazon-Advertising-API-AdvertiserId': amazonAdvertisingApiAdvertiserId,
                'Amazon-Advertising-API-MarketplaceId': amazonAdvertisingApiMarketplaceId,
            },
            query: {
                'nextToken': nextToken,
                'limit': limit,
            },

Even though we know the heades are

 headers: {
                'Amazon-Advertising-API-ClientId': amazonAdvertisingApiClientId,
                'Amazon-Advertising-API-AdvertiserId': amazonAdvertisingApiAdvertiserId,
                'Amazon-Advertising-API-MarketplaceId': amazonAdvertisingApiMarketplaceId,
            },

we have to pass this with every single API call. We can't even use a constructor and make instances of these classes. We just don't need such verbose API calls, we should be able to instantiate a client instance with some basic headers.

optikalefx commented 1 year ago

I have made a PR that allows for headers defined at the client level to be the default values for params that openAPI says come from the header.

https://github.com/ferdikoomen/openapi-typescript-codegen/pull/1865

BnAmN commented 12 months ago

I have come across a similar use case: When using Prism as a mock server, it is necessary to specify a Prefer HTTP header to select/query a specific sample (Prefer: 'example=Foo'). This is only useful for development and testing, but I wouldn't include the header in the OpenAPI specification itself.

So for this case it would be extremely helpful to provide optional/additional HTTP headers.

I can think of even more useful cases for this, especially for development/testing purposes (e.g. overwriting default headers).

mrlubos commented 7 months ago

Hey all, you could try using interceptors https://github.com/hey-api/openapi-ts

Let me know if that doesn't work for you!