Skip to content

Commit 06f5c45

Browse files
Ivan KamkinIvan Kamkin
authored andcommitted
Update templates
1 parent 540d5a6 commit 06f5c45

4 files changed

Lines changed: 245 additions & 248 deletions

File tree

codegen/Templates/nodejs/api.mustache

Lines changed: 242 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Configuration } from './Configuration';
2-
import { HttpClient, HttpOptions, HttpResponse, HttpResult } from './httpClient';
32
import { Multipart, RequestFile, FormParamsType } from './multipart';
43

54
export * from './models';
@@ -9,6 +8,243 @@ import { {{#models}} {{#model}}{{classname}},{{/model}} {{/models}} } from './mo
98
import { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} {{operationIdCamelCase}}RequestWrapper, {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} } from './models';
109

1110

11+
type StringKeyWithStringValue = Record<string, string>;
12+
13+
type ApiRequestOptions = {
14+
uri: string;
15+
body?: any;
16+
encoding?: BufferEncoding | null;
17+
form?: StringKeyWithStringValue;
18+
headers?: StringKeyWithStringValue;
19+
json?: boolean;
20+
method?: string;
21+
qs?: StringKeyWithStringValue;
22+
};
23+
24+
type ApiResponse = {
25+
statusCode: number;
26+
statusMessage: string;
27+
headers: NodeJS.Dict<string | string[]>;
28+
body: any;
29+
};
30+
31+
type ApiResult<T = any> = {
32+
response: ApiResponse;
33+
body: T;
34+
};
35+
36+
type ApiRejectType = {
37+
response: ApiResponse | null;
38+
errorResponse: ApiErrorResponse | null;
39+
error: Error;
40+
};
41+
42+
interface FetchHeaders {
43+
forEach(callback: (value: string, key: string) => void): void;
44+
}
45+
46+
interface FetchResponse {
47+
status: number;
48+
statusText: string;
49+
headers: FetchHeaders;
50+
ok: boolean;
51+
arrayBuffer(): Promise<ArrayBuffer>;
52+
}
53+
54+
interface FetchRequestInit {
55+
method?: string;
56+
headers?: StringKeyWithStringValue;
57+
body?: any;
58+
}
59+
60+
type Fetcher = (input: string | URL, init?: FetchRequestInit) => Promise<FetchResponse>;
61+
62+
export class ApiClient {
63+
public requestAsync(options: ApiRequestOptions): Promise<ApiResult> {
64+
const url: URL = options.qs
65+
? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri)
66+
: new URL(options.uri);
67+
68+
const requestBody = this.buildRequestBody(options);
69+
70+
const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';
71+
72+
const requestOptions: FetchRequestInit = {
73+
method: options.method || 'GET',
74+
headers: options.headers,
75+
};
76+
77+
if (requestBody) {
78+
requestOptions.body = requestBody;
79+
}
80+
81+
return this.doFetchRequest(url, requestOptions, responseEncoding);
82+
}
83+
84+
private buildRequestBody(options: ApiRequestOptions) {
85+
let requestBody = options.body;
86+
if (options.form) {
87+
// Override requestBody for form with form content
88+
requestBody = new URLSearchParams(options.form).toString();
89+
options.headers = Object.assign(
90+
{
91+
'Content-Type': 'application/x-www-form-urlencoded',
92+
},
93+
options.headers
94+
);
95+
}
96+
if (options.json) {
97+
// Override requestBody with JSON value
98+
requestBody = JSON.stringify(options.body);
99+
options.headers = Object.assign(
100+
{
101+
'Content-Type': 'application/json',
102+
},
103+
options.headers
104+
);
105+
}
106+
return requestBody;
107+
}
108+
109+
private async doFetchRequest(
110+
url: URL,
111+
requestOptions: FetchRequestInit,
112+
responseEncoding: BufferEncoding | null
113+
): Promise<ApiResult> {
114+
const fetcher = this.getFetch();
115+
let response: FetchResponse;
116+
try {
117+
response = await fetcher(url.toString(), requestOptions);
118+
} catch (error) {
119+
return Promise.reject({
120+
response: null,
121+
error: this.normalizeFetchError(error),
122+
errorResponse: null,
123+
});
124+
}
125+
126+
const respBody = await this.readResponseBody(response, responseEncoding);
127+
const responseHeaders = this.toHeaderDict(response.headers);
128+
129+
const httpResponse: ApiResponse = {
130+
statusCode: response.status,
131+
statusMessage: response.statusText,
132+
headers: responseHeaders,
133+
body: respBody,
134+
};
135+
136+
if (response.ok) {
137+
return {
138+
response: httpResponse,
139+
body: respBody,
140+
};
141+
}
142+
143+
const rejectObject: ApiRejectType = {
144+
response: httpResponse,
145+
error: new Error(`Error on '${url}': ${response.status} ${response.statusText}`),
146+
errorResponse: null,
147+
};
148+
let errorResponse = null;
149+
try {
150+
errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse;
151+
} catch (parseError) {}
152+
153+
if (errorResponse) {
154+
rejectObject.errorResponse = errorResponse;
155+
} else {
156+
rejectObject.error.message += `. ${respBody}`;
157+
}
158+
159+
return Promise.reject(rejectObject);
160+
}
161+
162+
private async readResponseBody(
163+
response: FetchResponse,
164+
responseEncoding: BufferEncoding | null
165+
): Promise<string | Buffer> {
166+
const arrayBuffer = await response.arrayBuffer();
167+
const buffer = Buffer.from(arrayBuffer);
168+
169+
if (responseEncoding === null) {
170+
return buffer;
171+
}
172+
173+
return buffer.toString(responseEncoding);
174+
}
175+
176+
private toHeaderDict(headers: FetchHeaders): NodeJS.Dict<string | string[]> {
177+
const normalizedHeaders: NodeJS.Dict<string | string[]> = {};
178+
179+
headers.forEach((value, key) => {
180+
const existing = normalizedHeaders[key];
181+
if (existing === undefined) {
182+
normalizedHeaders[key] = value;
183+
return;
184+
}
185+
186+
if (Array.isArray(existing)) {
187+
existing.push(value);
188+
normalizedHeaders[key] = existing;
189+
return;
190+
}
191+
192+
normalizedHeaders[key] = [existing, value];
193+
});
194+
195+
return normalizedHeaders;
196+
}
197+
198+
private getFetch(): Fetcher {
199+
const fetcher = (globalThis as { fetch?: Fetcher }).fetch;
200+
if (!fetcher) {
201+
throw new Error('Global fetch API is not available. Please use Node.js 18+.');
202+
}
203+
204+
return fetcher;
205+
}
206+
207+
private normalizeFetchError(error: unknown): Error {
208+
if (error instanceof Error) {
209+
const mutableError = error as Error & { code?: string; cause?: unknown; name: string };
210+
let normalizedCode = mutableError.code;
211+
212+
if (!normalizedCode) {
213+
const cause = mutableError.cause;
214+
if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) {
215+
const code = (cause as { code?: string }).code;
216+
if (code) {
217+
normalizedCode = String(code);
218+
}
219+
}
220+
}
221+
222+
if (!normalizedCode) {
223+
normalizedCode = mutableError.name || 'FETCH_ERROR';
224+
}
225+
226+
try {
227+
if (!mutableError.code) {
228+
mutableError.code = normalizedCode;
229+
}
230+
} catch (assignError) {}
231+
232+
if (mutableError.code) {
233+
return mutableError;
234+
}
235+
236+
const wrapped = new Error(mutableError.message);
237+
wrapped.name = mutableError.name;
238+
(wrapped as { code?: string }).code = normalizedCode;
239+
return wrapped;
240+
}
241+
242+
const wrapped = new Error(String(error));
243+
(wrapped as { code?: string }).code = 'FETCH_ERROR';
244+
return wrapped;
245+
}
246+
}
247+
12248
let primitives = ['string', 'boolean', 'double', 'integer', 'long', 'float', 'number', 'any'];
13249

