Skip to content

Commit

Permalink
refactor: extracted static functions to a single file
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurfiorette committed Jan 3, 2022
1 parent 4c1e0ec commit c57916f
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 123 deletions.
75 changes: 10 additions & 65 deletions src/interceptors/request.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { AxiosRequestConfig, Method } from 'axios';
import { deferred } from 'fast-defer';
import type { CacheProperties } from '..';
import type {
AxiosCacheInstance,
CacheAxiosResponse,
Expand All @@ -9,11 +7,15 @@ import type {
import type {
CachedResponse,
CachedStorageValue,
LoadingStorageValue,
StaleStorageValue
LoadingStorageValue
} from '../storage/types';
import { Header } from '../util/headers';
import type { AxiosInterceptor } from './types';
import {
ConfigWithCache,
createValidateStatus,
isMethodIn,
setRevalidationHeaders
} from './util';

export class CacheRequestInterceptor<D>
implements AxiosInterceptor<CacheRequestConfig<D>>
Expand All @@ -36,7 +38,7 @@ export class CacheRequestInterceptor<D>

if (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
!CacheRequestInterceptor.isMethodAllowed(config.method!, config.cache)
!isMethodIn(config.method!, config.cache.methods)
) {
return config;
}
Expand Down Expand Up @@ -75,13 +77,10 @@ export class CacheRequestInterceptor<D>
});

if (cache.state === 'stale') {
//@ts-expect-error type infer couldn't resolve this
CacheRequestInterceptor.setRevalidationHeaders(cache, config);
setRevalidationHeaders(cache, config as ConfigWithCache<D>);
}

config.validateStatus = CacheRequestInterceptor.createValidateStatus(
config.validateStatus
);
config.validateStatus = createValidateStatus(config.validateStatus);

return config;
}
Expand Down Expand Up @@ -125,58 +124,4 @@ export class CacheRequestInterceptor<D>

return config;
};

static readonly isMethodAllowed = (
method: Method,
properties: Partial<CacheProperties>
): boolean => {
const requestMethod = method.toLowerCase();

for (const method of properties.methods || []) {
if (method.toLowerCase() === requestMethod) {
return true;
}
}

return false;
};

static readonly setRevalidationHeaders = <D>(
cache: StaleStorageValue,
config: CacheRequestConfig<D> & { cache: Partial<CacheProperties> }
): void => {
config.headers ||= {};

const { etag, modifiedSince } = config.cache;

if (etag) {
const etagValue = etag === true ? cache.data?.headers[Header.ETag] : etag;
if (etagValue) {
config.headers[Header.IfNoneMatch] = etagValue;
}
}

if (modifiedSince) {
config.headers[Header.IfModifiedSince] =
modifiedSince === true
? // If last-modified is not present, use the createdAt timestamp
cache.data.headers[Header.LastModified] ||
new Date(cache.createdAt).toUTCString()
: modifiedSince.toUTCString();
}
};

/**
* Creates a new validateStatus function that will use the one already used and also
* accept status code 304.
*/
static readonly createValidateStatus = (
oldValidate?: AxiosRequestConfig['validateStatus']
) => {
return (status: number): boolean => {
return oldValidate
? oldValidate(status) || status === 304
: (status >= 200 && status < 300) || status === 304;
};
};
}
39 changes: 3 additions & 36 deletions src/interceptors/response.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { AxiosResponse } from 'axios';
import type { AxiosCacheInstance, CacheAxiosResponse } from '../cache/axios';
import type { CacheProperties } from '../cache/cache';
import type { CachedResponse, CachedStorageValue } from '../storage/types';
import type { CachedStorageValue } from '../storage/types';
import { shouldCacheResponse } from '../util/cache-predicate';
import { Header } from '../util/headers';
import { updateCache } from '../util/update-cache';
import type { AxiosInterceptor } from './types';
import { setupCacheData } from './util';

export class CacheResponseInterceptor<R, D>
implements AxiosInterceptor<CacheAxiosResponse<R, D>>
Expand Down Expand Up @@ -85,7 +86,7 @@ export class CacheResponseInterceptor<R, D>
ttl = expirationTime || expirationTime === 0 ? expirationTime : ttl;
}

const data = CacheResponseInterceptor.setupCacheData(response, cache.data);
const data = setupCacheData(response, cache.data);

const newCache: CachedStorageValue = {
state: 'cached',
Expand Down Expand Up @@ -130,38 +131,4 @@ export class CacheResponseInterceptor<R, D>
...response
};
};

