Skip to content

Commit 3714acc

Browse files
committed
improve
1 parent 4767529 commit 3714acc

File tree

2 files changed

+125
-120
lines changed

2 files changed

+125
-120
lines changed

src/fetch-wrapper.ts

Lines changed: 115 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { isPlainObject } from "./is-plain-object.js";
22
import { RequestError } from "@octokit/request-error";
3-
import type { EndpointInterface } from "@octokit/types";
3+
import type { EndpointInterface, OctokitResponse } from "@octokit/types";
44

5-
export default function fetchWrapper(
5+
export default async function fetchWrapper(
66
requestOptions: ReturnType<EndpointInterface>,
7-
) {
7+
): Promise<OctokitResponse<any>> {
88
const fetch: typeof globalThis.fetch =
99
requestOptions.request?.fetch || globalThis.fetch;
1010

@@ -30,109 +30,30 @@ export default function fetchWrapper(
3030
]),
3131
);
3232

33-
let responseHeaders: { [header: string]: string } = {};
34-
let status: number;
35-
let url: string;
36-
37-
return fetch(requestOptions.url, {
38-
method: requestOptions.method,
39-
body,
40-
redirect: requestOptions.request?.redirect,
41-
// Header values must be `string`
42-
headers: requestHeaders,
43-
signal: requestOptions.request?.signal,
44-
// duplex must be set if request.body is ReadableStream or Async Iterables.
45-
// See https://fetch.spec.whatwg.org/#dom-requestinit-duplex.
46-
...(requestOptions.body && { duplex: "half" }),
47-
})
48-
.then(async (response) => {
49-
url = response.url;
50-
status = response.status;
51-
52-
for (const keyAndValue of response.headers) {
53-
responseHeaders[keyAndValue[0]] = keyAndValue[1];
54-
}
55-
56-
if ("deprecation" in responseHeaders) {
57-
const matches =
58-
responseHeaders.link &&
59-
responseHeaders.link.match(/<([^>]+)>; rel="deprecation"/);
60-
const deprecationLink = matches && matches.pop();
61-
log.warn(
62-
`[@octokit/request] "${requestOptions.method} ${
63-
requestOptions.url
64-
}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${
65-
deprecationLink ? `. See ${deprecationLink}` : ""
66-
}`,
67-
);
68-
}
69-
70-
if (status === 204 || status === 205) {
71-
return;
72-
}
73-
74-
// GitHub API returns 200 for HEAD requests
75-
if (requestOptions.method === "HEAD") {
76-
if (status < 400) {
77-
return;
78-
}
79-
80-
throw new RequestError(response.statusText, status, {
81-
response: {
82-
url,
83-
status,
84-
headers: responseHeaders,
85-
data: undefined,
86-
},
87-
request: requestOptions,
88-
});
89-
}
90-
91-
if (status === 304) {
92-
throw new RequestError("Not modified", status, {
93-
response: {
94-
url,
95-
status,
96-
headers: responseHeaders,
97-
data: await getResponseData(response),
98-
},
99-
request: requestOptions,
100-
});
101-
}
102-
103-
if (status >= 400) {
104-
const data = await getResponseData(response);
105-
106-
const error = new RequestError(toErrorMessage(data), status, {
107-
response: {
108-
url,
109-
status,
110-
headers: responseHeaders,
111-
data,
112-
},
113-
request: requestOptions,
114-
});
115-
33+
let fetchResponse: Response;
34+
35+
try {
36+
fetchResponse = await fetch(requestOptions.url, {
37+
method: requestOptions.method,
38+
body,
39+
redirect: requestOptions.request?.redirect,
40+
// Header values must be `string`
41+
headers: requestHeaders,
42+
signal: requestOptions.request?.signal,
43+
// duplex must be set if request.body is ReadableStream or Async Iterables.
44+
// See https://fetch.spec.whatwg.org/#dom-requestinit-duplex.
45+
...(requestOptions.body && { duplex: "half" }),
46+
});
47+
// wrap fetch errors as RequestError if it is not a AbortError
48+
} catch (error) {
49+
let message = "Unknown Error";
50+
if (error instanceof Error) {
51+
if (error.name === "AbortError") {
52+
(error as RequestError).status = 500;
11653
throw error;
11754
}
11855

119-
return parseSuccessResponseBody
120-
? await getResponseData(response)
121-
: response.body;
122-
})
123-
.then((data) => {
124-
return {
125-
status,
126-
url,
127-
headers: responseHeaders,
128-
data,
129-
};
130-
})
131-
.catch((error) => {
132-
if (error instanceof RequestError) throw error;
133-
else if (error.name === "AbortError") throw error;
134-
135-
let message = error.message;
56+
message = error.message;
13657

13758
// undici throws a TypeError for network errors
13859
// and puts the error message in `error.cause`
@@ -144,11 +65,95 @@ export default function fetchWrapper(
14465
message = error.cause;
14566
}
14667
}
68+
}
14769

148-
throw new RequestError(message, 500, {
149-
request: requestOptions,
150-
});
70+
const requestError = new RequestError(message, 500, {
71+
request: requestOptions,
72+
});
73+
requestError.cause = error;
74+
75+
throw requestError;
76+
}
77+
78+
const status = fetchResponse.status;
79+
const url = fetchResponse.url;
80+
const responseHeaders: { [header: string]: string } = {};
81+
82+
for (const keyAndValue of fetchResponse.headers) {
83+
responseHeaders[keyAndValue[0]] = keyAndValue[1];
84+
}
85+
86+
const octokitResponse: OctokitResponse<any> = {
87+
url,
88+
status,
89+
headers: responseHeaders,
90+
data: "",
91+
};
92+
93+
if ("deprecation" in responseHeaders) {
94+
const matches =
95+
responseHeaders.link &&
96+
responseHeaders.link.match(/<([^>]+)>; rel="deprecation"/);
97+
const deprecationLink = matches && matches.pop();
98+
log.warn(
99+
`[@octokit/request] "${requestOptions.method} ${
100+
requestOptions.url
101+
}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${
102+
deprecationLink ? `. See ${deprecationLink}` : ""
103+
}`,
104+
);
105+
}
106+
107+
if (status === 204 || status === 205) {
108+
return octokitResponse;
109+
}
110+
111+
// GitHub API returns 200 for HEAD requests
112+
if (requestOptions.method === "HEAD") {
113+
if (status < 400) {
114+
return octokitResponse;
115+
}
116+
117+
throw new RequestError(fetchResponse.statusText, status, {
118+
response: octokitResponse,
119+
request: requestOptions,
120+
});
121+
}
122+
123+
if (status === 304) {
124+
octokitResponse.data = await getResponseData(fetchResponse);
125+
126+
throw new RequestError("Not modified", status, {
127+
response: octokitResponse,
128+
request: requestOptions,
151129
});
130+
}
131+
132+
if (status >= 400) {
133+
octokitResponse.data = await getResponseData(fetchResponse);
134+
135+
const error = new RequestError(
136+
toErrorMessage(octokitResponse.data),
137+
status,
138+
{
139+
response: octokitResponse,
140+
request: requestOptions,
141+
},
142+
);
143+
144+
throw error;
145+
}
146+
147+
const responseBody = parseSuccessResponseBody
148+
? await getResponseData(fetchResponse)
149+
: fetchResponse.body;
150+
151+
return {
152+
status,
153+
url,
154+
headers: responseHeaders,
155+
data: responseBody,
156+
};
152157
}
153158

154159
async function getResponseData(response: Response) {
@@ -167,7 +172,10 @@ async function getResponseData(response: Response) {
167172
);
168173
}
169174

170-
if (!contentType || /^text\/|charset=utf-8$/.test(contentType)) {
175+
if (
176+
(!contentType || /^text\/|charset=utf-8$/.test(contentType)) &&
177+
response.text
178+
) {
171179
return response.text();
172180
}
173181

test/request.test.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -977,28 +977,25 @@ x//0u+zd/R/QRUzLOw4N72/Hu+UG6MNt5iDZFCtapRaKt6OvSBwy8w==
977977
});
978978
};
979979

980-
const mock = (url: string) => {
981-
expect(url).toEqual("https://api.github.com/");
982-
return delay().then(() => {
983-
return {
984-
status: 200,
985-
headers: {},
986-
body: {
987-
message: "OK",
988-
},
989-
};
990-
});
991-
};
980+
const mock = fetchMock.sandbox().get("https://api.github.com/", () =>
981+
delay(3000).then(() => ({
982+
message: "Not Found",
983+
documentation_url:
984+
"https://docs.github.com/en/rest/reference/repos#get-a-repository",
985+
})),
986+
);
992987

993988
try {
994989
await request("GET /", {
995990
request: {
996991
fetch: mock,
992+
signal: AbortSignal.timeout(1000),
997993
},
998994
});
999995
throw new Error("should not resolve");
1000996
} catch (error) {
1001-
expect(error.name).toEqual("HttpError");
997+
expect(error.name).toEqual("AbortError");
998+
expect(error.message).toEqual("The operation was aborted.");
1002999
expect(error.status).toEqual(500);
10031000
}
10041001
});

0 commit comments

Comments
 (0)