main 9ec1b7060333 cached
24 files
107.9 KB
26.9k tokens
31 symbols
1 requests
Download .txt
Repository: openapistack/openapi-client-axios
Branch: main
Commit: 9ec1b7060333
Files: 24
Total size: 107.9 KB

Directory structure:
gitextract_dvpnw2jp/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── ci.yml
│       └── codeql.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DOCS.md
├── LICENSE
├── README.md
├── jest.config.ts
├── package.json
├── src/
│   ├── __tests__/
│   │   ├── fixtures.ts
│   │   └── resources/
│   │       ├── example-pet-api.openapi.json
│   │       └── example-pet-api.openapi.yml
│   ├── client.test.ts
│   ├── client.ts
│   ├── index.ts
│   ├── query-serializer.ts
│   └── types/
│       └── client.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .github/FUNDING.yml
================================================
github: anttiviljami
open_collective: openapi-stack
custom:
  - https://buymeacoff.ee/anttiviljami


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
  push:
    branches: ["main"]
    tags: ["*"]
  pull_request:
    branches: ["main"]

permissions:
  id-token: write # Required for OIDC
  contents: read

jobs:
  test:
    name: Test
    strategy:
      matrix:
        axios_version:
          - 0.25.0 # oldest supported
          - 0.*.* # latest 0.x
          - ^1.0.0 # latest 1.x
          - latest
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v6
        with:
          node-version: "20"
      - run: npm ci
      - run: npm install axios@${{ matrix.axios_version }} && npm why axios
      - run: npm run lint
      - run: npm test

  publish:
    name: Publish
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    needs:
      - test
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v6
        with:
          node-version: "24"
          registry-url: https://registry.npmjs.org/
      - run: npm ci
      - run: npm publish


================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]
  schedule:
    - cron: "26 21 * * 5"

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [javascript]

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: ${{ matrix.language }}
          queries: +security-and-quality

      - name: Autobuild
        uses: github/codeql-action/autobuild@v2

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2
        with:
          category: "/language:${{ matrix.language }}"


================================================
FILE: .gitignore
================================================
# build
*.js
*.js.map
*.d.ts

# include types
!src/types/*.d.ts

# include jest config
!jest.config.js

# npm
node_modules
npm_debug.log*


================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint
npm test

================================================
FILE: .prettierignore
================================================
*.d.ts
*.js


================================================
FILE: .prettierrc
================================================
{
  "parser": "typescript",
  "arrowParens": "always",
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 120
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
viljami@viljami.io.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior,  harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

OpenAPI Client Axios is Free and Open Source Software. Issues and pull requests are more than welcome!


================================================
FILE: DOCS.md
================================================
# Documentation

OpenAPI Client Axios documentation has moved to [openapistack.co](https://openapistack.co)

See: https://openapistack.co/docs/openapi-client-axios


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2021 Viljami Kuosmanen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
<span id="npm--fix-hidden-readme-header"></span>

<h1 align="center"><img alt="openapi-client-axios" src="https://raw.githubusercontent.com/openapistack/openapi-client-axios/main/header.png" style="max-width:50rem"></h1>

[![CI](https://github.com/openapistack/openapi-client-axios/workflows/CI/badge.svg)](https://github.com/openapistack/openapi-client-axios/actions?query=workflow%3ACI)
[![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/openapistack/openapi-client-axios/blob/main/LICENSE)
[![npm version](https://img.shields.io/npm/v/openapi-client-axios.svg)](https://www.npmjs.com/package/openapi-client-axios)
[![npm downloads](https://img.shields.io/npm/dw/openapi-client-axios.svg)](https://www.npmjs.com/package/openapi-client-axios)
[![bundle size](https://img.shields.io/bundlephobia/minzip/openapi-client-axios.svg?label=gzip%20bundle)](https://bundlephobia.com/package/openapi-client-axios)
[![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/openapi-client-axios.svg)](https://www.npmjs.com/package/openapi-client-axios?activeTab=dependencies)
![npm type definitions](https://img.shields.io/npm/types/openapi-client-axios.svg)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/openapistack/openapi-client-axios)
[![Buy me a coffee](https://img.shields.io/badge/donate-buy%20me%20a%20coffee-orange)](https://buymeacoff.ee/anttiviljami)

<p align="center">JavaScript client library for consuming OpenAPI-enabled APIs with <a href="https://github.com/axios/axios" target="_blank">axios</a>. Types included.</p>

## Features

- [x] Create API clients from [OpenAPI v3 definitions](https://github.com/OAI/OpenAPI-Specification)
- [x] Client is configured in runtime. **No generated code!**
- [x] Generate TypeScript definitions (.d.ts) for your APIs with full IntelliSense support
- [x] Easy to use API to call API operations using JavaScript methods
  - `client.getPet(1)`
  - `client.searchPets()`
  - `client.searchPets({ ids: [1, 2, 3] })`
  - `client.updatePet(1, payload)`
- [x] Built on top of the robust [axios](https://github.com/axios/axios) JavaScript library
- [x] Isomorphic, works both in browser and Node.js

## Documentation

**New!** OpenAPI Client Axios documentation is now found on [openapistack.co](https://openapistack.co)

https://openapistack.co/docs/openapi-client-axios/intro

## Quick Start

```
npm install --save axios openapi-client-axios
```

```
yarn add axios openapi-client-axios
```

With promises / CommonJS syntax:

```javascript
const OpenAPIClientAxios = require('openapi-client-axios').default;

const api = new OpenAPIClientAxios({ definition: 'https://example.com/api/openapi.json' });
api.init()
  .then(client => client.getPetById(1))
  .then(res => console.log('Here is pet id:1 from the api', res.data));
```

With async-await / ES6 syntax:

```javascript
import OpenAPIClientAxios from 'openapi-client-axios';

const api = new OpenAPIClientAxios({ definition: 'https://example.com/api/openapi.json' });
api.init();

async function createPet() {
  const client = await api.getClient();
  const res = await client.createPet(null, { name: 'Garfield' });
  console.log('Pet created', res.data);
}
```

## Client

OpenAPI Client Axios uses [operationIds](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object)
in OpenAPIv3 definitions to call API operations.

After initializing `OpenAPIClientAxios`, an axios client instance extended with OpenAPI capabilities is exposed.

Example:
```javascript
const api = new OpenAPIClientAxios({ definition: 'https://example.com/api/openapi.json' });
api.init().then((client) => {
  client.updatePet(1, { age: 12 });
});
```

`client` is an [axios instance](https://github.com/axios/axios#creating-an-instance) initialized with
baseURL from OpenAPI definitions and extended with extra operation methods for calling API operations.

It also has a reference to OpenAPIClientAxios at `client.api`

## Operation methods

OpenAPIClientAxios operation methods take 3 arguments:

```javascript
client.operationId(parameters?, data?, config?)
```

### Parameters

The first argument is used to pass parameters available for the operation.

```javascript
// GET /pets/{petId}
client.getPet({ petId: 1 })
```

For syntactic sugar purposes, you can also specify a single implicit parameter value, in which case OpenAPIClientAxios
will look for the first required parameter for the operation. Usually this is will be a path parameter.

```javascript
// GET /pets/{petId} - getPet
client.getPet(1)
```

Alternatively, you can explicitly specify parameters in array form. This method allows you to set custom parameters not defined
in the OpenAPI spec.

```javascript
// GET /pets?search=Garfield - searchPets
client.searchPets([{ name: 'search', value: 'Garfield', in: 'query' }])
```

The type of the parameters can be any of:
- query
- header
- path
- cookie

### Data

The second argument is used to pass the requestPayload

```javascript
// PUT /pets/1 - updatePet
client.updatePet(1, { name: 'Odie' })
```

More complex payloads, such as Node.js streams or FormData supported by Axios can be used.

The first argument can be set to null if there are no parameters required for the operation.

```javascript
// POST /pets - createPet
client.updatePet(null, { name: 'Garfield' })
```

### Config object

The last argument is the config object.

The config object is an [`AxiosRequestConfig`](https://github.com/axios/axios#request-config) object. You can use it to
override axios request config parameters, such as `headers`, `timeout`, `withCredentials` and many more.

```javascript
// POST /user - createUser
client.createUser(null, { user: 'admin', pass: '123' }, { headers: { 'x-api-key': 'secret' } });
```

## Paths Dictionary

OpenAPI Client Axios also allows calling API operations via their path and HTTP
method, using the paths dictionary.

Example:

```javascript
client.paths['/pets'].get(); // GET /pets, same as calling client.getPets()
client.paths['/pets'].post(); // POST /pets
client.paths['/pets/{petId}'].put(1); // PUT /pets/1
client.paths['/pets/{petId}/owner/{ownerId}'].get({ petId: 1, ownerId: 2 }) ; // GET /pets/1/owner/2
```

This allows calling operation methods without using their operationIds, which
may be sometimes preferred.

## Typesafe Clients

![TypeScript IntelliSense](https://openapistack.co/assets/images/intellisense-b61ace10fd35746dd5bfefa977c0645e.gif)

`openapi-client-axios` comes with a CLI command `openapicmd typegen` to generate Typescript types for type safety and code autocomplete.

```
npx openapicmd typegen ./openapi.yaml > src/types/openapi.d.ts
```

The output of `typegen` exports a type called `Client`, which can be used for instances created with `OpenAPIClientAxios`.

Both the `api.getClient()` and `api.init()` methods support passing in a Client type.

```typescript
import { Client as PetStoreClient } from './client.d.ts';

const client = await api.init<PetStoreClient>();
const client = await api.getClient<PetStoreClient>();
```

`openapicmd typegen` supports using both local and remote URLs for OpenAPI definition files.

```
$ npx openapicmd typegen ./petstore.yaml
$ npx openapicmd typegen https://petstore3.swagger.io/api/v3/openapi.json
```

## Commercial support

For assistance with openapi-client-axios in your company, reach out at support@openapistack.co.

## Contributing

OpenAPI Client Axios is Free and Open Source Software. Issues and pull requests are more than welcome!


================================================
FILE: jest.config.ts
================================================
import type { JestConfigWithTsJest } from 'ts-jest'

const jestConfig: JestConfigWithTsJest = {
  testEnvironment: 'node',
  testMatch: ['**/?(*.)+(spec|test).ts?(x)'],
  transform: {
    '^.+\\.[tj]s?$': [
      'ts-jest',
      {
        useESM: true,
      },
    ],
  },
}

export default jestConfig