/**
* Creates the new date to the cache by the provided response. Also handles possible 304
* Not Modified by updating response properties.
*/
static readonly setupCacheData = <R, D>(
response: CacheAxiosResponse<R, D>,
cache?: CachedResponse
): CachedResponse => {
if (response.status === 304 && cache) {
// Set the cache information into the response object
response.cached = true;
response.data = cache.data;
response.status = cache.status;
response.statusText = cache.statusText;

// Update possible new headers
response.headers = {
...cache.headers,
...response.headers
};

// return the old cache
return cache;
}

// New Response
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers
};
};
}
92 changes: 92 additions & 0 deletions src/interceptors/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { Method } from 'axios';
import type { CachedResponse, CacheProperties, StaleStorageValue } from '..';
import type { CacheAxiosResponse, CacheRequestConfig } from '../cache/axios';
import { Header } from '../util/headers';

/**
* Creates a new validateStatus function that will use the one already used and also
* accept status code 304.
*/
export function createValidateStatus(
oldValidate?: CacheRequestConfig['validateStatus']
): (status: number) => boolean {
return oldValidate
? (status) => oldValidate(status) || status === 304
: (status) => (status >= 200 && status < 300) || status === 304;
}

/** Checks if the given method is in the methods array */
export function isMethodIn(requestMethod: Method, methodList: Method[] = []): boolean {
requestMethod = requestMethod.toLowerCase() as Lowercase<Method>;

for (const method of methodList) {
if (method.toLowerCase() === requestMethod) {
return true;
}
}

return false;
}

export type ConfigWithCache<D> = CacheRequestConfig<D> & {
cache: Partial<CacheProperties>;
};

export function setRevalidationHeaders<D>(
cache: StaleStorageValue,
config: ConfigWithCache<D>
): void {
config.headers ||= {};

const { etag, modifiedSince } = config.cache;

if (etag) {
const etagValue = etag === true ? cache.data?.headers[Header.ETag] : etag;
if (etagValue) {
config.headers[Header.IfNoneMatch] = etagValue;
}
}

if (modifiedSince) {
config.headers[Header.IfModifiedSince] =
modifiedSince === true
? // If last-modified is not present, use the createdAt timestamp
cache.data.headers[Header.LastModified] ||
new Date(cache.createdAt).toUTCString()
: modifiedSince.toUTCString();
}
}

/**
* Creates the new date to the cache by the provided response. Also handles possible 304
* Not Modified by updating response properties.
*/
export function setupCacheData<R, D>(
response: CacheAxiosResponse<R, D>,
cache?: CachedResponse
): CachedResponse {
if (response.status === 304 && cache) {
// Set the cache information into the response object
response.cached = true;
response.data = cache.data;
response.status = cache.status;
response.statusText = cache.statusText;

// Update possible new headers
response.headers = {
...cache.headers,
...response.headers
};

// return the old cache
return cache;
}

// New Response
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers
};
}
22 changes: 0 additions & 22 deletions test/interceptors/request.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { CacheRequestInterceptor } from '../../src/interceptors/request';
import { mockAxios } from '../mocks/axios';
import { sleep } from '../utils';

Expand Down Expand Up @@ -114,25 +113,4 @@ describe('test request interceptor', () => {
// nothing to use for revalidation
expect(response.cached).toBe(false);
});

it('tests validate-status function', async () => {
const { createValidateStatus } = CacheRequestInterceptor;

const def = createValidateStatus();
expect(def(200)).toBe(true);
expect(def(345)).toBe(false);
expect(def(304)).toBe(true);

const only200 = createValidateStatus((s) => s >= 200 && s < 300);
expect(only200(200)).toBe(true);
expect(only200(299)).toBe(true);
expect(only200(304)).toBe(true);
expect(only200(345)).toBe(false);

const randomValue = createValidateStatus((s) => s >= 405 && s <= 410);
expect(randomValue(200)).toBe(false);
expect(randomValue(404)).toBe(false);
expect(randomValue(405)).toBe(true);
expect(randomValue(304)).toBe(true);
});
});
22 changes: 22 additions & 0 deletions test/interceptors/util.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createValidateStatus } from '../../src/interceptors/util';

describe('test util functions', () => {
it('tests validate-status function', async () => {
const def = createValidateStatus();
expect(def(200)).toBe(true);
expect(def(345)).toBe(false);
expect(def(304)).toBe(true);

const only200 = createValidateStatus((s) => s >= 200 && s < 300);
expect(only200(200)).toBe(true);
expect(only200(299)).toBe(true);
expect(only200(304)).toBe(true);
expect(only200(345)).toBe(false);

const randomValue = createValidateStatus((s) => s >= 405 && s <= 410);
expect(randomValue(200)).toBe(false);
expect(randomValue(404)).toBe(false);
expect(randomValue(405)).toBe(true);
expect(randomValue(304)).toBe(true);
});
});

0 comments on commit c57916f

Please sign in to comment.