14250
class ObjectSerializer {
@@ -184,11 +420,11 @@ export class {{classname}} {
184420
'x-aspose-client-version': '{{npmVersion}}'
185421
};
186422
protected _configuration: Configuration;
187-
private _client: HttpClient;
423+
private _client: ApiClient;
188424

189425
constructor(configuration: Configuration) {
190426
this._configuration = configuration;
191-
this._client = new HttpClient();
427+
this._client = new ApiClient();
192428
}
193429

194430
{{#operation}}
@@ -200,7 +436,7 @@ export class {{classname}} {
200436
{{/summary}}
201437
* @param request {{operationIdCamelCase}}RequestWrapper
202438
*/
203-
public async {{nickname}}(request: {{operationIdCamelCase}}RequestWrapper): Promise<{ response: HttpResponse; {{#returnType}}body: {{{returnType}}}; {{/returnType}}{{^returnType}}body?: any; {{/returnType}} }> {
439+
public async {{nickname}}(request: {{operationIdCamelCase}}RequestWrapper): Promise<{ response: ApiResponse; {{#returnType}}body: {{{returnType}}}; {{/returnType}}{{^returnType}}body?: any; {{/returnType}} }> {
204440
const requestPath =
205441
this._configuration.getApiBaseUrl() +
206442
'{{{path}}}'{{#pathParams}}.replace(
@@ -247,7 +483,7 @@ export class {{classname}} {
247483
}
248484
{{/isFile}}
249485
{{/formParams}}
250-
const requestOptions: HttpOptions = {
486+
const requestOptions: ApiRequestOptions = {
251487
method: '{{httpMethod}}',
252488
qs: queryParameters,
253489
headers: headerParams,
@@ -272,7 +508,7 @@ export class {{classname}} {
272508

273509
await this._configuration.authentication.applyToRequestAsync(requestOptions);
274510

275-
const result: HttpResult = await this._client.requestAsync(requestOptions);
511+
const result: ApiResult = await this._client.requestAsync(requestOptions);
276512

277513
{{#returnType}}
278514
return {

0 commit comments

Comments
 (0)