acacode / acacode/swagger-typescript-api

If an error occurs in a web call then it throws 'Response', it would be nice to be able to customize this behaviour

Open
#95 2 comments 6 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
4.1k
Forks
436
PR merge metrics
No merged PRs in 30d

Description

Converting the http response into the return data or an error takes place in

return fetch(requestUrl, requestOptions).then(async (response) => {
const data = await this.safeParseResponse(response);
if (!response.ok) throw data;
return data;
});

If this could be placed into a protected function, then its could be sub classed and overridden.

i.e.

```
return fetch(requestUrl, requestOptions).then(async (response) => {
return await this.handleResponse(response);
});
}

protected async handleResponse(response: Response): Promise> {
const data = await this.safeParseResponse(response);
if (!response.ok) throw data;
return data;
}
```

Note safeParseResponse also needs changing to protected, it may also be a good idea to export HttpResponse

Full Template.
```
export type RequestParams = Omit & {
secure?: boolean;
}

{{#hasQueryRoutes}}
export type RequestQueryParamsType = Record;
{{/hasQueryRoutes}}

interface ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> {
baseUrl?: string;
baseApiParams?: RequestParams;
securityWorker?: (securityData: SecurityDataType) => RequestParams;
}

{{#generateResponses}}
/** Overrided Promise type. Needs for additional typings of `.catch` callback */
type TPromise = Omit, "then" | "catch"> & {
then(onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: RejectType) => TResult2 | PromiseLike) | undefined | null): TPromise;
catch(onrejected?: ((reason: RejectType) => TResult | PromiseLike) | undefined | null): TPromise;
}
{{/generateResponses}}

interface HttpResponse extends Response {
data: D | null;
error: E | null;
}

enum BodyType {
Json,
{{#hasFormDataRoutes}}
FormData,
{{/hasFormDataRoutes}}
}

class HttpClient<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> {
public baseUrl: string = "{{apiConfig.baseUrl}}";
private securityData: SecurityDataType = (null as any);
private securityWorker: null | ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}>["securityWorker"] = null;

private baseApiParams: RequestParams = {
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
}

constructor(apiConfig: ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> = {}) {
Object.assign(this, apiConfig);
}

public setSecurityData = (data: SecurityDataType) => {
this.securityData = data
}

{{#hasQueryRoutes}}
private addQueryParam(query: RequestQueryParamsType, key: string) {
return encodeURIComponent(key) + "=" + encodeURIComponent(Array.isArray(query[key]) ? query[key].join(",") : query[key])
}

protected addQueryParams(rawQuery?: RequestQueryParamsType): string {
const query = rawQuery || {};
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
return keys.length ? `?${keys.map(key =>
typeof query[key] === "object" && !Array.isArray(query[key]) ?
this.addQueryParams(query[key] as object).substring(1) :
this.addQueryParam(query, key)).join("&")
}` : "";
}
{{/hasQueryRoutes}}

private bodyFormatters: Record any> = {
[BodyType.Json]: JSON.stringify,
{{#hasFormDataRoutes}}
[BodyType.FormData]: (input: any) =>
Object.keys(input).reduce((data, key) => {
data.append(key, input[key]);
return data;
}, new FormData()),
{{/hasFormDataRoutes}}
}

private mergeRequestOptions(params: RequestParams, securityParams?: RequestParams): RequestParams {
return {
...this.baseApiParams,
...params,
...(securityParams || {}),
headers: {
...(this.baseApiParams.headers || {}),
...(params.headers || {}),
...((securityParams && securityParams.headers) || {})
}
}
}

protected safeParseResponse = (response: Response): Promise> => {
const r = response as HttpResponse;
r.data = null;
r.error = null;

return response
.json()
.then((data) => {
if (r.ok) {
r.data = data;
} else {
r.error = data;
}
return r;
})
.catch((e) => {
r.error = e;
return r;
});
}

public request = (
path: string,
method: string,
{ secure, ...params }: RequestParams = {},
body?: any,
bodyType?: BodyType,
secureByDefault?: boolean,
): {{#generateResponses}}TPromise>{{/generateResponses}}{{^generateResponses}}Promise>{{/generateResponses}} => {
const requestUrl = `${this.baseUrl}${path}`;
const secureOptions = (secureByDefault || secure) && this.securityWorker ? this.securityWorker(this.securityData) : {};
const requestOptions = {
...this.mergeRequestOptions(params, secureOptions),
method,
body: body ? this.bodyFormatters[bodyType || BodyType.Json](body) : null,
}

return fetch(requestUrl, requestOptions).then(async (response) => {
return await this.handleResponse(response);
});
}

protected async handleResponse(response: Response): Promise> {
const data = await this.safeParseResponse(response);
if (!response.ok) throw data;
return data;
}
}

```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.