Skip to content

Commit

Permalink
move API interceptor setup to OpenAPI module
Browse files Browse the repository at this point in the history
  • Loading branch information
fergusean committed Oct 13, 2023
1 parent ed135da commit 4d42858
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 130 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@signal24/vue-foundation",
"type": "module",
"version": "4.7.4",
"version": "4.7.5",
"description": "Common components, directives, and helpers for Vue 3 apps",
"module": "./dist/vue-foundation.es.js",
"bin": {
Expand Down Expand Up @@ -46,7 +46,7 @@
"devDependencies": {
"@nabla/vite-plugin-eslint": "^1.5.0",
"@rushstack/eslint-patch": "^1.3.2",
"@signal24/openapi-client-codegen": "^1.0.1",
"@signal24/openapi-client-codegen": "^1.0.2",
"@tsconfig/node18": "^18.2.0",
"@types/jsdom": "^21.1.1",
"@types/lodash": "^4.14.196",
Expand Down
134 changes: 10 additions & 124 deletions src/helpers/openapi.ts
Original file line number Diff line number Diff line change
@@ -1,130 +1,16 @@
import { UserError } from './error';
import { installOpenApiClientInterceptors, isOpenApiError } from '@signal24/openapi-client-codegen';

interface IRequestOptions {
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
readonly url: string;
readonly path?: Record<string, any>;
readonly cookies?: Record<string, any>;
readonly headers?: Record<string, any>;
readonly query?: Record<string, any>;
readonly formData?: Record<string, any>;
readonly body?: any;
readonly mediaType?: string;
readonly responseHeader?: string;
readonly errors?: Record<number, string>;
}

interface IBaseHttpRequest {
request<T>(options: IRequestOptions): ICancelablePromise<T>;
}

export interface IApiClient {
request: IBaseHttpRequest;
}

export interface IApiError extends Error {
status: number;
statusText: string;
body: any;
}

export declare class ICancelablePromise<T = any> {
constructor(executor: (resolve: (value: any) => void, reject: (reason: any) => void, onCancel: (cancel: () => void) => void) => void);
then<TResult1 = any, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
finally(onfinally?: (() => void) | undefined | null): Promise<T>;
cancel(): void;
}

interface IWrappedApiClientOptions<P extends ICancelablePromise = ICancelablePromise, Arguments extends unknown[] = any[]> {
apiClient: IApiClient;
wrapper?: (options: IRequestOptions, fn: (options: IRequestOptions) => P) => P;
onRequest?: (options: IRequestOptions) => IRequestOptions;
onError?: (err: Error, options: IRequestOptions) => Error | null | void;
afterRequest?: (options: IRequestOptions) => void;
CancelablePromise: new (...arguments_: Arguments) => P;
}
import { UserError } from '.';

export function isApiError(err: any): err is IApiError {
return err instanceof Error && 'status' in err && 'body' in err;
}

export function installApiClientInterceptors({ apiClient, wrapper, onRequest, onError, afterRequest, CancelablePromise }: IWrappedApiClientOptions) {
const originalRequest = apiClient.request.request.bind(apiClient.request);
const resolvedWrapper = wrapper ?? ((options, fn) => fn(options));
apiClient.request.request = (options: IRequestOptions) => {
return resolvedWrapper(options, options => {
options = rewriteOptionsForFileUpload(options);

if (onRequest) {
options = onRequest(options);
export function installApiClientInterceptors(clientOptions: Parameters<typeof installOpenApiClientInterceptors>[0]) {
installOpenApiClientInterceptors({
...clientOptions,
onError(err, options) {
if (isOpenApiError(err) && err.status === 422 && typeof err.body === 'object' && 'error' in err.body) {
err = new UserError(err.body.error);
}

return new CancelablePromise((resolve: (value: any) => void, reject: (err: any) => void, onCancel: (handler: () => void) => void) => {
const promise = originalRequest(options);
onCancel(promise.cancel);
promise
.then(resolve)
.catch(err => {
if (isApiError(err) && typeof err.body === 'object' && 'error' in err.body) {
if (err.status === 422) {
return reject(new UserError(err.body.error));
}

err.message = `${err.body.error} (${err.status})`;
}
if (onError) {
const handlerResult = onError(err, options);
if (handlerResult === null) {
return;
}
if (handlerResult instanceof Error) {
return reject(handlerResult);
}
}
reject(err);
})
.finally(() => afterRequest?.(options));
});
});
};
}

export class FileUploadRequest {
constructor(blob: Blob) {
this.blob = blob;
}

validator = null;
lastModifiedDate = null;
size = 0;
path = '';
name = '';
type = '';
blob!: Blob;
}

function rewriteOptionsForFileUpload(options: IRequestOptions): IRequestOptions {
const hasFileUpload = typeof options.body === 'object' && Object.values(options.body).some(v => v instanceof FileUploadRequest);
if (!hasFileUpload) return options;

const formData: Record<string, any> = {};
const jsonBody: Record<string, any> = {};
for (const [key, value] of Object.entries(options.body)) {
if (value instanceof FileUploadRequest) {
formData[key] = value.blob;
} else {
jsonBody[key] = value;
clientOptions.onError?.(err, options);
}
}
formData._payload = new Blob([JSON.stringify(jsonBody)], { type: 'application/json' });

return {
...options,
body: undefined,
formData
};
});
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -444,10 +444,10 @@
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==

"@signal24/openapi-client-codegen@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@signal24/openapi-client-codegen/-/openapi-client-codegen-1.0.1.tgz#3053a5fbe74a76b2885bc06d57b540fb71acc544"
integrity sha512-7qUPoAT6i9bX+a/ZPDUg4iKtEZJOKhOHisP3ggQG/1lsPn7wOT8CJhNXlMCcKWUweT3+gvf1pH6XVBzzGNZdcA==
"@signal24/openapi-client-codegen@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@signal24/openapi-client-codegen/-/openapi-client-codegen-1.0.2.tgz#a25be64d8e1427fa298aab77ef862b1af97839a4"
integrity sha512-UyufI1ka/s4p+UyG3ayak3VvJcfYKms3zfdA5Ydwx3VYnMwhamGxVrk8huC2l6yTfjxOeD9xodPMBzThj+gIHQ==
dependencies:
openapi-typescript-codegen "^0.25.0"

Expand Down

0 comments on commit 4d42858

Please sign in to comment.