================================================
FILE: package.json
================================================
{
  "name": "openapi-client-axios",
  "description": "JavaScript client library for consuming OpenAPI-enabled APIs with axios. Types included.",
  "version": "7.9.0",
  "license": "MIT",
  "keywords": [
    "openapi",
    "swagger",
    "client",
    "axios",
    "frontend",
    "browser",
    "mock",
    "typescript"
  ],
  "author": "Viljami Kuosmanen <viljami@viljami.io>",
  "funding": "https://github.com/sponsors/anttiviljami",
  "homepage": "https://openapistack.co",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/openapistack/openapi-client-axios.git"
  },
  "bugs": {
    "url": "https://github.com/openapistack/openapi-client-axios/issues"
  },
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "*.js",
    "*.d.ts",
    "*.map",
    "**/*.js",
    "**/*.d.ts",
    "**/*.map",
    "!*.test.*",
    "!**/*.test.*",
    "!__tests__/**",
    "!src/**",
    "!*.config.js"
  ],
  "peerDependencies": {
    "axios": ">=0.25.0",
    "js-yaml": "^4.1.0"
  },
  "dependencies": {
    "bath-es5": "^3.0.3",
    "dereference-json-schema": "^0.2.1",
    "openapi-types": "^12.1.3"
  },
  "devDependencies": {
    "@types/jest": "^29.5.5",
    "@types/js-yaml": "^4.0.5",
    "@types/json-schema": "^7.0.6",
    "axios": "^1.13.5",
    "axios-mock-adapter": "^1.22.0",
    "jest": "^29.7.0",
    "json-schema": "^0.4.0",
    "json-schema-deref-sync": "^0.14.0",
    "msw": "^1.3.2",
    "prettier": "^3.0.3",
    "source-map-support": "^0.5.10",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "typescript": "^4.5.5"
  },
  "scripts": {
    "build": "tsc",
    "watch-build": "tsc -w",
    "prepare": "npm run build",
    "test": "NODE_ENV=test jest",
    "lint": "prettier --check src/**/*.ts __tests__/**/*.ts",
    "lint:fix": "prettier --write src/**/*.ts __tests__/**/*.ts"
  },
  "gitHead": "6ea364b653a2264ddd2de4d2f1ddabc5cf3cbfd5"
}


================================================
FILE: src/__tests__/fixtures.ts
================================================
import { OpenAPIV3 } from 'openapi-types';

export const baseURL = 'http://localhost:8080';
export const baseURLAlternative = 'http://localhost:9090/';
export const baseURLWithVariable = 'http://{foo1}.localhost:9090/{foo2}/{foo3}/';
export const baseURLWithVariableResolved = 'http://bar1.localhost:9090/bar2a/bar3b/';
export const baseURLV2 = 'http://localhost:8080/v2';

export const responses: OpenAPIV3.ResponsesObject = {
  200: { description: 'ok' },
};

export const petId: OpenAPIV3.ParameterObject = {
  name: 'petId',
  in: 'path',
  required: true,
  schema: {
    type: 'integer',
  },
};

export const petShopId: OpenAPIV3.ParameterObject = {
  name: 'x-petshop-id',
  in: 'header',
  schema: {
    type: 'string',
  },
};

export const ownerId: OpenAPIV3.ParameterObject = {
  name: 'ownerId',
  in: 'path',
  required: true,
  schema: {
    type: 'integer',
  },
};

export const createDefinition = (overrides: Partial<OpenAPIV3.Document> = {}): OpenAPIV3.Document => ({
  openapi: '3.0.0',
  info: {
    title: 'api',
    version: '1.0.0',
  },
  servers: [],
  paths: {},
  ...overrides,
});

export const definition: OpenAPIV3.Document = {
  openapi: '3.0.0',
  info: {
    title: 'api',
    version: '1.0.0',
  },
  servers: [
    { url: baseURL },
    {
      url: baseURLAlternative,
      description: 'Alternative server',
    },
    {
      url: baseURLWithVariable,
      description: 'server with variable baseURL',
      variables: {
        foo1: {
          default: 'bar1',
          enum: ['bar1', 'bar1a'],
        },
        foo2: {
          default: 'bar2b',
          enum: ['bar2a', 'bar2b'],
        },
        foo3: {
          default: 'bar3a',
          enum: ['bar3a', 'bar3b'],
        },
      },
    },
  ],
  paths: {
    '/pets': {
      get: {
        operationId: 'getPets',
        responses: {
          200: {
            $ref: '#/components/responses/PetsListRes',
          },
        },
        parameters: [
          {
            name: 'q',
            in: 'query',
            schema: {
              type: 'array',
              items: {
                type: 'string',
              },
            },
          },
        ],
      },
      post: {
        operationId: 'createPet',
        responses: {
          201: {
            $ref: '#/components/responses/PetRes',
          },
        },
      },
    },
    '/pets/{petId}': {
      get: {
        operationId: 'getPetById',
        responses: {
          200: {
            $ref: '#/components/responses/PetRes',
          },
        },
        parameters: [petShopId],
      },
      put: {
        operationId: 'replacePetById',
        responses: {
          200: {
            $ref: '#/components/responses/PetRes',
          },
        },
      },
      patch: {
        operationId: 'updatePetById',
        responses: {
          200: {
            $ref: '#/components/responses/PetRes',
          },
        },
      },
      delete: {
        operationId: 'deletePetById',
        responses: {
          200: {
            $ref: '#/components/responses/PetRes',
          },
        },
      },
      parameters: [petId],
    },
    '/pets/{petId}/owner': {
      get: {
        operationId: 'getOwnerByPetId',
        responses,
      },
      parameters: [petId],
    },
    '/pets/{petId}/owner/{ownerId}': {
      get: {
        operationId: 'getPetOwner',
        responses,
      },
      parameters: [petId, ownerId],
    },
    '/pets/meta': {
      get: {
        operationId: 'getPetsMeta',
        responses,
      },
    },
    '/pets/relative': {
      servers: [{ url: baseURLV2 }],
      get: {
        operationId: 'getPetsRelative',
        responses,
      },
    },
  },
  components: {
    schemas: {
      PetWithName: {
        type: 'object',
        properties: {
          id: {
            type: 'integer',
            minimum: 1,
          },
          name: {
            type: 'string',
            example: 'Garfield',
          },
        },
      },
    },
    responses: {
      PetRes: {
        description: 'ok',
        content: {
          'application/json': {
            schema: {
              $ref: '#/components/schemas/PetWithName',
            },
          },
        },
      },
      PetsListRes: {
        description: 'ok',
        content: {
          'application/json': {
            schema: {
              type: 'array',
              items: {
                $ref: '#/components/schemas/PetWithName',
              },
            },
          },
        },
      },
    },
  },
};


================================================
FILE: src/__tests__/resources/example-pet-api.openapi.json
================================================
{
  "openapi": "3.0.0",
  "info": {
    "title": "Example API",
    "description": "Example CRUD API for pets",
    "version": "1.0.0"
  },
  "tags": [
    {
      "name": "pets",
      "description": "Pet operations"
    }
  ],
  "servers": [
    {
      "url": "http://localhost:8080"
    }
  ],
  "paths": {
    "/pets": {
      "get": {
        "operationId": "getPets",
        "summary": "List pets",
        "description": "Returns all pets in database",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "List of pets in database"
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Number of items to return",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/QueryLimit"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Starting offset for returning items",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/QueryOffset"
            }
          }
        ]
      },
      "post": {
        "operationId": "createPet",
        "summary": "Create a pet",
        "description": "Crete a new pet into the database",
        "tags": [
          "pets"
        ],
        "responses": {
          "201": {
            "description": "Pet created succesfully"
          }
        },
        "parameters": [],
        "requestBody": {
          "$ref": "#/components/requestBodies/PetPayload"
        }
      }
    },
    "/pets/{id}": {
      "get": {
        "operationId": "getPetById",
        "summary": "Get a pet",
        "description": "Returns a pet by its id in database",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "Pet object corresponding to id"
          },
          "404": {
            "description": "Pet not found"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unique identifier for pet in database",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PetId"
            }
          },
          {
            "name": "x-petshop-id",
            "in": "header",
            "description": "Optional header parameter to pass petshop id",
            "schema": {
              "type": "string",
              "example": "123"
            }
          }
        ]
      },
      "put": {
        "operationId": "replacePetById",
        "summary": "Replace pet",
        "description": "Replace an existing pet in the database",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "Pet replaced succesfully"
          },
          "404": {
            "description": "Pet not found"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unique identifier for pet in database",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PetId"
            }
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/PetPayload"
        }
      },
      "patch": {
        "operationId": "updatePetById",
        "summary": "Update pet",
        "description": "Update an existing pet in the database",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "Pet updated succesfully"
          },
          "404": {
            "description": "Pet not found"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unique identifier for pet in database",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PetId"
            }
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/PetPayload"
        }
      },
      "delete": {
        "operationId": "deletePetById",
        "summary": "Delete a pet",
        "description": "Deletes a pet by its id in database",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "Pet deleted succesfully"
          },
          "404": {
            "description": "Pet not found"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unique identifier for pet in database",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PetId"
            }
          }
        ]
      }
    },
    "/pets/{id}/owner": {
      "get": {
        "operationId": "getOwnerByPetId",
        "summary": "Get a pet's owner",
        "description": "Get the owner for a pet",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "Human corresponding pet id"
          },
          "404": {
            "description": "Human or pet not found"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Unique identifier for pet in database",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PetId"
            }
          }
        ]
      }
    },
    "/pets/{petId}/owner/{ownerId}": {
      "get": {
        "operationId": "getPetOwner",
        "summary": "Get owner by id",
        "description": "Get the owner for a pet",
        "tags": [
          "pets"
        ],
        "parameters": [
          {
            "name": "petId",
            "in": "path",
            "description": "Unique identifier for pet in database",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PetId"
            }
          },
          {
            "name": "ownerId",
            "in": "path",
            "description": "Unique identifier for owner in database",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PetId"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Human corresponding owner id"
          },
          "404": {
            "description": "Human or pet not found"
          }
        }
      }
    },
    "/pets/meta": {
      "get": {
        "operationId": "getPetsMeta",
        "summary": "Get pet metadata",
        "description": "Returns a list of metadata about pets and their relations in the database",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "Metadata for pets"
          }
        }
      }
    },
    "/pets/relative": {
      "servers": [
        {
          "url": "baseURLV2"
        }
      ],
      "get": {
        "operationId": "getPetsRelative",
        "summary": "Get pet metadata",
        "description": "Returns a list of metadata about pets and their relations in the database",
        "tags": [
          "pets"
        ],
        "responses": {
          "200": {
            "description": "Metadata for pets"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PetId": {
        "description": "Unique identifier for pet in database",
        "example": 1,
        "title": "PetId",
        "type": "integer"
      },
      "PetPayload": {
        "type": "object",
        "properties": {
          "name": {
            "description": "Name of the pet",
            "example": "Garfield",
            "title": "PetName",
            "type": "string"
          }
        },
        "additionalProperties": false,
        "required": [
          "name"
        ]
      },
      "QueryLimit": {
        "description": "Number of items to return",
        "example": 25,
        "title": "QueryLimit",
        "type": "integer"
      },
      "QueryOffset": {
        "description": "Starting offset for returning items",
        "example": 0,
        "title": "QueryOffset",
        "type": "integer",
        "minimum": 0
      }
    },
    "requestBodies": {
      "PetPayload": {
        "description": "Request payload containing a pet object",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PetPayload"
            }
          }
        }
      }
    }
  }
}


