Skip to content

Commit

Permalink
feat: add axios HTTP adapter (#4)
Browse files Browse the repository at this point in the history
* Use type instead of string for HTTP method.

* Add AxiosHttpAdapter.

* Add documentation for AxiosHttpAdapter.
  • Loading branch information
masterT authored Mar 24, 2022
1 parent eaabc0d commit e0584f5
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 3 deletions.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Bare metal TypeScript and JavaScript client for web API implementing [JSON:API v
- Type-safe
- Isomorphic

Documentations
- [Typescript documentation](https://masterT.github.io/jsonapi-metal-client/typescript/)

---

## Table of Contents
Expand All @@ -18,7 +21,8 @@ Bare metal TypeScript and JavaScript client for web API implementing [JSON:API v
* [Usage](#usage)
* [Documentation](#documentation)
+ [HTTP adapter](#http-adapter)
- [Using `fetch`](#using--fetch-)
- [Using `fetch`](#using-fetch)
- [Using `axios`](#using-axios)
+ [Client](#client)
- [Configure custom HTTP headers](#configure-custom-http-headers)
- [Result](#result)
Expand Down Expand Up @@ -162,6 +166,30 @@ client.deleteRelationshipToMany(

### HTTP adapter

#### Using `axios`

HTTP Adapter using the [axios](://github.com/axios/axios).

```js
import axios from 'axios'

const httpAdapter = new HttpAdapters.AxiosHttpAdapter(
axios
);
```

Using custom instance:

```js
import axios from 'axios'

const instance = axios.create({ /* ... */ });

const httpAdapter = new HttpAdapters.AxiosHttpAdapter(
instance
);
```

#### Using `fetch`

HTTP Adapter using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
Expand Down
67 changes: 67 additions & 0 deletions __tests__/HttpAdapters/AxiosHttpAdapter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import axios, { AxiosInstance } from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { HttpAdapters, HttpAdapter } from '../../src';

describe.only('HttpAdapters.AxiosHttpAdapter', () => {
let axiosInstance: AxiosInstance;
let axiosMock: MockAdapter;
let request: HttpAdapter.AdapterRequest;
let httpAdapter: HttpAdapters.AxiosHttpAdapter;

beforeAll(() => {
axiosInstance = axios.create();
axiosMock = new MockAdapter(axiosInstance);
httpAdapter = new HttpAdapters.AxiosHttpAdapter(axiosInstance);
});

beforeEach(() => {
request = {
url: 'https://examples.com/',
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: '{"foo":"bar"}'
};
});

afterEach(() => {
axiosMock.reset();
});

describe('request', () => {
beforeEach(() => {
axiosMock
.onPost('https://examples.com/')
.reply(200, '{"message":"ok"}', { 'Content-Type': 'application/json' });
});

test('calls request on the axios instance', async () => {
await httpAdapter.request(request);

expect(axiosMock.history.post.length).toBe(1);
expect(axiosMock.history.post[0]).toMatchObject({
url: 'https://examples.com/',
method: expect.stringMatching(/post/i),
headers: {
'Content-Type': 'application/json'
},
data: '{"foo":"bar"}',
transitional: {
silentJSONParsing: false,
forcedJSONParsing: false
}
});
});

test('resolves with response', async () => {
await expect(httpAdapter.request(request)).resolves.toEqual({
body: '{"message":"ok"}',
headers: {
'Content-Type': 'application/json'
},
status: 200
});
});
});
});
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@types/jest": "^27.4.0",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"axios-mock-adapter": "^1.20.0",
"cross-fetch": "^3.1.5",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
Expand All @@ -33,5 +34,8 @@
"ts-jest": "^27.1.2",
"typedoc": "^0.22.10",
"typescript": "^4.5.4"
},
"dependencies": {
"axios": "^0.26.1"
}
}
14 changes: 13 additions & 1 deletion src/HttpAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,22 @@ export interface AdapterResponse {
body?: string;
}

export type AdapterRequestMethod =
| 'GET'
| 'DELETE'
| 'HEAD'
| 'OPTIONS'
| 'POST'
| 'PUT'
| 'PATCH'
| 'PURGE'
| 'LINK'
| 'UNLINK';

/** @see {@link isAdapterRequest} ts-auto-guard:type-guard */
export interface AdapterRequest {
url: string;
method: string;
method: AdapterRequestMethod;
headers?: { [key: string]: string };
body: string | null;
}
Expand Down
47 changes: 47 additions & 0 deletions src/HttpAdapters/AxiosHttpAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { AxiosInstance } from 'axios';
import * as HttpAdapter from '../HttpAdapter';

/**
* {@link HttpAdapter.Adapter} using {@link https://github.com/axios/axios|axios} for make HTTP requests.
*/
export class AxiosHttpAdapter implements HttpAdapter.Adapter {
/**
* The Axios instance.
*/
instance: AxiosInstance;

/**
* Create an axios HTTP adapter.
* @param instance - The Axios instance.
*/
constructor(instance: AxiosInstance) {
this.instance = instance;
}

/**
* @see {@link HttpAdapter.Adapter.request}
* @param options
* @returns
*/
async request(
options: HttpAdapter.AdapterRequest
): Promise<HttpAdapter.AdapterResponse> {
const { url, method, headers, body } = options;
const response = await this.instance.request({
headers,
method,
url,
data: body,
// Specify not to parse JSON as the adapter should return the raw response body.
transitional: {
silentJSONParsing: false,
forcedJSONParsing: false
}
});
return {
status: response.status,
body: response.data,
headers: response.headers
};
}
}
1 change: 1 addition & 0 deletions src/HttpAdapters/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { FetchHttpAdapter } from './FetchHttpAdapter';
export { AxiosHttpAdapter } from './AxiosHttpAdapter';
2 changes: 1 addition & 1 deletion src/JsonApi/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ export class Client {

private async requestUpdateRelationshipToMany(
url: string,
method: string,
method: HttpAdapter.AdapterRequestMethod,
document: any
): Promise<Result<Specification.UpdateRelationshipToManyResponse>> {
try {
Expand Down
31 changes: 31 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,22 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=

axios-mock-adapter@^1.20.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.20.0.tgz#21f5b4b625306f43e8c05673616719da86e20dcb"
integrity sha512-shZRhTjLP0WWdcvHKf3rH3iW9deb3UdKbdnKUoHmmsnBhVXN3sjPJM6ZvQ2r/ywgvBVQrMnjrSyQab60G1sr2w==
dependencies:
fast-deep-equal "^3.1.3"
is-blob "^2.1.0"
is-buffer "^2.0.5"

axios@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"

babel-jest@^27.5.0:
version "27.5.0"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.0.tgz#c653985241af3c76f59d70d65a570860c2594a50"
Expand Down Expand Up @@ -1579,6 +1595,11 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==

follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==

form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
Expand Down Expand Up @@ -1793,6 +1814,16 @@ is-absolute@^1.0.0:
is-relative "^1.0.0"
is-windows "^1.0.1"

is-blob@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-blob/-/is-blob-2.1.0.tgz#e36cd82c90653f1e1b930f11baf9c64216a05385"
integrity sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==

is-buffer@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==

is-core-module@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
Expand Down

0 comments on commit e0584f5

Please sign in to comment.