Skip to content

Commit e273b2f

Browse files
author
Justin Calleja
committed
chore: add nextjs_fetch client
1 parent 6a93331 commit e273b2f

16 files changed

+262
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ $ openapi --help
3838
-V, --version output the version number
3939
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
4040
-o, --output <value> Output directory (required)
41-
-c, --client <value> HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch")
41+
-c, --client <value> HTTP client to generate [nextjs_fetch, fetch, xhr, node, axios, angular] (default: "fetch")
4242
--name <value> Custom client class name
4343
--useOptions Use options instead of arguments
4444
--useUnionTypes Use union types instead of enums

bin/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const params = program
1212
.version(pkg.version)
1313
.requiredOption('-i, --input <value>', 'OpenAPI specification, can be a path, url or string content (required)')
1414
.requiredOption('-o, --output <value>', 'Output directory (required)')
15-
.option('-c, --client <value>', 'HTTP client to generate [fetch, xhr, node, axios, angular]', 'fetch')
15+
.option('-c, --client <value>', 'HTTP client to generate [nextjs_fetch, fetch, xhr, node, axios, angular]', 'fetch')
1616
.option('--name <value>', 'Custom client class name')
1717
.option('--useOptions', 'Use options instead of arguments')
1818
.option('--useUnionTypes', 'Use union types instead of enums')

