Skip to content

Commit 93df966

Browse files
authored
Refactoring fetch-based code after PR, again #541, #582 (#723)
As a result of neglecting #582, it became impossible to merge, so I recreated the Pull-request. Compared to the last time, we developed it by narrowing down the changes to make it less likely to cause conflicts. ## Development steps 1. make axios as a optionalDependency 2. move http.ts as http-axios.ts 3. create new http-fetch.ts as a result, legacy API will depends on the `http-axios.ts`. and so openapi based clients are depend on `http-fetch.ts`.
1 parent f0682e4 commit 93df966

File tree

21 files changed

+573
-123
lines changed

21 files changed

+573
-123
lines changed

generator/src/main/resources/line-bot-sdk-nodejs-generator/api-single.pebble

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import * as Types from "../../types";
1111
import {ensureJSON} from "../../utils";
1212
import {Readable} from "stream";
1313

14-
import HTTPClient from "../../http";
15-
import {AxiosResponse} from "axios";
14+
import HTTPFetchClient from "../../http-fetch";
1615

1716
// ===============================================
1817
// This file is autogenerated - Please do not edit
@@ -28,13 +27,13 @@ interface httpClientConfig {
2827

2928

3029
export class {{operations.classname}} {
31-
private httpClient: HTTPClient;
30+
private httpClient: HTTPFetchClient;
3231

3332
constructor(config: httpClientConfig) {
3433
if (!config.baseURL) {
3534
config.baseURL = '{{endpoint(operations.classname)}}';
3635
}
37-
this.httpClient = new HTTPClient({
36+
this.httpClient = new HTTPFetchClient({
3837
defaultHeaders: {
3938
{% if authMethods != null -%}
4039
Authorization: "Bearer " + config.channelAccessToken,
@@ -45,14 +44,14 @@ export class {{operations.classname}} {
4544
});
4645
}
4746

48-
private parseHTTPResponse(response: AxiosResponse) {
47+
private async parseHTTPResponse(response: Response) {
4948
const { LINE_REQUEST_ID_HTTP_HEADER_NAME } = Types;
50-
let resBody = {
51-
...response.data,
49+
let resBody: Record<string, any> = {
50+
...await response.json(),
5251
};
53-
if (response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME]) {
52+
if (response.headers.get(LINE_REQUEST_ID_HTTP_HEADER_NAME)) {
5453
resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] =
55-
response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME];
54+
response.headers.get(LINE_REQUEST_ID_HTTP_HEADER_NAME);
5655
}
5756
return resBody;
5857
}

generator/src/main/resources/line-bot-sdk-nodejs-generator/api_test.pebble

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ describe("{{operations.classname}}", () => {
8585
{% endif -%}
8686
{% endfor %}
8787
);
88+
{% if op.isResponseFile %}
89+
res.destroy();
90+
{% endif %}
8891

8992
equal(requestCount, 1);
9093
});

lib/channel-access-token/api/channelAccessTokenClient.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ import * as Types from "../../types";
2222
import { ensureJSON } from "../../utils";
2323
import { Readable } from "stream";
2424

25-
import HTTPClient from "../../http";
26-
import { AxiosResponse } from "axios";
25+
import HTTPFetchClient from "../../http-fetch";
2726

2827
// ===============================================
2928
// This file is autogenerated - Please do not edit
@@ -35,27 +34,28 @@ interface httpClientConfig {
3534
}
3635

3736
export class ChannelAccessTokenClient {
38-
private httpClient: HTTPClient;
37+
private httpClient: HTTPFetchClient;
3938

4039
constructor(config: httpClientConfig) {
4140
if (!config.baseURL) {
4241
config.baseURL = "https://api.line.me";
4342
}
44-
this.httpClient = new HTTPClient({
43+
this.httpClient = new HTTPFetchClient({
4544
defaultHeaders: {},
4645
responseParser: this.parseHTTPResponse.bind(this),
4746
baseURL: config.baseURL,
4847
});
4948
}
5049

51-
private parseHTTPResponse(response: AxiosResponse) {
50+
private async parseHTTPResponse(response: Response) {
5251
const { LINE_REQUEST_ID_HTTP_HEADER_NAME } = Types;
53-
let resBody = {
54-
...response.data,
52+
let resBody: Record<string, any> = {
53+
...(await response.json()),
5554
};
56-
if (response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME]) {
57-
resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] =
58-
response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME];
55+
if (response.headers.get(LINE_REQUEST_ID_HTTP_HEADER_NAME)) {
56+
resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] = response.headers.get(
57+
LINE_REQUEST_ID_HTTP_HEADER_NAME,
58+
);
5959
}
6060
return resBody;
6161
}

lib/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Readable } from "stream";
2-
import HTTPClient from "./http";
2+
import HTTPClient from "./http-axios";
33
import * as Types from "./types";
44
import { AxiosRequestConfig, AxiosResponse } from "axios";
55
import { createMultipartFormData, ensureJSON, toArray } from "./utils";

lib/http.ts renamed to lib/http-axios.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ import * as qs from "querystring";
1111

1212
const pkg = require("../package.json");
1313

14-
export interface RequestFile {
15-
data: Blob;
16-
contentType: string;
17-
}
18-
1914
interface httpClientConfig extends Partial<AxiosRequestConfig> {
2015
baseURL?: string;
2116
defaultHeaders?: any;
@@ -24,7 +19,7 @@ interface httpClientConfig extends Partial<AxiosRequestConfig> {
2419

2520
export default class HTTPClient {
2621
private instance: AxiosInstance;
27-
private config: httpClientConfig;
22+
private readonly config: httpClientConfig;
2823

2924
constructor(config: httpClientConfig = {}) {
3025
this.config = config;

lib/http-fetch.ts

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { Readable } from "stream";
2+
import { HTTPError } from "./exceptions";
3+
import * as qs from "querystring";
4+
5+
const pkg = require("../package.json");
6+
export interface FetchRequestConfig {
7+
headers?: Record<string, string>;
8+
}
9+
10+
interface httpFetchClientConfig {
11+
baseURL: string;
12+
defaultHeaders: Record<string, string>;
13+
responseParser: <T>(res: Response) => Promise<T>;
14+
}
15+
16+
export default class HTTPFetchClient {
17+
private readonly baseURL: string;
18+
private readonly defaultHeaders: Record<string, string>;
19+
private readonly responseParser: <T>(res: Response) => Promise<T>;
20+
21+
constructor(config: httpFetchClientConfig) {
22+
this.baseURL = config.baseURL;
23+
this.defaultHeaders = {
24+
"User-Agent": `${pkg.name}/${pkg.version}`,
25+
...config.defaultHeaders,
26+
};
27+
this.responseParser = config.responseParser;
28+
}
29+
30+
public async get<T>(url: string, params?: any): Promise<T> {
31+
const requestUrl = new URL(url, this.baseURL);
32+
if (params) {
33+
requestUrl.search = qs.stringify(params);
34+
}
35+
const response = await fetch(requestUrl, {
36+
headers: this.defaultHeaders,
37+
});
38+
this.checkResponseStatus(response);
39+
return response.json();
40+
}
41+
42+
public async getStream(url: string, params?: any): Promise<Readable> {
43+
const requestUrl = new URL(url, this.baseURL);
44+
if (params) {
45+
requestUrl.search = new URLSearchParams(params).toString();
46+
}
47+
const response = await fetch(requestUrl, {
48+
headers: this.defaultHeaders,
49+
});
50+
const reader = response.body.getReader();
51+
return new Readable({
52+
async read() {
53+
const { done, value } = await reader.read();
54+
if (done) {
55+
this.push(null);
56+
} else {
57+
this.push(Buffer.from(value));
58+
}
59+
},
60+
});
61+
}
62+
63+
public async post<T>(
64+
url: string,
65+
body?: any,
66+
config?: Partial<FetchRequestConfig>,
67+
): Promise<T> {
68+
const requestUrl = new URL(url, this.baseURL);
69+
const response = await fetch(requestUrl, {
70+
method: "POST",
71+
headers: {
72+
"Content-Type": "application/json",
73+
...this.defaultHeaders,
74+
...(config && config.headers),
75+
},
76+
body: JSON.stringify(body),
77+
});
78+
this.checkResponseStatus(response);
79+
return this.responseParse(response);
80+
}
81+
82+
private async responseParse<T>(res: Response): Promise<T> {
83+
if (this.responseParser) return this.responseParser(res);
84+
else return await res.json();
85+
}
86+
87+
public async put<T>(
88+
url: string,
89+
body?: any,
90+
config?: Partial<FetchRequestConfig>,
91+
): Promise<T> {
92+
const requestUrl = new URL(url, this.baseURL);
93+
const response = await fetch(requestUrl, {
94+
method: "PUT",
95+
headers: {
96+
"Content-Type": "application/json",
97+
...this.defaultHeaders,
98+
...(config && config.headers),
99+
},
100+
body: JSON.stringify(body),
101+
});
102+
this.checkResponseStatus(response);
103+
return this.responseParse(response);
104+
}
105+
106+
public async postForm<T>(url: string, body?: any): Promise<T> {
107+
const requestUrl = new URL(url, this.baseURL);
108+
const response = await fetch(requestUrl, {
109+
method: "POST",
110+
headers: {
111+
"Content-Type": "application/x-www-form-urlencoded",
112+
...this.defaultHeaders,
113+
},
114+
body: qs.stringify(body),
115+
});
116+
this.checkResponseStatus(response);
117+
return response.json();
118+
}
119+
120+
public async postFormMultipart<T>(url: string, form: FormData): Promise<T> {
121+
const requestUrl = new URL(url, this.baseURL);
122+
const response = await fetch(requestUrl, {
123+
method: "POST",
124+
headers: {
125+
...this.defaultHeaders,
126+
},
127+
body: form,
128+
});
129+
this.checkResponseStatus(response);
130+
return response.json();
131+
}
132+
133+
public async putFormMultipart<T>(
134+
url: string,
135+
form: FormData,
136+
config?: Partial<FetchRequestConfig>,
137+
): Promise<T> {
138+
const requestUrl = new URL(url, this.baseURL);
139+
const response = await fetch(requestUrl, {
140+
method: "PUT",
141+
headers: {
142+
...this.defaultHeaders,
143+
...(config && (config.headers ? config.headers : {})),
144+
},
145+
body: form,
146+
});
147+
this.checkResponseStatus(response);
148+
return response.json();
149+
}
150+
public async postBinaryContent<T>(url: string, body: Blob): Promise<T> {
151+
const requestUrl = new URL(url, this.baseURL);
152+
const response = await fetch(requestUrl, {
153+
method: "POST",
154+
headers: {
155+
"Content-Type": body.type,
156+
...this.defaultHeaders,
157+
},
158+
body: body,
159+
});
160+
this.checkResponseStatus(response);
161+
return response.json();
162+
}
163+
164+
public async delete<T>(url: string, params?: any): Promise<T> {
165+
const requestUrl = new URL(url, this.baseURL);
166+
if (params) {
167+
requestUrl.search = new URLSearchParams(params).toString();
168+
}
169+
const response = await fetch(requestUrl, {
170+
method: "DELETE",
171+
headers: {
172+
...this.defaultHeaders,
173+
},
174+
});
175+
this.checkResponseStatus(response);
176+
return response.json();
177+
}
178+
179+
private checkResponseStatus(response: Response) {
180+
if (!response.ok) {
181+
throw new HTTPError(
182+
"HTTP request failed",
183+
response.status,
184+
response.statusText,
185+
undefined,
186+
);
187+
}
188+
}
189+
}

lib/insight/api/insightClient.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ import * as Types from "../../types";
2121
import { ensureJSON } from "../../utils";
2222
import { Readable } from "stream";
2323

24-
import HTTPClient from "../../http";
25-
import { AxiosResponse } from "axios";
24+
import HTTPFetchClient from "../../http-fetch";
2625

2726
// ===============================================
2827
// This file is autogenerated - Please do not edit
@@ -35,13 +34,13 @@ interface httpClientConfig {
3534
}
3635

3736
export class InsightClient {
38-
private httpClient: HTTPClient;
37+
private httpClient: HTTPFetchClient;
3938

4039
constructor(config: httpClientConfig) {
4140
if (!config.baseURL) {
4241
config.baseURL = "https://api.line.me";
4342
}
44-
this.httpClient = new HTTPClient({
43+
this.httpClient = new HTTPFetchClient({
4544
defaultHeaders: {
4645
Authorization: "Bearer " + config.channelAccessToken,
4746
},
@@ -50,14 +49,15 @@ export class InsightClient {
5049
});
5150
}
5251

53-
private parseHTTPResponse(response: AxiosResponse) {
52+
private async parseHTTPResponse(response: Response) {
5453
const { LINE_REQUEST_ID_HTTP_HEADER_NAME } = Types;
55-
let resBody = {
56-
...response.data,
54+
let resBody: Record<string, any> = {
55+
...(await response.json()),
5756
};
58-
if (response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME]) {
59-
resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] =
60-
response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME];
57+
if (response.headers.get(LINE_REQUEST_ID_HTTP_HEADER_NAME)) {
58+
resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] = response.headers.get(
59+
LINE_REQUEST_ID_HTTP_HEADER_NAME,
60+
);
6161
}
6262
return resBody;
6363
}

0 commit comments

Comments
 (0)