Skip to content

Commit

Permalink
feat: expose axios types at the top-level
Browse files Browse the repository at this point in the history
  • Loading branch information
Weffe committed Feb 11, 2020
1 parent 957bc24 commit 879718f
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 194 deletions.
30 changes: 15 additions & 15 deletions src/axios-api-versioning-interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { AxiosInstance } from 'axios';
import { VersioningStrategy, IVersioningConfig, } from './types'
import { AxiosRequestConfigWithVersioning } from './types/axios';
import { VersioningStrategy, IVersioningConfig, AxiosRequestConfigWithVersioning } from './types';

function replaceUrlPathWithVersion(url: string, apiVersion: string) {
// the template name of the api version must be "apiVersion"
return url.replace('{apiVersion}', apiVersion);
}

function enhanceConfigByVersioningStrategy(requestConfig: AxiosRequestConfigWithVersioning, versioningConfig: IVersioningConfig): AxiosRequestConfigWithVersioning {

function enhanceConfigByVersioningStrategy(
requestConfig: AxiosRequestConfigWithVersioning,
versioningConfig: IVersioningConfig
): AxiosRequestConfigWithVersioning {
// we prioritize the apiVersion passed via the RequestConfig first
// then use the initial versioningConfig last
const apiVersion = requestConfig['apiVersion'] || versioningConfig['apiVersion'];
Expand All @@ -19,13 +20,13 @@ function enhanceConfigByVersioningStrategy(requestConfig: AxiosRequestConfigWith
if (versioningStrategy === VersioningStrategy.QueryString) {
requestConfig.params = {
...requestConfig.params,
[versioningConfig.queryStringKeyName]: apiVersion
[versioningConfig.queryStringKeyName]: apiVersion,
};
}

if (versioningStrategy === VersioningStrategy.MediaType) {
const defaultAcceptHeader: string = requestConfig.headers.common["Accept"];
const reqAcceptHeader: string | undefined = requestConfig.headers["Accept"] || undefined;
const defaultAcceptHeader: string = requestConfig.headers.common['Accept'];
const reqAcceptHeader: string | undefined = requestConfig.headers['Accept'] || undefined;

// we prioritize an accept header passed in the RequestConfig but default to the
// the common default accept header value axios provides
Expand All @@ -35,19 +36,18 @@ function enhanceConfigByVersioningStrategy(requestConfig: AxiosRequestConfigWith
const formattedAcceptHeader = versioningConfig.mediaTypeFormatter({
apiVersion,
acceptHeader,
mediaTypeKeyName: versioningConfig.mediaTypeKeyName
})
mediaTypeKeyName: versioningConfig.mediaTypeKeyName,
});

requestConfig.headers = {
...requestConfig.headers,
["Accept"]: formattedAcceptHeader
}
}
else {
['Accept']: formattedAcceptHeader,
};
} else {
requestConfig.headers = {
...requestConfig.headers,
["Accept"]: acceptHeader + `;${versioningConfig.mediaTypeKeyName}=${apiVersion}`
}
['Accept']: acceptHeader + `;${versioningConfig.mediaTypeKeyName}=${apiVersion}`,
};
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/axios-api-versioning.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios, { AxiosInstance, AxiosStatic } from 'axios';
import { AxiosInstanceWithVersioning } from './types/axios';
import { IWithVersioningConfig, IVersioningConfig } from './types'
import { injectApiVersioningInterceptor } from './axios-api-versioning-interceptor'
import { IWithVersioningConfig, IVersioningConfig } from './types';
import { injectApiVersioningInterceptor } from './axios-api-versioning-interceptor';
import { defaultWithVersioningConfig } from './defaultConfig';

export function withVersioning(instance: AxiosInstance | AxiosStatic, config: IWithVersioningConfig) {
Expand All @@ -15,4 +15,4 @@ export function withVersioning(instance: AxiosInstance | AxiosStatic, config: IW
injectApiVersioningInterceptor(clonedInstance, versioningConfig);

return clonedInstance as AxiosInstanceWithVersioning;
}
}
4 changes: 2 additions & 2 deletions src/defaultConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IVersioningConfig, PickPartial } from './types';