src/HttpClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export enum HttpClient {
22
FETCH = 'fetch',
33
XHR = 'xhr',
4+
NEXTJS_FETCH = 'nextjs_fetch',
45
NODE = 'node',
56
AXIOS = 'axios',
67
ANGULAR = 'angular',

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type Options = {
3636
* service layer, etc.
3737
* @param input The relative ___location of the OpenAPI spec
3838
* @param output The relative ___location of the output directory
39-
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
39+
* @param httpClient The selected httpClient (nextjs_fetch, fetch, xhr, node or axios)
4040
* @param clientName Custom client class name
4141
* @param useOptions Use options or arguments functions
4242
* @param useUnionTypes Use union types instead of enums

src/templates/core/ApiRequestOptions.hbs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
{{>header}}
22

3+
4+
{{#equals @root.httpClient 'nextjs_fetch'}}
5+
export type ApiRequestOptions = {
6+
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
7+
readonly url: string;
8+
readonly path?: Record<string, any>;
9+
readonly cookies?: Record<string, any>;
10+
readonly headers?: Record<string, any>;
11+
readonly query?: Record<string, any>;
12+
readonly formData?: Record<string, any>;
13+
readonly body?: any;
14+
readonly mediaType?: string;
15+
readonly responseHeader?: string;
16+
readonly errors?: Record<number, string>;
17+
readonly cache?: RequestInit['cache']
18+
readonly next?: { revalidate: number }
19+
};
20+
{{else}}
321
export type ApiRequestOptions = {
422
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
523
readonly url: string;
@@ -13,3 +31,4 @@ export type ApiRequestOptions = {
1331
readonly responseHeader?: string;
1432
readonly errors?: Record<number, string>;
1533
};
34+
{{/equals}}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise<Headers> => {
2+
const token = await resolve(options, config.TOKEN);
3+
const username = await resolve(options, config.USERNAME);
4+
const password = await resolve(options, config.PASSWORD);
5+
const additionalHeaders = await resolve(options, config.HEADERS);
6+
7+
const headers = Object.entries({
8+
Accept: 'application/json',
9+
...additionalHeaders,
10+
...options.headers,
11+
})
12+
.filter(([_, value]) => isDefined(value))
13+
.reduce((headers, [key, value]) => ({
14+
...headers,
15+
[key]: String(value),
16+
}), {} as Record<string, string>);
17+
18+
if (isStringWithValue(token)) {
19+
headers['Authorization'] = `Bearer ${token}`;
20+
}
21+
22+
if (isStringWithValue(username) && isStringWithValue(password)) {
23+
const credentials = base64(`${username}:${password}`);
24+
headers['Authorization'] = `Basic ${credentials}`;
25+
}
26+
27+
if (options.body) {
28+
if (options.mediaType) {
29+
headers['Content-Type'] = options.mediaType;
30+
} else if (isBlob(options.body)) {
31+
headers['Content-Type'] = options.body.type || 'application/octet-stream';
32+
} else if (isString(options.body)) {
33+
headers['Content-Type'] = 'text/plain';
34+
} else if (!isFormData(options.body)) {
35+
headers['Content-Type'] = 'application/json';
36+
}
37+
}
38+
39+
return new Headers(headers);
40+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const getRequestBody = (options: ApiRequestOptions): any => {
2+
if (options.body !== undefined) {
3+
if (options.mediaType?.includes('/json')) {
4+
return JSON.stringify(options.body)
5+
} else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) {
6+
return options.body;
7+
} else {
8+
return JSON.stringify(options.body);
9+
}
10+
}
11+
return undefined;
12+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const getResponseBody = async (response: Response): Promise<any> => {
2+
if (response.status !== 204) {
3+
try {
4+
const contentType = response.headers.get('Content-Type');
5+
if (contentType) {
6+
const jsonTypes = ['application/json', 'application/problem+json']
7+
const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type));
8+
if (isJSON) {
9+
return await response.json();
10+
} else {
11+
return await response.text();
12+
}
13+
}
14+
} catch (error) {
15+
console.error(error);
16+
}
17+
}
18+
return undefined;
19+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => {
2+
if (responseHeader) {
3+
const content = response.headers.get(responseHeader);
4+
if (isString(content)) {
5+
return content;
6+
}
7+
}
8+
return undefined;
9+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{{>header}}
2+
3+
import { ApiError } from './ApiError';
4+
import type { ApiRequestOptions } from './ApiRequestOptions';
5+
import type { ApiResult } from './ApiResult';
6+
import { CancelablePromise } from './CancelablePromise';
7+
import type { OnCancel } from './CancelablePromise';
8+
import type { OpenAPIConfig } from './OpenAPI';
9+
10+
{{>functions/isDefined}}
11+
12+
13+
{{>functions/isString}}
14+
15+
16+
{{>functions/isStringWithValue}}
17+
18+
19+
{{>functions/isBlob}}
20+
21+
22+
{{>functions/isFormData}}
23+
24+
25+
{{>functions/base64}}
26+
27+
28+
{{>functions/getQueryString}}
29+
30+
31+
{{>functions/getUrl}}
32+
33+
34+
{{>functions/getFormData}}
35+
36+
37+
{{>functions/resolve}}
38+
39+
40+
{{>fetch/getHeaders}}
41+
42+
43+
{{>fetch/getRequestBody}}
44+
45+
46+
{{>fetch/sendRequest}}
47+
48+
49+
{{>fetch/getResponseHeader}}
50+
51+
52+
{{>fetch/getResponseBody}}
53+
54+
55+
{{>functions/catchErrorCodes}}
56+
57+
58+
/**
59+
* Request method
60+
* @param config The OpenAPI configuration object
61+
* @param options The request options from the service
62+
* @returns CancelablePromise<T>
63+
* @throws ApiError
64+
*/
65+
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise<T> => {
66+
return new CancelablePromise(async (resolve, reject, onCancel) => {
67+
try {
68+
const url = getUrl(config, options);
69+
const formData = getFormData(options);
70+
const body = getRequestBody(options);
71+
const headers = await getHeaders(config, options);
72+
73+
if (!onCancel.isCancelled) {
74+
const response = await sendRequest(config, options, url, body, formData, headers, onCancel);
75+
const responseBody = await getResponseBody(response);
76+
const responseHeader = getResponseHeader(response, options.responseHeader);
77+
78+
const result: ApiResult = {
79+
url,
80+
ok: response.ok,
81+
status: response.status,
82+
statusText: response.statusText,
83+
body: responseHeader ?? responseBody,
84+
};
85+
86+
catchErrorCodes(options, result);
87+
88+
resolve(result.body);
89+
}
90+
} catch (error) {
91+
reject(error);
92+
}
93+
});
94+
};

0 commit comments

Comments
 (0)