openapistack / openapi-client-axios

JavaScript client library for consuming OpenAPI-enabled APIs with axios
https://openapistack.co
MIT License
556 stars 67 forks source link

How to send in: header within parameters? #155

Closed dumdiedum closed 1 year ago

dumdiedum commented 1 year ago

Hi there!

I have an OpenAPI with header as parameters like e.g.:

...
parameters: 
- name: My-Version
  in: header
  schema: 
    type: string
    enum: 
    - v1
    - v2
   # might also resemble a reference instead, e.g.:
   # $ref: "#/components/parameters/MyVersion"

I'm using path dictionary to call request so I'm trying to call this:

const response = await client.paths["/endpoint"].get({"My-Version": "v2"}, undefined, axiosConfigWithBearerTokenInHeader)

it seems like the header parameter will not be applied to request. Sadly I can't find informations in docs. I also looked up in test cases - also no. Do I miss something?

Can you please help me, how to send headers over parameters? Thanks in advance!

anttiviljami commented 1 year ago

Hi @dumdiedum ! We were indeed missing a test where a header is being passed so I added one here:

https://github.com/anttiviljami/openapi-client-axios/commit/dc930553d77d51e026154064694f8ff272a3da7c

As the tests indicate, passing a header as a parameter works fine. Path dictionary uses the same operation method reference, just accessed in a different way.

Could you post some more information about your setup?

You could also try setting up an interceptor to log the axios request config to check whether the header is present:

instance.interceptors.request.use(
  (config) => {
    console.log('Request Config:', config);
    return config;
  },
  (error) => {
    console.error('Request Error:', error);
    return Promise.reject(error);
  }
);
dumdiedum commented 1 year ago

Hi @anttiviljami! Thank you for your response!

I've debugged through and it seems like the My-Version header is getting lost in line 433 of client.ts: https://github.com/anttiviljami/openapi-client-axios/blob/master/packages/openapi-client-axios/src/client.ts#L433 maybe this is because my config is not empty and it overwrites the axiosConfig with the header of My-Version, since it contains a bearer token also as header parameter "Authorization".

I'm using openapi-client-axios in a very generic way (because I'm implementing an API wrapper with this client), so here a piece of pseudocode

import OpenAPIClientAxios, { OpenAPIClient, UnknownOperationMethod} from "openapi-client-axios";

// within async initializer method
const api = new OpenAPIClientAxios({definition: myDefinition, url: myBaseUrl);
const client = await api.getClient();

// within async requestor method
const choiceOperation = "/simple"
const choiceMethod = "get"
const clientPath = client.paths[choiceOperation];
// check for operation and method existance...
const methodCaller = (clientPath as any)[choiceMethod] as UnknownOperationMethod;
const response= methodCaller({"My-Version": "v2"}, undefined, { headers: {"Authorization": "Bearer ..."} });

whit this example definition:

openapi: 3.0.0
info:
  title: "My definition"
  version: "1.0.0"
paths:
  "/simple":
    get:
      summary: "simple get call"
      parameters:
      - in: header
        name: My-Version
        schema:
          type: string
          enum: ["v1", "v2"]
      responses:
        200:
          description: "success"
          content:
            application/json:
              schema:
                type: object

Hope this helps and thanks in advance!

dumdiedum commented 1 year ago

Hi @anttiviljami! I prepared a fix myself and wanted to create a pull request, but there are appearing errors while push. I'm new to lerna and contributing to public repositories and I'm just not able to figure out how to set up everything.

Edit: I could open a pull request! see #156

Here are my fixes, hopefully you can add them:

In client.ts I've added merging of headers if config is present. But clearly my solution is not very clever, it will only work for mergin headers. Maybe there are more fields which must be merged also. Anyway, with this change my issue was solved.

/**
   * Creates an axios config object for operation + arguments
   * @memberof OpenAPIClientAxios
   */
  public getAxiosConfigForOperation = (operation: Operation | string, args: OperationMethodArguments) => {
    if (typeof operation === 'string') {
      operation = this.getOperation(operation);
    }
    const request = this.getRequestConfigForOperation(operation, args);

    // construct axios request config
    const axiosConfig: AxiosRequestConfig = {
      method: request.method as Method,
      url: request.path,
      data: request.payload,
      params: request.query,
      headers: request.headers,
    };

    // allow overriding baseURL with operation / path specific servers
    const { servers } = operation;
    if (servers && servers[0]) {
      axiosConfig.baseURL = servers[0].url;
    }

    // allow overriding any parameters in AxiosRequestConfig
    const [, , config] = args;
    let mergedConfig;
    if (config) {
      mergedConfig = {
        ...axiosConfig,
        ...config,
        headers: {
          ...axiosConfig.headers,
          ...config.headers,
        },
      };
    }
    return mergedConfig || axiosConfig;
  };

In client.test.ts I've added a new test (copy of your lately added test for headers) and added a fake axios config containing an authorization header. But because of the problem while setting up environment I could not run tests and check if it works correctly...

test('getPetById({ petId: 1, "x-petshop-id": "test-shop" }) calls GET /pets/1 with request header and extern axios config', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

      const mock = new MockAdapter(api.client);
      const mockResponse = { id: 1, name: 'Garfield' };
      const mockHandler = jest.fn((config) => [200, mockResponse]);
      mock.onGet('/pets/1').reply((config) => mockHandler(config));

      const config = { headers: { authorization: 'Bearer abc' } };
      const res = await client.getPetById({ petId: 1, 'x-petshop-id': 'test-shop' }, undefined, config);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalledWith(
        expect.objectContaining({
          headers: expect.objectContaining({ 'x-petshop-id': 'test-shop', authorization: 'Bearer abc' }),
        }),
      );
    });