export const defaultWithVersioningConfig: PickPartial<IVersioningConfig, "apiVersion" | "versioningStrategy"> = {
export const defaultWithVersioningConfig: PickPartial<IVersioningConfig, 'apiVersion' | 'versioningStrategy'> = {
mediaTypeKeyName: 'v',
queryStringKeyName: 'api-version',
}
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { withVersioning } from './axios-api-versioning';
export { VersioningStrategy, IWithVersioningConfig, MediaTypeFormatterFn } from './types';
export * from './types/axios';
37 changes: 22 additions & 15 deletions src/types/axios/axios.types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
// Matching type definitions for axios 0.18

import {
AxiosRequestConfig,
AxiosPromise,
AxiosInterceptorManager,
AxiosResponse,
} from 'axios';
import { AxiosRequestConfig, AxiosPromise, AxiosInterceptorManager, AxiosResponse } from 'axios';

/**
* In order to only expose the apiVersion and versioningStrategy to axios instances with
Expand Down Expand Up @@ -44,15 +39,27 @@ export interface AxiosInstanceWithVersioning {
get<T = any, R = AxiosResponseWithVersioning<T>>(url: string, config?: AxiosRequestConfigWithVersioning): Promise<R>;
delete<T = any, R = AxiosResponseWithVersioning<T>>(url: string, config?: AxiosRequestConfigWithVersioning): Promise<R>;
head<T = any, R = AxiosResponseWithVersioning<T>>(url: string, config?: AxiosRequestConfigWithVersioning): Promise<R>;
post<T = any, R = AxiosResponseWithVersioning<T>>(url: string, data?: any, config?: AxiosRequestConfigWithVersioning): Promise<R>;
put<T = any, R = AxiosResponseWithVersioning<T>>(url: string, data?: any, config?: AxiosRequestConfigWithVersioning): Promise<R>;
patch<T = any, R = AxiosResponseWithVersioning<T>>(url: string, data?: any, config?: AxiosRequestConfigWithVersioning): Promise<R>;
post<T = any, R = AxiosResponseWithVersioning<T>>(
url: string,
data?: any,
config?: AxiosRequestConfigWithVersioning
): Promise<R>;
put<T = any, R = AxiosResponseWithVersioning<T>>(
url: string,
data?: any,
config?: AxiosRequestConfigWithVersioning
): Promise<R>;
patch<T = any, R = AxiosResponseWithVersioning<T>>(
url: string,
data?: any,
config?: AxiosRequestConfigWithVersioning
): Promise<R>;
}

export default interface AxiosTypes {
AxiosRequestConfigWithVersioning: AxiosRequestConfigWithVersioning,
AxiosAdapterWithVersioning: AxiosAdapterWithVersioning,
AxiosResponseWithVersioning: AxiosResponseWithVersioning,
AxiosErrorWithVersioning: AxiosErrorWithVersioning,
AxiosInstanceWithVersioning: AxiosInstanceWithVersioning,
}
AxiosRequestConfigWithVersioning: AxiosRequestConfigWithVersioning;
AxiosAdapterWithVersioning: AxiosAdapterWithVersioning;
AxiosResponseWithVersioning: AxiosResponseWithVersioning;
AxiosErrorWithVersioning: AxiosErrorWithVersioning;
AxiosInstanceWithVersioning: AxiosInstanceWithVersioning;
}
2 changes: 1 addition & 1 deletion src/types/axios/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export {
AxiosResponseWithVersioning,
AxiosErrorWithVersioning,
AxiosInstanceWithVersioning,
} from './axios.types'
} from './axios.types';
10 changes: 3 additions & 7 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
export {
VersioningStrategy,
MediaTypeFormatterFn,
IVersioningConfig,
IWithVersioningConfig,
PickPartial,
} from './types'
export { VersioningStrategy, MediaTypeFormatterFn, IVersioningConfig, IWithVersioningConfig, PickPartial } from './types';

export * from './axios';
14 changes: 4 additions & 10 deletions src/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
export enum VersioningStrategy {
QueryString = 'QUERY_STRING',
UrlPath = 'URL_PATH',
MediaType = 'MEDIA_TYPE'
MediaType = 'MEDIA_TYPE',
}

export type MediaTypeFormatterFn = (data: {
apiVersion: string,
mediaTypeKeyName: string,
acceptHeader: string
}) => string;
export type MediaTypeFormatterFn = (data: { apiVersion: string; mediaTypeKeyName: string; acceptHeader: string }) => string;

export interface IVersioningConfig {
apiVersion: string;
Expand All @@ -18,10 +14,8 @@ export interface IVersioningConfig {
mediaTypeFormatter?: MediaTypeFormatterFn;
}

export interface IWithVersioningConfig
extends PickPartial<IVersioningConfig, "mediaTypeKeyName" | "queryStringKeyName"> {
}
export interface IWithVersioningConfig extends PickPartial<IVersioningConfig, 'mediaTypeKeyName' | 'queryStringKeyName'> {}

// type helper
// @see https://stackoverflow.com/a/53742583
export type PickPartial<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> & Partial<Pick<T, K>>
export type PickPartial<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> & Partial<Pick<T, K>>;
110 changes: 55 additions & 55 deletions tests/axios-api-versioning.mock.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,79 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import * as status from "http-status-codes";
import { withVersioning } from "../axios-api-versioning";
import { IWithVersioningConfig, VersioningStrategy } from "../types";
import { AxiosInstanceWithVersioning } from "../types/axios";

const test_url = "http://localhost:3000";
const MOCK_RES = "hello_world";
import axios, { AxiosInstance } from 'axios';
import MockAdapter from 'axios-mock-adapter';
import * as status from 'http-status-codes';
import { withVersioning } from '../src/axios-api-versioning';
import { IWithVersioningConfig, VersioningStrategy } from '../src/types';
import { AxiosInstanceWithVersioning } from '../src/types/axios';

const test_url = 'http://localhost:3000';
const MOCK_RES = 'hello_world';
let mock: MockAdapter;
let instance: AxiosInstanceWithVersioning;

describe('Testing correct response config of "QueryString" strategy', () => {
let versioningConfig: IWithVersioningConfig = {
apiVersion: "1.0",
versioningStrategy: VersioningStrategy.QueryString
};
let versioningConfig: IWithVersioningConfig = {
apiVersion: '1.0',
versioningStrategy: VersioningStrategy.QueryString,
};

beforeAll(() => {
instance = withVersioning(axios, versioningConfig);
mock = new MockAdapter(instance);
});
beforeAll(() => {
instance = withVersioning(axios, versioningConfig);
mock = new MockAdapter(instance as AxiosInstance);
});

test('it should have the "apiVersion" as a query param in the response config', async () => {
mock.onGet(test_url).reply(status.OK, MOCK_RES);
test('it should have the "apiVersion" as a query param in the response config', async () => {
mock.onGet(test_url).reply(status.OK, MOCK_RES);

const res = await instance.get(test_url);
const { params } = res.config;
const res = await instance.get(test_url);
const { params } = res.config;

expect(params).toHaveProperty("api-version");
expect(params["api-version"]).toBe(versioningConfig.apiVersion);
});
expect(params).toHaveProperty('api-version');
expect(params['api-version']).toBe(versioningConfig.apiVersion);
});
});

describe('Testing correct response config of "MediaType" strategy', () => {
let versioningConfig: IWithVersioningConfig = {
apiVersion: "1.0",
versioningStrategy: VersioningStrategy.MediaType
};
let versioningConfig: IWithVersioningConfig = {
apiVersion: '1.0',
versioningStrategy: VersioningStrategy.MediaType,
};

beforeAll(() => {
instance = withVersioning(axios, versioningConfig);
mock = new MockAdapter(instance);
});
beforeAll(() => {
instance = withVersioning(axios, versioningConfig);
mock = new MockAdapter(instance as AxiosInstance);
});

test('it should have the "apiVersion" as an accept-param in the Accept header', async () => {
mock.onGet(test_url).reply(status.OK, MOCK_RES);
test('it should have the "apiVersion" as an accept-param in the Accept header', async () => {
mock.onGet(test_url).reply(status.OK, MOCK_RES);

const res = await instance.get(test_url);
const { headers } = res.config;
const res = await instance.get(test_url);
const { headers } = res.config;

expect(headers).toHaveProperty("Accept");
expect(headers["Accept"]).toMatch(`v=${versioningConfig.apiVersion}`);
});
expect(headers).toHaveProperty('Accept');
expect(headers['Accept']).toMatch(`v=${versioningConfig.apiVersion}`);
});
});

describe('Testing correct response config of "UrlPath" strategy', () => {
let versioningConfig: IWithVersioningConfig = {
apiVersion: "1",
versioningStrategy: VersioningStrategy.UrlPath
};
let versioningConfig: IWithVersioningConfig = {
apiVersion: '1',
versioningStrategy: VersioningStrategy.UrlPath,
};

const blank_test_url = test_url + "/v{apiVersion}";
const versioned_test_url = test_url + `/v${versioningConfig.apiVersion}`;
const blank_test_url = test_url + '/v{apiVersion}';
const versioned_test_url = test_url + `/v${versioningConfig.apiVersion}`;

beforeAll(() => {
instance = withVersioning(axios, versioningConfig);
mock = new MockAdapter(instance);
});
beforeAll(() => {
instance = withVersioning(axios, versioningConfig);
mock = new MockAdapter(instance as AxiosInstance);
});

test('it should have the "apiVersion" as a url param in the url', async () => {
mock.onGet(versioned_test_url).reply(status.OK, MOCK_RES);
test('it should have the "apiVersion" as a url param in the url', async () => {
mock.onGet(versioned_test_url).reply(status.OK, MOCK_RES);

const res = await instance.get(blank_test_url);
const { url } = res.config;
const res = await instance.get(blank_test_url);
const { url } = res.config;

expect(url).toBe(versioned_test_url);
});
expect(url).toBe(versioned_test_url);
});
});

0 comments on commit 879718f

Please sign in to comment.