================================================
FILE: src/__tests__/resources/example-pet-api.openapi.yml
================================================
openapi: 3.0.0
info:
  title: Example API
  description: Example CRUD API for pets
  version: 1.0.0
tags:
  - name: pets
    description: Pet operations
servers:
  - url: http://localhost:8080
paths:
  /pets:
    get:
      operationId: getPets
      summary: List pets
      description: Returns all pets in database
      tags:
        - pets
      responses:
        '200':
          description: List of pets in database
      parameters:
        - name: limit
          in: query
          description: Number of items to return
          required: false
          schema:
            $ref: '#/components/schemas/QueryLimit'
        - name: offset
          in: query
          description: Starting offset for returning items
          required: false
          schema:
            $ref: '#/components/schemas/QueryOffset'
    post:
      operationId: createPet
      summary: Create a pet
      description: Crete a new pet into the database
      tags:
        - pets
      responses:
        '201':
          description: Pet created succesfully
      parameters: []
      requestBody:
        $ref: '#/components/requestBodies/PetPayload'
  '/pets/{id}':
    get:
      operationId: getPetById
      summary: Get a pet
      description: Returns a pet by its id in database
      tags:
        - pets
      responses:
        '200':
          description: Pet object corresponding to id
        '404':
          description: Pet not found
      parameters:
        - name: id
          in: path
          description: Unique identifier for pet in database
          required: true
          schema:
            $ref: '#/components/schemas/PetId'
        - name: x-petshop-id
          in: header
          description: Optional header parameter to pass petshop id
          schema:
            type: string
            example: "123"
    put:
      operationId: replacePetById
      summary: Replace pet
      description: Replace an existing pet in the database
      tags:
        - pets
      responses:
        '200':
          description: Pet replaced succesfully
        '404':
          description: Pet not found
      parameters:
        - name: id
          in: path
          description: Unique identifier for pet in database
          required: true
          schema:
            $ref: '#/components/schemas/PetId'
      requestBody:
        $ref: '#/components/requestBodies/PetPayload'
    patch:
      operationId: updatePetById
      summary: Update pet
      description: Update an existing pet in the database
      tags:
        - pets
      responses:
        '200':
          description: Pet updated succesfully
        '404':
          description: Pet not found
      parameters:
        - name: id
          in: path
          description: Unique identifier for pet in database
          required: true
          schema:
            $ref: '#/components/schemas/PetId'
      requestBody:
        $ref: '#/components/requestBodies/PetPayload'
    delete:
      operationId: deletePetById
      summary: Delete a pet
      description: Deletes a pet by its id in database
      tags:
        - pets
      responses:
        '200':
          description: Pet deleted succesfully
        '404':
          description: Pet not found
      parameters:
        - name: id
          in: path
          description: Unique identifier for pet in database
          required: true
          schema:
            $ref: '#/components/schemas/PetId'
  '/pets/{id}/owner':
    get:
      operationId: getOwnerByPetId
      summary: Get a pet's owner
      description: Get the owner for a pet
      tags:
        - pets
      responses:
        '200':
          description: Human corresponding pet id
        '404':
          description: Human or pet not found
      parameters:
        - name: id
          in: path
          description: Unique identifier for pet in database
          required: true
          schema:
            $ref: '#/components/schemas/PetId'
  '/pets/{petId}/owner/{ownerId}':
    get:
      operationId: getPetOwner
      summary: Get owner by id
      description: Get the owner for a pet
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          description: Unique identifier for pet in database
          required: true
          schema:
            $ref: '#/components/schemas/PetId'
        - name: ownerId
          in: path
          description: Unique identifier for owner in database
          required: true
          schema:
            $ref: '#/components/schemas/PetId'
      responses:
        '200':
          description: Human corresponding owner id
        '404':
          description: Human or pet not found
  /pets/meta:
    get:
      operationId: getPetsMeta
      summary: Get pet metadata
      description: Returns a list of metadata about pets and their relations in the database
      tags:
        - pets
      responses:
        '200':
          description: Metadata for pets
  /pets/relative:
    servers: [{ url: baseURLV2 }]
    get:
      operationId: 'getPetsRelative'
      summary: Get pet metadata
      description: Returns a list of metadata about pets and their relations in the database
      tags:
        - pets
      responses:
        '200':
          description: Metadata for pets
components:
  schemas:
    PetId:
      description: Unique identifier for pet in database
      example: 1
      title: PetId
      type: integer
    PetPayload:
      type: object
      properties:
        name:
          description: Name of the pet
          example: Garfield
          title: PetName
          type: string
      additionalProperties: false
      required:
        - name
    QueryLimit:
      description: Number of items to return
      example: 25
      title: QueryLimit
      type: integer
    QueryOffset:
      description: Starting offset for returning items
      example: 0
      title: QueryOffset
      type: integer
      minimum: 0
  requestBodies:
    PetPayload:
      description: 'Request payload containing a pet object'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PetPayload'


================================================
FILE: src/client.test.ts
================================================
import path from 'path';
import fs from 'fs';
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import MockAdapter from 'axios-mock-adapter';
import { definition, baseURL, baseURLV2, baseURLAlternative, baseURLWithVariableResolved, createDefinition } from './__tests__/fixtures';
import { OpenAPIClientAxios, OpenAPIClient } from './client';
import axios, { AxiosResponse } from 'axios';

const testsDir = path.join(__dirname, '.', '__tests__');

const examplePetAPIJSON = path.join(testsDir, 'resources', 'example-pet-api.openapi.json');
const examplePetAPIYAML = path.join(testsDir, 'resources', 'example-pet-api.openapi.yml');

const server = setupServer(
  rest.get('http://localhost/example-pet-api.openapi.json', (_req, res, ctx) => {  
    return res(ctx.body(fs.readFileSync(examplePetAPIJSON)), ctx.set('Content-Type', 'application/json'));
  }),
  rest.get('http://localhost/example-pet-api.openapi.yml', (_req, res, ctx) => {  
    return res(ctx.body(fs.readFileSync(examplePetAPIYAML)), ctx.set('Content-Type', 'application/yaml'));
  })
);

beforeAll(() => server.listen());

describe('OpenAPIClientAxios', () => {
  const checkHasOperationMethods = (client: OpenAPIClient) => {
    expect(client).toHaveProperty('getPets');
    expect(client).toHaveProperty('createPet');
    expect(client).toHaveProperty('getPetById');
    expect(client).toHaveProperty('replacePetById');
    expect(client).toHaveProperty('updatePetById');
    expect(client).toHaveProperty('deletePetById');
    expect(client).toHaveProperty('getOwnerByPetId');
    expect(client).toHaveProperty('getPetOwner');
    expect(client).toHaveProperty('getPetsMeta');
    expect(client).toHaveProperty('getPetsRelative');
  };

  describe('init', () => {
    test('can be initalised with a valid OpenAPI document as JS Object', async () => {
      const api = new OpenAPIClientAxios({ definition });
      await api.init();
      expect(api.initialized).toEqual(true);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('operation method names are configurable', async () => {
      const api = new OpenAPIClientAxios({
        definition,
        transformOperationName: (operation) => operation.toUpperCase(),
      });
      await api.init();

      expect(api.client).toHaveProperty('GETPETS');
      expect(api.client).toHaveProperty('CREATEPET');
      expect(api.client).toHaveProperty('GETPETBYID');
    });

    test('dereferences the input document', async () => {
      const api = new OpenAPIClientAxios({ definition });
      await api.init();
      expect(JSON.stringify(api.inputDocument)).toMatch('$ref');
      expect(JSON.stringify(api.definition)).not.toMatch('$ref');
    });

    test('can be initialized using a valid YAML file', async () => {
      const api = new OpenAPIClientAxios({ definition: 'http://localhost/example-pet-api.openapi.yml' });
      await api.init();
      expect(api.initialized).toEqual(true);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('can be initialized using a valid JSON file', async () => {
      const api = new OpenAPIClientAxios({ definition: 'http://localhost/example-pet-api.openapi.json' });
      await api.init();
      expect(api.initialized).toEqual(true);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('can be initialized using alternative server using index', async () => {
      const api = new OpenAPIClientAxios({ definition, withServer: 1 });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURLAlternative);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('can be initialized using alternative server using description', async () => {
      const api = new OpenAPIClientAxios({ definition, withServer: 'Alternative server' });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURLAlternative);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('can be initialized using alternative server with variable in baseURL', async () => {
      const api = new OpenAPIClientAxios({
        definition,
        withServer: 2,
        baseURLVariables: { foo2: 'bar2a', foo3: 1 },
      });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURLWithVariableResolved);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('can be initialized using alternative server using object', async () => {
      const url = 'http://examplde.com/v5';
      const api = new OpenAPIClientAxios({ definition, withServer: { url } });
      await api.init();
      expect(api.getBaseURL()).toEqual(url);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('can be initialized using default baseUrl resolver', async () => {
      const api = new OpenAPIClientAxios({ definition });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURL);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });
  });

  describe('withServer', () => {
    test('can set default server as object', async () => {
      const api = new OpenAPIClientAxios({ definition });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURL);
      const newServer = {
        url: 'http://example.com/apiv4',
        description: 'example api v4',
      };
      api.withServer(newServer);
      expect(api.getBaseURL()).toEqual(newServer.url);
    });

    test('can set default server by using description', async () => {
      const api = new OpenAPIClientAxios({ definition });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURL);
      const newServer = 'Alternative server';
      api.withServer(newServer);
      expect(api.getBaseURL()).toEqual(baseURLAlternative);
    });

    test('can set default server by index', async () => {
      const api = new OpenAPIClientAxios({ definition });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURL);
      const newServer = 1;
      api.withServer(newServer);
      expect(api.getBaseURL()).toEqual(baseURLAlternative);
    });

    test('can set default server with variables', async () => {
      const api = new OpenAPIClientAxios({ definition });
      await api.init();
      expect(api.getBaseURL()).toEqual(baseURL);
      const newServer = 2;
      const newServerVars = { foo2: 'bar2a', foo3: 1 };
      api.withServer(newServer, newServerVars);
      expect(api.getBaseURL()).toEqual(baseURLWithVariableResolved);
    });
  });

  describe('initSync', () => {
    test('can be initialized synchronously with a valid OpenAPI document as JS Object', () => {
      const api = new OpenAPIClientAxios({ definition });
      api.initSync();
      expect(api.initialized).toEqual(true);
      expect(api.client.api).toBe(api);
      checkHasOperationMethods(api.client);
    });

    test('dereferences the input document', () => {
      const api = new OpenAPIClientAxios({ definition });
      api.initSync();
      expect(JSON.stringify(api.inputDocument)).toMatch('$ref');
      expect(JSON.stringify(api.definition)).not.toMatch('$ref');
    });

    test('throws an error when initialized using a URL', () => {
      const api = new OpenAPIClientAxios({ definition: '/example-pet-api.openapi.json' });
      expect(api.initSync).toThrowError();
    });
  });

  describe('client', () => {
    test('has set default baseURL to the first server in config', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      expect(client.defaults.baseURL).toBe(baseURL);
    });

    test('can override axios default config', async () => {
      const api = new OpenAPIClientAxios({
        definition,
        axiosConfigDefaults: { maxRedirects: 1, withCredentials: true },
      });
      const client = await api.init();
      expect(client.defaults.maxRedirects).toBe(1);
      expect(client.defaults.withCredentials).toBe(true);
    });

    test('request defaults from user-provided axios instance are not modified', async () => {
      const userRequestTransformer = (data: object, headers?: object) => data;
      const userResponseTransformer = (data: object) => data;
      const userParamsSerializer = () => "";
      const userAdapter = () => new Promise<AxiosResponse<any, any>>(() => ({}));
      const userOnUploadProgress = () => {};
      const userOnDownloadProgress = () => {};
      const userValidateStatus = () => true;
      const userCancelToken = new axios.CancelToken(() => {});

      const userAxiosInstance = axios.create({
        url: '/hello',
        method: 'post',
        baseURL: 'https://some-domain.com/api',
        transformRequest: userRequestTransformer,
        transformResponse: userResponseTransformer,
        headers: {'X-Requested-With': 'fake'},
        params: {
          ID: 123456789,
        },
        paramsSerializer: userParamsSerializer,
        data: {
          hello: 'world',
        },
        timeout: 1234,
        withCredentials: true,
        adapter: userAdapter,
        auth: {
          username: 'fake',
          password: 'fakepassword'
        },
        responseType: 'stream',
        responseEncoding: 'fakeEncoding',
        xsrfCookieName: 'HELLO-WORLD',
        xsrfHeaderName: 'HELLO-WORLD-2',
        onUploadProgress: userOnUploadProgress,
        onDownloadProgress: userOnDownloadProgress,
        maxContentLength: 1234,
        maxBodyLength: 5678,
        validateStatus: userValidateStatus,
        maxRedirects: 99,
        socketPath: '/fake/path/example',
        proxy: {
          host: '1.2.3.4',
          port: 9876,
          auth: {
            username: 'anotherFakeUsername',
            password: 'anotherFakePassword'
          }
        },
        cancelToken: userCancelToken,
        decompress: false
      });

      const api = new OpenAPIClientAxios({
        definition,
        axiosInstance: userAxiosInstance,
      });
      const client = await api.init();
      const d = client.defaults;

      expect(d.url).toBe('/hello');
      expect(d.method).toBe('post');
      expect(d.baseURL).toBe('https://some-domain.com/api');
      expect(d.transformRequest).toBe(userRequestTransformer);
      expect(d.transformResponse).toBe(userResponseTransformer);
      expect((d.headers as unknown as Record<string, unknown>)['X-Requested-With']).toBe('fake');
      expect(d.params.ID).toBe(123456789);
      expect(d.paramsSerializer).toBe(userParamsSerializer);
      expect(d.data.hello).toBe('world');
      expect(d.timeout).toBe(1234);
      expect(d.withCredentials).toBe(true);
      expect(d.adapter).toBe(userAdapter);
      expect(d.auth).toStrictEqual({
        username: 'fake',
        password: 'fakepassword'
      }),
      expect(d.responseType).toBe('stream');
      expect(d.responseEncoding).toBe('fakeEncoding');
      expect(d.xsrfCookieName).toBe('HELLO-WORLD');
      expect(d.xsrfHeaderName).toBe('HELLO-WORLD-2');
      expect(d.onUploadProgress).toBe(userOnUploadProgress);
      expect(d.onDownloadProgress).toBe(userOnDownloadProgress);
      expect(d.maxContentLength).toBe(1234);
      expect(d.maxBodyLength).toBe(5678);
      expect(d.validateStatus).toBe(userValidateStatus);
      expect(d.maxRedirects).toBe(99);
      expect(d.socketPath).toBe('/fake/path/example');
      expect(d.proxy).toStrictEqual({
          host: '1.2.3.4',
          port: 9876,
          auth: {
            username: 'anotherFakeUsername',
            password: 'anotherFakePassword'
          },
      });
      expect(d.cancelToken).toBe(userCancelToken);
      expect(d.decompress).toBe(false);
    });
  });

  describe('operation methods', () => {
    test('getPets() calls GET /pets', 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').reply((config) => mockHandler(config));

      const res = await client.getPets();
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test("getPets({ q: 'cats' }) calls GET /pets?q=cats", 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').reply((config) => mockHandler(config));

      const params = { q: 'cats ' };
      const res = await client.getPets(params);

      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
      const mockContext = mockHandler.mock.calls[mockHandler.mock.calls.length - 1][0];
      expect(mockContext.params).toEqual(params);
    });

    test("getPets({ q: ['cats', 'dogs'] }) calls GET /pets?q=cats&q=dogs", 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').reply((config) => mockHandler(config));

      const params = { q: ['cats', 'dogs'] };
      const res = await client.getPets(params);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
      const mockContext = mockHandler.mock.calls[mockHandler.mock.calls.length - 1][0];
      expect(mockContext.params).toEqual(params);
    });

    test('getPetById({ petId: 1 }) calls GET /pets/1', 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 res = await client.getPetById({ petId: 1 });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetById(1) calls GET /pets/1', 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 res = await client.getPetById(1);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetById({ petId: 1 }) calls GET /pets/1', 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 res = await client.getPetById({ petId: 1 });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetById({ petId: 1, "x-petshop-id": "test-shop" }) calls GET /pets/1 with request header', 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 res = await client.getPetById({ petId: 1, 'x-petshop-id': 'test-shop' });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalledWith(expect.objectContaining({ headers: expect.objectContaining({ 'x-petshop-id': 'test-shop' }) }))
    });

    test('getPetById({ petId: 1, "x-petshop-id": "test-shop" }) calls GET /pets/1 with request header and extern 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' }),
        }),
      );
    });

    test('getPetById([{ name: "petId", value: "1", in: "path" }]) calls GET /pets/1', 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 res = await client.getPetById([{ name: 'petId', value: '1', in: 'path' }]);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetById([{ name: "petId", value: "1", in: "path" }, { name: "new", value: "2", in: "query" }]) calls GET /pets/1?new=2', 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 res = await client.getPetById([{ name: 'petId', value: '1', in: 'path' }]);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('createPet(pet) calls POST /pets with JSON payload', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.createPet(null, pet);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
      const mockContext = mockHandler.mock.calls[mockHandler.mock.calls.length - 1][0];
      expect(mockContext.data).toEqual(JSON.stringify(pet));
    });

    test('replacePetById(1, pet) calls PUT /pets/1 with JSON payload', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.replacePetById(1, pet);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
      const mockContext = mockHandler.mock.calls[mockHandler.mock.calls.length - 1][0];
      expect(mockContext.data).toEqual(JSON.stringify(pet));
    });

    test('deletePetById(1) calls DELETE /pets/1', 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.onDelete('/pets/1').reply((config) => mockHandler(config));

      const res = await client.deletePetById(1);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getOwnerByPetId(1) calls GET /pets/1/owner', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.getOwnerByPetId(1);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetOwner([1, 2]) calls GET /pets/1/owner/2', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.getPetOwner({ petId: 1, ownerId: 2 });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetOwner({ petId: 1, ownerId: 2 }) calls GET /pets/1/owner/2', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.getPetOwner({ petId: 1, ownerId: 2 });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetsMeta() calls GET /pets/meta', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.getPetsMeta();
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test('getPetsRelative() calls GET /v2/pets/relative', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

      const mock = new MockAdapter(api.client);
      const mockHandler = jest.fn((config) => [200, config.baseURL]);
      mock.onGet('/pets/relative').reply((config) => mockHandler(config));

      const res = await client.getPetsRelative();
      expect(res.data).toEqual(baseURLV2);
      expect(mockHandler).toBeCalled();
    });
  });

  describe('paths dictionary', () => {
    test(`paths['/pets'].get() calls GET /pets`, 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').reply((config) => mockHandler(config));

      const res = await client.paths['/pets'].get();
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test(`paths['/pets/{petId}'].get(1) calls GET /pets/1`, 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 res = await client.paths['/pets/{petId}'].get(1);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test(`paths['/pets'].post() calls POST /pets`, async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.paths['/pets'].post(null, pet);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
      const mockContext = mockHandler.mock.calls[mockHandler.mock.calls.length - 1][0];
      expect(mockContext.data).toEqual(JSON.stringify(pet));
    });

    test(`paths['/pets/{petId}'].put(1) calls PUT /pets/1`, async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.paths['/pets/{petId}'].put(1, pet);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
      const mockContext = mockHandler.mock.calls[mockHandler.mock.calls.length - 1][0];
      expect(mockContext.data).toEqual(JSON.stringify(pet));
    });

    test(`paths['/pets/{petId}'].delete(1) calls DELETE /pets/1`, 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.onDelete('/pets/1').reply((config) => mockHandler(config));

      const res = await client.paths['/pets/{petId}'].delete(1);
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test(`paths['/pets/{petId}/owner/{ownerId}'].get({ petId: 1, ownerId: 2 }) calls GET /pets/1/owner/2`, async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();

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

      const res = await client.paths['/pets/{petId}/owner/{ownerId}'].get({ petId: 1, ownerId: 2 });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });
  });

  describe('getRequestConfigForOperation()', () => {
    test('getPets() calls GET /pets', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getPets', []);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets');
    });

    test('getPets({ q: "cat" }) calls GET /pets?q=cat', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getPets', [{ q: 'cat' }]);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets');
      expect(config.url).toMatch('/pets?q=cat');
      expect(config.query).toEqual({ q: 'cat' });
      expect(config.queryString).toEqual('q=cat');
    });

    test('getPets({ q: ["cat"] }) calls GET /pets?q=cat', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getPets', [{ q: ['cat'] }]);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets');
      expect(config.url).toMatch('/pets?q=cat');
      expect(config.query).toEqual({ q: ['cat'] });
      expect(config.queryString).toEqual('q=cat');
    });

    test('getPetById({ petId: 1 }) calls GET /pets/1', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getPetById', [{ petId: 1 }]);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets/1');
      expect(config.pathParams).toEqual({ petId: '1' });
    });

    test('getPetById(1) calls GET /pets/1', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getPetById', [1]);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets/1');
      expect(config.pathParams).toEqual({ petId: '1' });
    });

    test('createPet(null, pet) calls POST /pets with JSON payload', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const pet = { name: 'Garfield' };
      const config = api.getRequestConfigForOperation('createPet', [null, pet]);

      expect(config.method).toEqual('post');
      expect(config.path).toEqual('/pets');
      expect(config.payload).toEqual(pet);
    });

    test('replacePetById(1, pet) calls PUT /pets/1 with JSON payload', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const pet = { id: 1, name: 'Garfield' };
      const config = api.getRequestConfigForOperation('replacePetById', [1, pet]);

      expect(config.method).toEqual('put');
      expect(config.path).toEqual('/pets/1');
      expect(config.pathParams).toEqual({ petId: '1' });
      expect(config.payload).toEqual(pet);
    });

    test('deletePetById(1) calls DELETE /pets/1', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('deletePetById', [1]);

      expect(config.method).toEqual('delete');
      expect(config.path).toEqual('/pets/1');
      expect(config.pathParams).toEqual({ petId: '1' });
    });

    test('getOwnerByPetId(1) calls GET /pets/1/owner', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getOwnerByPetId', [1]);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets/1/owner');
      expect(config.pathParams).toEqual({ petId: '1' });
    });

    test('getPetOwner({ petId: 1, ownerId: 2 }) calls GET /pets/1/owner/2', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getPetOwner', [{ petId: 1, ownerId: 2 }]);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets/1/owner/2');
      expect(config.pathParams).toEqual({ petId: '1', ownerId: '2' });
    });

    test('getPetsMeta() calls GET /pets/meta', async () => {
      const api = new OpenAPIClientAxios({ definition });
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getPetsMeta', []);

      expect(config.method).toEqual('get');
      expect(config.path).toEqual('/pets/meta');
    });

    test('should url encode path parameters', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/discounts/{name}': {
            get: {
              operationId: 'getDiscount',
              parameters: [{ name: 'name', in: 'path', required: true, schema: { type: 'string' } }],
              responses: { '200': { description: 'ok' } } },
            }
          }
      })});
      const client = await api.init();
      const config = api.getRequestConfigForOperation('getDiscount', ['20% / 30% off']);
      expect(config.path).toEqual('/discounts/20%25%20%2F%2030%25%20off');
    });
  });

  describe('query parameter array serialization', () => {
    test('array query param with default style (form, explode: true) serializes as repeated params', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/items': {
            get: {
              operationId: 'getItems',
              parameters: [{
                name: 'id',
                in: 'query',
                schema: { type: 'array', items: { type: 'integer' } },
                // default is style: 'form', explode: true
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getItems', [{ id: [3, 4, 5] }]);
      expect(config.queryString).toEqual('id=3&id=4&id=5');
    });

    test('array query param with style: form, explode: false serializes as comma-separated', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/items': {
            get: {
              operationId: 'getItems',
              parameters: [{
                name: 'id',
                in: 'query',
                schema: { type: 'array', items: { type: 'integer' } },
                style: 'form',
                explode: false
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getItems', [{ id: [3, 4, 5] }]);
      expect(config.queryString).toEqual('id=3,4,5');
    });

    test('array query param with style: spaceDelimited serializes as space-separated', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/items': {
            get: {
              operationId: 'getItems',
              parameters: [{
                name: 'id',
                in: 'query',
                schema: { type: 'array', items: { type: 'integer' } },
                style: 'spaceDelimited',
                explode: false
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getItems', [{ id: [3, 4, 5] }]);
      expect(config.queryString).toEqual('id=3%204%205');
    });

    test('array query param with style: pipeDelimited serializes as pipe-separated', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/items': {
            get: {
              operationId: 'getItems',
              parameters: [{
                name: 'id',
                in: 'query',
                schema: { type: 'array', items: { type: 'integer' } },
                style: 'pipeDelimited',
                explode: false
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getItems', [{ id: [3, 4, 5] }]);
      expect(config.queryString).toEqual('id=3%7C4%7C5');
    });

    test('object query param with style: deepObject serializes with bracket notation', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/users': {
            get: {
              operationId: 'getUsers',
              parameters: [{
                name: 'filter',
                in: 'query',
                schema: { type: 'object' },
                style: 'deepObject',
                explode: true
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getUsers', [{ filter: { role: 'admin', firstName: 'Alex' } } as any]);
      expect(config.queryString).toMatch(/filter\[role\]=admin/);
      expect(config.queryString).toMatch(/filter\[firstName\]=Alex/);
    });

    test('object query param with style: form, explode: true serializes as flat params', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/users': {
            get: {
              operationId: 'getUsers',
              parameters: [{
                name: 'filter',
                in: 'query',
                schema: { type: 'object' },
                style: 'form',
                explode: true
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getUsers', [{ filter: { role: 'admin', firstName: 'Alex' } } as any]);
      expect(config.queryString).toMatch(/role=admin/);
      expect(config.queryString).toMatch(/firstName=Alex/);
    });

    test('object query param with style: form, explode: false serializes as comma-separated key-value pairs', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/users': {
            get: {
              operationId: 'getUsers',
              parameters: [{
                name: 'filter',
                in: 'query',
                schema: { type: 'object' },
                style: 'form',
                explode: false
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getUsers', [{ filter: { role: 'admin', firstName: 'Alex' } } as any]);
      expect(config.queryString).toMatch(/filter=role,admin,firstName,Alex/);
    });

    test('array of strings with special characters is properly encoded', async () => {
      const api = new OpenAPIClientAxios({ definition: createDefinition({
        paths: {
          '/items': {
            get: {
              operationId: 'getItems',
              parameters: [{
                name: 'tags',
                in: 'query',
                schema: { type: 'array', items: { type: 'string' } },
                style: 'form',
                explode: true
              }],
              responses: { '200': { description: 'ok' } }
            }
          }
        }
      })});
      await api.init();
      const config = api.getRequestConfigForOperation('getItems', [{ tags: ['foo bar', 'baz&qux'] }]);
      expect(config.queryString).toEqual('tags=foo%20bar&tags=baz%26qux');
    });
  });

  describe('axios methods', () => {
    test("get('/pets') calls GET /pets", 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').reply((config) => mockHandler(config));

      const res = await client.get('/pets');
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });

    test("({ method: 'get', url: '/pets' }) calls GET /pets", 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').reply((config) => mockHandler(config));

      const res = await client({ method: 'get', url: '/pets' });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });
  });

  describe('transforms', () => {
    test('transformOperationName', async () => {
      const api = new OpenAPIClientAxios({
        definition,
        transformOperationName: (operationName) => `${operationName}V1`,
      });
      const client = await api.init();

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

      const res = await client.getPetOwnerV1({ petId: 1, ownerId: 2 });
      expect(res.data).toEqual(mockResponse);
      expect(mockHandler).toBeCalled();
    });
  });

  test('transformOperationMethod', async () => {
    const api = new OpenAPIClientAxios({
      definition,
      transformOperationMethod: (operationMethod) => {
        return (params: any, body, config) => {
          params['petId'] = 1;
          params['ownerId'] = 2;
          return operationMethod(params, body, config);
        };
      },
    });
    const client = await api.init();

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

    const res = await client.getPetOwner({});
    expect(res.data).toEqual(mockResponse);
    expect(mockHandler).toBeCalled();
  });

  test('transformOperationMethod based on operation', async () => {
    const api = new OpenAPIClientAxios({
      definition,
      transformOperationMethod: (operationMethod, operation) => {
        return (params: any, body, config) => {
          if (operation.operationId === 'getPetOwner') {
            params['petId'] = 1;
            params['ownerId'] = 2;
          }
          return operationMethod(params, body, config);
        };
      },
    });
    const client = await api.init();

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

    const res = await client.getPetOwner({});
    expect(res.data).toEqual(mockResponse);
    expect(mockHandler).toBeCalled();
  });
});


================================================
FILE: src/client.ts
================================================
import axios, { AxiosInstance, AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse, Method } from 'axios';
import bath from 'bath-es5';
import { dereferenceSync } from 'dereference-json-schema';

import {
  Document,
  Operation,
  UnknownOperationMethod,
  OperationMethodArguments,
  UnknownOperationMethods,
  RequestConfig,
  ParamType,
  HttpMethod,
  UnknownPathsDictionary,
  Server,
  ParameterObject,
} from './types/client';
import { serializeQueryParameter } from './query-serializer';

/**
 * OpenAPIClient is an AxiosInstance extended with operation methods
 */
export type OpenAPIClient<
  OperationMethods = UnknownOperationMethods,
  PathsDictionary = UnknownPathsDictionary,
> = AxiosInstance &
  OperationMethods & {
    api: OpenAPIClientAxios;
    paths: PathsDictionary;
  };

/**
 * By default OpenAPIClient will use axios as request runner. You can register a different runner,
 * in case you want to switch over from axios.
 */
export declare type Runner = {
  runRequest: RunRequestFunc;
  context?: UnknownContext;
};

/**
 * Context to be injected into Runner.runRequest
 */
export declare type UnknownContext = Record<string, unknown>;

/**
 * Type for runRequest function. It allows extending/switching from axios to another method of running http requests.
 */
export declare type RunRequestFunc = (
  axiosConfig: AxiosRequestConfig,
  operation: Operation,
  context?: UnknownContext,
) => Promise<AxiosResponse>;

const DefaultRunnerKey = 'default';

export type OpenAPIClientAxiosOptions = {
  definition: Document | string;
  quick?: boolean;
  withServer?: number | string | Server;
  baseURLVariables?: { [key: string]: string | number };
  applyMethodCommonHeaders?: boolean;
  transformOperationName?: (operation: string) => string;
  transformOperationMethod?: (
    operationMethod: UnknownOperationMethod,
    operationToTransform: Operation,
  ) => UnknownOperationMethod;
  axiosRunner?: (axiosConfig: AxiosRequestConfig) => Promise<AxiosResponse>;
  axiosConfigDefaults?: AxiosRequestConfig;
} & ({
  axiosConfigDefaults?: AxiosRequestConfig;
  axiosInstance?: never;
} | {
  axiosConfigDefaults?: never;
  axiosInstance?: AxiosInstance;
});

/**
 * Main class and the default export of the 'openapi-client-axios' module
 *
 * @export
 * @class OpenAPIClientAxios
 */
export class OpenAPIClientAxios {
  public document: Document;
  public inputDocument: Document | string;
  public definition: Document;

  public quick: boolean;

  public initialized: boolean;
  public instance: any;

  public axiosConfigDefaults: AxiosRequestConfig;

  private defaultServer: number | string | Server;
  private baseURLVariables: { [key: string]: string | number };
  private applyMethodCommonHeaders: boolean;

  private transformOperationName: (operation: string) => string;
  private transformOperationMethod: (
    operationMethod: UnknownOperationMethod,
    operationToTransform: Operation,
  ) => UnknownOperationMethod;

  // maps operationId to Runner
  private runners: Record<string, Runner>;

  /**
   * Creates an instance of OpenAPIClientAxios.
   *
   * @param opts - constructor options
   * @param {Document | string} opts.definition - the OpenAPI definition, file path or Document object
   * @param {boolean} opts.quick - quick mode, skips validation and doesn't guarantee document is unchanged
   * @param {boolean} opts.applyMethodCommonHeaders Should method (patch / post / put / etc.) specific default headers (from axios.defaults.headers.{method}) be applied to operation methods?
   * @param {boolean} opts.axiosConfigDefaults - default axios config for the instance
   * @param {boolean} opts.axiosInstance - axios instance to use
   * @memberof OpenAPIClientAxios
   */
  constructor(opts: OpenAPIClientAxiosOptions) {
    this.inputDocument = opts.definition;
    this.quick = opts.quick ?? false;
    this.axiosConfigDefaults = opts.axiosConfigDefaults ?? {};
    this.instance = opts.axiosInstance;
    this.defaultServer = opts.withServer ?? 0;
    this.baseURLVariables = opts.baseURLVariables ?? {};
    this.applyMethodCommonHeaders = opts.applyMethodCommonHeaders ?? false;
    this.transformOperationName = opts.transformOperationName ?? ((operationId: string) => operationId);
    this.transformOperationMethod =
      opts.transformOperationMethod ?? ((operationMethod: UnknownOperationMethod) => operationMethod);
    this.runners = {
      [DefaultRunnerKey]: {
        runRequest: opts.axiosRunner ?? ((axiosConfig: AxiosRequestConfig) => this.client.request(axiosConfig)),
      },
    };
  }

  /**
   * Returns the instance of OpenAPIClient
   *
   * @readonly
   * @type {OpenAPIClient}
   * @memberof OpenAPIClientAxios
   */
  get client() {
    return this.instance as OpenAPIClient;
  }

  /**
   * Returns the instance of OpenAPIClient
   *
   * @returns
   * @memberof OpenAPIClientAxios
   */
  public getClient = async <Client = OpenAPIClient>(): Promise<Client> => {
    if (!this.initialized) {
      return this.init<Client>();
    }
    return this.instance as Client;
  };

  public withServer(server: number | string | Server, variables: { [key: string]: string | number } = {}) {
    this.defaultServer = server;
    this.baseURLVariables = variables;
  }

  /**
   * Initializes OpenAPIClientAxios and creates a member axios client instance
   *
   * The init() method should be called right after creating a new instance of OpenAPIClientAxios
   *
   * @returns AxiosInstance
   * @memberof OpenAPIClientAxios
   */
  public init = async <Client = OpenAPIClient>(): Promise<Client> => {
    await this.loadDocument();

    // dereference the document into definition
    this.definition = dereferenceSync(this.document) as Document;

    // create axios instance
    this.instance = this.createAxiosInstance();

    // we are now initialized
    this.initialized = true;
    return this.instance as Client;
  };

  /**
   * Loads document from inputDocument
   *
   * Supports loading from a string (url) or an object (json)
   *
   * @memberof OpenAPIClientAxios
   */
  public async loadDocument() {
    if (typeof this.inputDocument === 'object') {
      this.document = this.inputDocument;
    } else {
      // create temporary instance to get the document
      const client = this.getAxiosInstance();

      // load the document
      const documentRes = await client.get(this.inputDocument);

      // set document
      if (typeof documentRes.data === 'object') {
        // json response
        this.document = documentRes.data;
      } else if (typeof documentRes.data === 'string' && documentRes.headers['content-type']?.match(/ya?ml/)) {
        // yaml response
        const yaml = await import('js-yaml');
        this.document = yaml.load(documentRes.data) as Document;
      } else {
        const err = new Error(`Invalid response fetching OpenAPI definition: ${documentRes}`) as any;
        err.response = documentRes;
        throw err;
      }
    }

    return this.document;
  }

  /**
   * Synchronous version of .init()
   *
   * Note: Only works when the input definition is a valid OpenAPI v3 object (URLs are not supported)
   *
   * @memberof OpenAPIClientAxios
   */
  public initSync = <Client = OpenAPIClient>(): Client => {
    if (typeof this.inputDocument !== 'object') {
      throw new Error(`.initSync() can't be called with a non-object definition. Please use .init()`);
    }

    // set document
    this.document = this.inputDocument;

    // dereference the document into definition
    this.definition = dereferenceSync(this.document) as Document;

    // create axios instance
    this.instance = this.createAxiosInstance();

    // we are now initialized
    this.initialized = true;
    return this.instance as Client;
  };

  /**
   * Creates a new axios instance, if necessary, and returns it
   */
  public getAxiosInstance = (): AxiosInstance => {
    let instance = this.instance;
    if (!instance) {
      instance = axios.create(this.axiosConfigDefaults) as OpenAPIClient;
    }
    return instance;
  };

  /**
   * Creates a new axios instance, extends it and returns it
   *
   * @memberof OpenAPIClientAxios
   */
  public createAxiosInstance = <Client = OpenAPIClient>(): Client => {
    // create axios instance
    const instance = this.getAxiosInstance() as OpenAPIClient;

    // set baseURL to the one found in the definition servers (if not set in axios defaults)
    const baseURL = this.getBaseURL();
    if (baseURL && !instance.defaults.baseURL) {
      instance.defaults.baseURL = baseURL;
    }

    // create methods for operationIds
    const operations = this.getOperations();
    for (const operation of operations) {
      const { operationId } = operation;
      if (operationId) {
        instance[this.transformOperationName(operationId)] = this.createOperationMethod(operation);
      }
    }

    // create paths dictionary
    // Example: api.paths['/pets/{id}'].get({ id: 1 });
    instance.paths = {};
    for (const path in this.definition.paths) {
      if (this.definition.paths[path]) {
        if (!instance.paths[path]) {
          instance.paths[path] = {};
        }
        const methods = this.definition.paths[path];
        for (const m in methods) {
          if (methods[m as HttpMethod] && Object.values(HttpMethod).includes(m as HttpMethod)) {
            const method = m as HttpMethod;
            const operation = this.getOperations().find((op) => op.method === method && op.path === path);
            instance.paths[path][method] = this.createOperationMethod(operation);
          }
        }
      }
    }

    // add reference to parent class instance
    instance.api = this;
    return instance as any as Client;
  };

  /**
   * Gets the API baseurl defined in the first OpenAPI specification servers property
   *
   * @returns string
   * @memberof OpenAPIClientAxios
   */
  public getBaseURL = (operation?: Operation): string | undefined => {
    if (!this.definition) {
      return undefined;
    }
    if (operation) {
      if (typeof operation === 'string') {
        operation = this.getOperation(operation);
      }
      if (operation.servers && operation.servers[0]) {
        return operation.servers[0].url;
      }
    }

    // get the target server from this.defaultServer
    let targetServer;
    if (typeof this.defaultServer === 'number') {
      if (this.definition.servers && this.definition.servers[this.defaultServer]) {
        targetServer = this.definition.servers[this.defaultServer];
      }
    } else if (typeof this.defaultServer === 'string') {
      for (const server of this.definition.servers) {
        if (server.description === this.defaultServer) {
          targetServer = server;
          break;
        }
      }
    } else if (this.defaultServer.url) {
      targetServer = this.defaultServer;
    }

    // if no targetServer is found, return undefined
    if (!targetServer) {
      return undefined;
    }

    const baseURL = targetServer.url;
    const baseURLVariableSet = targetServer.variables;

    // get baseURL var names
    const baseURLBuilder = bath(baseURL);

    // if there are no variables to resolve: return baseURL as is
    if (!baseURLBuilder.names.length) {
      return baseURL;
    }

    // object to place variables resolved from this.baseURLVariables
    const baseURLVariablesResolved: { [key: string]: string } = {};

    // step through names and assign value from this.baseURLVariables or the default value
    // note: any variables defined in baseURLVariables but not actually variables in baseURL are ignored
    for (const name of baseURLBuilder.names) {
      const varValue = this.baseURLVariables[name];

      if (varValue !== undefined && baseURLVariableSet[name].enum) {
        // if varValue exists assign to baseURLVariablesResolved object
        if (typeof varValue === 'number') {
          // if number, get value from enum array

          const enumVal = baseURLVariableSet[name].enum[varValue];

          if (enumVal) {
            baseURLVariablesResolved[name] = enumVal;
          } else {
            // if supplied value out of range: throw error

            throw new Error(
              `index ${varValue} out of range for enum of baseURL variable: ${name}; \
              enum max index is ${baseURLVariableSet[name].enum.length - 1}`,
            );
          }
        } else if (typeof varValue === 'string') {
          // if string, validate against enum array

          if (baseURLVariableSet[name].enum.includes(varValue)) {
            baseURLVariablesResolved[name] = varValue;
          } else {
            // if supplied value doesn't exist on enum: throw error

            throw new Error(
              `${varValue} is not a valid entry for baseURL variable ${name}; \
                variable must be of the following: ${baseURLVariableSet[name].enum.join(', ')}`,
            );
          }
        }
      } else {
        // if varValue doesn't exist: get default

        baseURLVariablesResolved[name] = baseURLVariableSet[name].default;
      }
    }

    // return resolved baseURL
    return baseURLBuilder.path(baseURLVariablesResolved);
  };

  /**
   * Creates an axios config object for operation + arguments
   * @memberof OpenAPIClientAxios
   */
  public getAxiosConfigForOperation = (
    operation: Operation | string,
    args: OperationMethodArguments,
  ): AxiosRequestConfig => {
    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;
    return {
      ...axiosConfig,
      ...config,
      params: {
        ...axiosConfig?.params,
        ...config?.params,
      },
      headers: {
        ...axiosConfig?.headers,
        ...config?.headers,
      },
    };
  };

  /**
   * Creates a generic request config object for operation + arguments.
   *
   * This function contains the logic that handles operation method parameters.
   *
   * @memberof OpenAPIClientAxios
   */
  public getRequestConfigForOperation = (operation: Operation | string, args: OperationMethodArguments) => {
    if (typeof operation === 'string') {
      operation = this.getOperation(operation);
    }

    const pathParams = {} as RequestConfig['pathParams'];
    const query = {} as RequestConfig['query'];
    const queryStringParts: string[] = [];
    const headers = {} as RequestConfig['headers'];
    const cookies = {} as RequestConfig['cookies'];
    const parameters = (operation.parameters || []) as ParameterObject[];

    const setRequestParam = (name: string, value: any, type: ParamType | string) => {
      switch (type) {
        case ParamType.Path:
          pathParams[name] = value;
          break;
        case ParamType.Query: {
          query[name] = value;
          // Find the parameter definition to get style and explode
          const param = parameters.find((p) => p.name === name && p.in === 'query');
          const serialized = serializeQueryParameter(param, name, value);
          queryStringParts.push(...serialized);
          break;
        }
        case ParamType.Header:
          headers[name] = value;
          break;
        case ParamType.Cookie:
          cookies[name] = value;
          break;
      }
    };

    const getParamType = (paramName: string): ParamType => {
      const param = parameters.find(({ name }) => name === paramName);
      if (param) {
        return param.in as ParamType;
      }
      // default all params to query if operation doesn't specify param
      return ParamType.Query;
    };

    const getFirstOperationParam = () => {
      const firstRequiredParam = parameters.find(({ required }) => required === true);
      if (firstRequiredParam) {
        return firstRequiredParam;
      }
      const firstParam = parameters[0];
      if (firstParam) {
        return firstParam;
      }
    };

    const [paramsArg, payload] = args;
    if (Array.isArray(paramsArg)) {
      // ParamsArray
      for (const param of paramsArg) {
        setRequestParam(param.name, param.value, param.in || getParamType(param.name));
      }
    } else if (typeof paramsArg === 'object') {
      // ParamsObject
      for (const name in paramsArg) {
        if (paramsArg[name] !== undefined) {
          setRequestParam(name, paramsArg[name], getParamType(name));
        }
      }
    } else if (paramsArg) {
      const firstParam = getFirstOperationParam();
      if (!firstParam) {
        throw new Error(`No parameters found for operation ${operation.operationId}`);
      }
      setRequestParam(firstParam.name, paramsArg, firstParam.in as ParamType);
    }

    // path parameters
    const pathBuilder = bath(operation.path);
    // make sure all path parameters are set
    for (const name of pathBuilder.names) {
      const value = pathParams[name];
      pathParams[name] = `${value}`;
    }
    const path = pathBuilder.path(pathParams);

    // queryString parameter
    const queryString = queryStringParts.join('&');

    // full url with query string
    const url = `${this.getBaseURL(operation)}${path}${queryString ? `?${queryString}` : ''}`;

    // add default common headers
    const defaultHeaders = this.client.defaults.headers;
    for (const [key, val] of Object.entries(defaultHeaders.common ?? {})) {
      headers[key] = val;
    }

    // add method specific default headers
    if (this.applyMethodCommonHeaders) {
      const methodHeaders: AxiosRequestHeaders = (defaultHeaders as any)[operation.method] ?? {};
      for (const [key, val] of Object.entries(methodHeaders)) {
        headers[key] = val;
      }
    }

    // construct request config
    const config: RequestConfig = {
      method: operation.method,
      url,
      path,
      pathParams,
      query,
      queryString,
      headers,
      cookies,
      payload,
    };
    return config;
  };

  /**
   * Flattens operations into a simple array of Operation objects easy to work with
   *
   * @returns {Operation[]}
   * @memberof OpenAPIBackend
   */
  public getOperations = (): Operation[] => {
    const paths = this.definition?.paths || {};
    return Object.entries(paths).flatMap(([path, pathObject]) => {
      return Object.values(HttpMethod)
        .map((method) => ({ path, method, operation: pathObject[method] }))
        .filter(({ operation }) => operation)
        .map(({ operation, method }) => {
          const op: Partial<Operation> = {
            ...(typeof operation === 'object' ? operation : {}),
            path,
            method: method as HttpMethod,
          };
          if (pathObject.parameters) {
            op.parameters = [...(op.parameters || []), ...pathObject.parameters];
          }
          if (pathObject.servers) {
            op.servers = [...(op.servers || []), ...pathObject.servers];
          }
          op.security = op.security ?? this.definition.security;
          return op as Operation;
        });
    });
  };

  /**
   * Gets a single operation based on operationId
   *
   * @param {string} operationId
   * @returns {Operation}
   * @memberof OpenAPIBackend
   */
  public getOperation = (operationId: string): Operation | undefined => {
    return this.getOperations().find((op) => op.operationId === operationId);
  };

  /**
   * By default OpenAPIClient will use axios as request runner. You can register a different runner,
   * in case you want to switch over from axios. This allows transitioning from axios to your library of choice.
   * @param runner - request runner to be registered, either for all operations, or just one operation.
   * @param operationId - optional parameter. If provided, runner will be registered for a single operation. Else, it will be registered for all operations.
   */
  public registerRunner(runner: Runner, operationId?: string) {
    this.runners[operationId ?? DefaultRunnerKey] = runner;
  }

  private getRunner(operationId: string) {
    return this.runners[operationId] ?? this.runners[DefaultRunnerKey];
  }

  /**
   * Creates an axios method for an operation
   * (...pathParams, data?, config?) => Promise<AxiosResponse>
   *
   * @param {Operation} operation
   * @memberof OpenAPIClientAxios
   */
  private createOperationMethod = (operation: Operation): UnknownOperationMethod => {
    const originalOperationMethod = async (...args: OperationMethodArguments) => {
      const axiosConfig = this.getAxiosConfigForOperation(operation, args);
      // run the axios request with the registered runner
      // by default: axios runner
      const runner = this.getRunner(operation.operationId);
      return runner.runRequest(axiosConfig, operation, runner.context);
    };

    return this.transformOperationMethod(originalOperationMethod, operation);
  };
}


================================================
FILE: src/index.ts
================================================
import { OpenAPIClientAxios } from './client';
export default OpenAPIClientAxios;
export * from './client';
export * from './types/client';

// re-export axios types
export type { Axios, AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';


================================================
FILE: src/query-serializer.ts
================================================
import { ParameterObject } from './types/client';

/**
 * Serializes a query parameter according to OpenAPI 3.0 specification
 *
 * @param param - The parameter definition from OpenAPI spec
 * @param name - The parameter name
 * @param value - The parameter value (can be primitive, array, or object)
 * @returns Array of query string parts (key=value pairs)
 */
export const serializeQueryParameter = (
  param: ParameterObject | undefined,
  name: string,
  value: any,
): string[] => {
  // Get style and explode from parameter definition with defaults
  // Per OpenAPI spec: default style for query is 'form', default explode is true
  const style = param?.style || 'form';
  const explode = param?.explode !== undefined ? param.explode : true;

  // Handle null/undefined values
  if (value === null || value === undefined) {
    return [];
  }

  // Handle arrays
  if (Array.isArray(value)) {
    return serializeArrayParameter(name, value, style, explode);
  }

  // Handle objects
  if (typeof value === 'object') {
    return serializeObjectParameter(name, value, style, explode);
  }

  // Handle primitive values
  return [`${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`];
};

/**
 * Serializes an array query parameter
 */
const serializeArrayParameter = (
  name: string,
  value: any[],
  style: string,
  explode: boolean,
): string[] => {
  if (value.length === 0) {
    return [];
  }

  switch (style) {
    case 'form':
      if (explode) {
        // form explode=true: id=3&id=4&id=5
        return value.map((item) => `${encodeURIComponent(name)}=${encodeURIComponent(String(item))}`);
      } else {
        // form explode=false: id=3,4,5
        const encodedValues = value.map((item) => encodeURIComponent(String(item))).join(',');
        return [`${encodeURIComponent(name)}=${encodedValues}`];
      }

    case 'spaceDelimited':
      if (explode) {
        // spaceDelimited with explode=true is not valid per spec, but fallback to form explode=true
        return value.map((item) => `${encodeURIComponent(name)}=${encodeURIComponent(String(item))}`);
      } else {
        // spaceDelimited explode=false: id=3%204%205
        const encodedValues = value.map((item) => encodeURIComponent(String(item))).join('%20');
        return [`${encodeURIComponent(name)}=${encodedValues}`];
      }

    case 'pipeDelimited':
      if (explode) {
        // pipeDelimited with explode=true is not valid per spec, but fallback to form explode=true
        return value.map((item) => `${encodeURIComponent(name)}=${encodeURIComponent(String(item))}`);
      } else {
        // pipeDelimited explode=false: id=3%7C4%7C5
        const encodedValues = value.map((item) => encodeURIComponent(String(item))).join('%7C');
        return [`${encodeURIComponent(name)}=${encodedValues}`];
      }

    default:
      // Default to form explode=true
      return value.map((item) => `${encodeURIComponent(name)}=${encodeURIComponent(String(item))}`);
  }
};

/**
 * Serializes an object query parameter
 */
const serializeObjectParameter = (
  name: string,
  value: Record<string, any>,
  style: string,
  explode: boolean,
): string[] => {
  const keys = Object.keys(value);

  if (keys.length === 0) {
    return [];
  }

  switch (style) {
    case 'deepObject':
      if (explode) {
        // deepObject explode=true: filter[role]=admin&filter[firstName]=Alex
        return keys.map((key) =>
          `${encodeURIComponent(name)}[${encodeURIComponent(key)}]=${encodeURIComponent(String(value[key]))}`
        );
      } else {
        // deepObject with explode=false is not valid per spec, but fallback to form
        const pairs: string[] = [];
        for (const key of keys) {
          pairs.push(encodeURIComponent(key));
          pairs.push(encodeURIComponent(String(value[key])));
        }
        return [`${encodeURIComponent(name)}=${pairs.join(',')}`];
      }

    case 'form':
      if (explode) {
        // form explode=true: role=admin&firstName=Alex (flattened)
        return keys.map((key) =>
          `${encodeURIComponent(key)}=${encodeURIComponent(String(value[key]))}`
        );
      } else {
        // form explode=false: filter=role,admin,firstName,Alex
        const pairs: string[] = [];
        for (const key of keys) {
          pairs.push(encodeURIComponent(key));
          pairs.push(encodeURIComponent(String(value[key])));
        }
        return [`${encodeURIComponent(name)}=${pairs.join(',')}`];
      }

    default:
      // Default to form explode=true
      return keys.map((key) =>
        `${encodeURIComponent(key)}=${encodeURIComponent(String(value[key]))}`
      );
  }
};


================================================
FILE: src/types/client.ts
================================================
import type { AxiosResponse, AxiosRequestConfig } from 'axios';
import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
export type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';

/**
 * Type alias for OpenAPI document. We only support v3
 */
export declare type Document = OpenAPIV3.Document | OpenAPIV3_1.Document;
export declare type Server = OpenAPIV3.ServerObject | OpenAPIV3_1.ServerObject;
export declare type ParameterObject = OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject;

/**
 * OpenAPI allowed HTTP methods
 */
export enum HttpMethod {
  Get = 'get',
  Put = 'put',
  Post = 'post',
  Patch = 'patch',
  Delete = 'delete',
  Options = 'options',
  Head = 'head',
  Trace = 'trace',
}

/**
 * OpenAPI parameters "in"
 */
export enum ParamType {
  Query = 'query',
  Header = 'header',
  Path = 'path',
  Cookie = 'cookie',
}

/**
 * Operation method spec
 */
export declare type ImplicitParamValue = string | number;
export interface ExplicitParamValue {
  value: string | number;
  name: string;
  in?: ParamType | string;
}
export interface UnknownParamsObject {
  [parameter: string]: ImplicitParamValue | ImplicitParamValue[];
}
export declare type ParamsArray = ExplicitParamValue[];
export declare type SingleParam = ImplicitParamValue;
export declare type Parameters<ParamsObject = UnknownParamsObject> = ParamsObject | ParamsArray | SingleParam;
export declare type RequestPayload = any;
export declare type OperationMethodArguments = [Parameters?, RequestPayload?, AxiosRequestConfig?];
export declare type OperationResponse<Response> = Promise<AxiosResponse<Response>>;
export declare type UnknownOperationMethod = (
  parameters?: Parameters,
  data?: RequestPayload,
  config?: AxiosRequestConfig,
) => OperationResponse<any>;
export interface UnknownOperationMethods {
  [operationId: string]: UnknownOperationMethod;
}

/**
 * Generic request config object
 */
export interface RequestConfig {
  method: HttpMethod;
  url: string;
  path: string;
  pathParams: {
    [key: string]: string;
  };
  query: {
    [key: string]: string | string[];
  };
  queryString: string;
  headers: AxiosRequestConfig['headers'];
  cookies: {
    [cookie: string]: string;
  };
  payload?: RequestPayload;
}

/**
 * Operation object extended with path and method for easy looping
 */
export interface Operation extends OpenAPIV3.OperationObject {
  path: string;
  method: HttpMethod;
}

/**
 * A dictionary of paths and their methods
 */
export interface UnknownPathsDictionary {
  [path: string]: {
    [method in HttpMethod]?: UnknownOperationMethod;
  };
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "strict": true,
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "lib": ["esnext", "dom"],
    "esModuleInterop": true,
    "noImplicitAny": true,
    "strictPropertyInitialization": false,
    "strictNullChecks": false,
    "baseUrl": ".",
    "rootDir": "src/",
    "outDir": "",
    "sourceMap": true,
    "declaration": true,
    "downlevelIteration": true,
    "allowSyntheticDefaultImports": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "**/*.test.ts"
  ]
}
Download .txt
gitextract_dvpnw2jp/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── ci.yml
│       └── codeql.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DOCS.md
├── LICENSE
├── README.md
├── jest.config.ts
├── package.json
├── src/
│   ├── __tests__/
│   │   ├── fixtures.ts
│   │   └── resources/
│   │       ├── example-pet-api.openapi.json
│   │       └── example-pet-api.openapi.yml
│   ├── client.test.ts
│   ├── client.ts
│   ├── index.ts
│   ├── query-serializer.ts
│   └── types/
│       └── client.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (31 symbols across 2 files)

FILE: src/client.ts
  type OpenAPIClient (line 23) | type OpenAPIClient<
  type Runner (line 36) | type Runner = {
  type UnknownContext (line 44) | type UnknownContext = Record<string, unknown>;
  type RunRequestFunc (line 49) | type RunRequestFunc = (
  type OpenAPIClientAxiosOptions (line 57) | type OpenAPIClientAxiosOptions = {
  class OpenAPIClientAxios (line 84) | class OpenAPIClientAxios {
    method constructor (line 120) | constructor(opts: OpenAPIClientAxiosOptions) {
    method client (line 145) | get client() {
    method withServer (line 162) | public withServer(server: number | string | Server, variables: { [key:...
    method loadDocument (line 196) | public async loadDocument() {
    method registerRunner (line 628) | public registerRunner(runner: Runner, operationId?: string) {
    method getRunner (line 632) | private getRunner(operationId: string) {

FILE: src/types/client.ts
  type Document (line 8) | type Document = OpenAPIV3.Document | OpenAPIV3_1.Document;
  type Server (line 9) | type Server = OpenAPIV3.ServerObject | OpenAPIV3_1.ServerObject;
  type ParameterObject (line 10) | type ParameterObject = OpenAPIV3.ParameterObject | OpenAPIV3_1.Parameter...
  type HttpMethod (line 15) | enum HttpMethod {
  type ParamType (line 29) | enum ParamType {
  type ImplicitParamValue (line 39) | type ImplicitParamValue = string | number;
  type ExplicitParamValue (line 40) | interface ExplicitParamValue {
  type UnknownParamsObject (line 45) | interface UnknownParamsObject {
  type ParamsArray (line 48) | type ParamsArray = ExplicitParamValue[];
  type SingleParam (line 49) | type SingleParam = ImplicitParamValue;
  type Parameters (line 50) | type Parameters<ParamsObject = UnknownParamsObject> = ParamsObject | Par...
  type RequestPayload (line 51) | type RequestPayload = any;
  type OperationMethodArguments (line 52) | type OperationMethodArguments = [Parameters?, RequestPayload?, AxiosRequ...
  type OperationResponse (line 53) | type OperationResponse<Response> = Promise<AxiosResponse<Response>>;
  type UnknownOperationMethod (line 54) | type UnknownOperationMethod = (
  type UnknownOperationMethods (line 59) | interface UnknownOperationMethods {
  type RequestConfig (line 66) | interface RequestConfig {
  type Operation (line 87) | interface Operation extends OpenAPIV3.OperationObject {
  type UnknownPathsDictionary (line 95) | interface UnknownPathsDictionary {
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (116K chars).
[
  {
    "path": ".editorconfig",
    "chars": 197,
    "preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 99,
    "preview": "github: anttiviljami\nopen_collective: openapi-stack\ncustom:\n  - https://buymeacoff.ee/anttiviljami\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1011,
    "preview": "name: CI\non:\n  push:\n    branches: [\"main\"]\n    tags: [\"*\"]\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n  id-to"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 832,
    "preview": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n  schedule:\n    - cron: \"26 21"
  },
  {
    "path": ".gitignore",
    "chars": 138,
    "preview": "# build\n*.js\n*.js.map\n*.d.ts\n\n# include types\n!src/types/*.d.ts\n\n# include jest config\n!jest.config.js\n\n# npm\nnode_modul"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 74,
    "preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run lint\nnpm test"
  },
  {
    "path": ".prettierignore",
    "chars": 12,
    "preview": "*.d.ts\n*.js\n"
  },
  {
    "path": ".prettierrc",
    "chars": 126,
    "preview": "{\n  \"parser\": \"typescript\",\n  \"arrowParens\": \"always\",\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"printWidth\": "
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5220,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 119,
    "preview": "# Contributing\n\nOpenAPI Client Axios is Free and Open Source Software. Issues and pull requests are more than welcome!\n"
  },
  {
    "path": "DOCS.md",
    "chars": 164,
    "preview": "# Documentation\n\nOpenAPI Client Axios documentation has moved to [openapistack.co](https://openapistack.co)\n\nSee: https:"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2021 Viljami Kuosmanen\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "README.md",
    "chars": 7574,
    "preview": "<span id=\"npm--fix-hidden-readme-header\"></span>\n\n<h1 align=\"center\"><img alt=\"openapi-client-axios\" src=\"https://raw.gi"
  },
  {
    "path": "jest.config.ts",
    "chars": 303,
    "preview": "import type { JestConfigWithTsJest } from 'ts-jest'\n\nconst jestConfig: JestConfigWithTsJest = {\n  testEnvironment: 'node"
  },
  {
    "path": "package.json",
    "chars": 1891,
    "preview": "{\n  \"name\": \"openapi-client-axios\",\n  \"description\": \"JavaScript client library for consuming OpenAPI-enabled APIs with "
  },
  {
    "path": "src/__tests__/fixtures.ts",
    "chars": 4566,
    "preview": "import { OpenAPIV3 } from 'openapi-types';\n\nexport const baseURL = 'http://localhost:8080';\nexport const baseURLAlternat"
  },
  {
    "path": "src/__tests__/resources/example-pet-api.openapi.json",
    "chars": 8759,
    "preview": "{\n  \"openapi\": \"3.0.0\",\n  \"info\": {\n    \"title\": \"Example API\",\n    \"description\": \"Example CRUD API for pets\",\n    \"ver"
  },
  {
    "path": "src/__tests__/resources/example-pet-api.openapi.yml",
    "chars": 6192,
    "preview": "openapi: 3.0.0\ninfo:\n  title: Example API\n  description: Example CRUD API for pets\n  version: 1.0.0\ntags:\n  - name: pets"
  },
  {
    "path": "src/client.test.ts",
    "chars": 42746,
    "preview": "import path from 'path';\nimport fs from 'fs';\nimport { rest } from 'msw'\nimport { setupServer } from 'msw/node'\nimport M"
  },
  {
    "path": "src/client.ts",
    "chars": 21353,
    "preview": "import axios, { AxiosInstance, AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse, Method } from 'axios';\nimport bat"
  },
  {
    "path": "src/index.ts",
    "chars": 264,
    "preview": "import { OpenAPIClientAxios } from './client';\nexport default OpenAPIClientAxios;\nexport * from './client';\nexport * fro"
  },
  {
    "path": "src/query-serializer.ts",
    "chars": 4671,
    "preview": "import { ParameterObject } from './types/client';\n\n/**\n * Serializes a query parameter according to OpenAPI 3.0 specific"
  },
  {
    "path": "src/types/client.ts",
    "chars": 2587,
    "preview": "import type { AxiosResponse, AxiosRequestConfig } from 'axios';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-typ"
  },
  {
    "path": "tsconfig.json",
    "chars": 546,
    "preview": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"nod"
  }
]

About this extraction

This page contains the full source code of the openapistack/openapi-client-axios GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (107.9 KB), approximately 26.9k tokens, and a symbol index with 31 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!