Skip to content

Commit 04980af

Browse files
authored
Merge pull request #97 from takker99/purge-req-and-res
✨ extract request builder and response analyzer in `getPage`/`listPages` and `getProject`/`listProjects`
2 parents 0b58fe7 + fed3e94 commit 04980af

16 files changed

+323
-116
lines changed

rest/__snapshots__/pages.test.ts.snap

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const snapshot = {};
2+
3+
snapshot[`getPage 1`] = `
4+
Request {
5+
bodyUsed: false,
6+
headers: Headers {},
7+
method: "GET",
8+
redirect: "follow",
9+
url: "https://scrapbox.io/api/pages/takker/%E3%83%86%E3%82%B9%E3%83%88%E3%83%9A%E3%83%BC%E3%82%B8?followRe..."
10+
}
11+
`;
12+
13+
snapshot[`listPages 1`] = `
14+
Request {
15+
bodyUsed: false,
16+
headers: Headers {},
17+
method: "GET",
18+
redirect: "follow",
19+
url: "https://scrapbox.io/api/pages/takker?sort=updated"
20+
}
21+
`;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const snapshot = {};
2+
3+
snapshot[`getProject 1`] = `
4+
Request {
5+
bodyUsed: false,
6+
headers: Headers {},
7+
method: "GET",
8+
redirect: "follow",
9+
url: "https://scrapbox.io/api/projects/takker"
10+
}
11+
`;
12+
13+
snapshot[`listProjects 1`] = `
14+
Request {
15+
bodyUsed: false,
16+
headers: Headers {},
17+
method: "GET",
18+
redirect: "follow",
19+
url: "https://scrapbox.io/api/projects?ids=dummy-id1&ids=dummy-id2"
20+
}
21+
`;

rest/error.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@ import { tryToErrorLike } from "../is.ts";
44
/** 想定されない応答が帰ってきたときに投げる例外 */
55
export class UnexpectedResponseError extends Error {
66
name = "UnexpectedResponseError";
7-
request: Request;
8-
response: Response;
97

108
constructor(
11-
init: { request: Request; response: Response },
9+
public response: Response,
1210
) {
1311
super(
14-
`${init.response.status} ${init.response.statusText} when fetching ${init.request.url}`,
12+
`${response.status} ${response.statusText} when fetching ${response.url}`,
1513
);
1614

17-
this.request = init.request.clone();
18-
this.response = init.response.clone();
19-
2015
// @ts-ignore only available on V8
2116
if (Error.captureStackTrace) {
2217
// @ts-ignore only available on V8
@@ -27,14 +22,13 @@ export class UnexpectedResponseError extends Error {
2722

2823
/** 失敗した要求からエラー情報を取り出す */
2924
export const makeError = async <T extends ErrorLike>(
30-
req: Request,
3125
res: Response,
3226
): Promise<{ ok: false; value: T }> => {
3327
const response = res.clone();
3428
const text = await response.text();
3529
const value = tryToErrorLike(text);
3630
if (!value) {
37-
throw new UnexpectedResponseError({ request: req, response });
31+
throw new UnexpectedResponseError(response);
3832
}
3933
return {
4034
ok: false,

rest/getGyazoToken.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ export const getGyazoToken = async (
3434

3535
const res = await fetch(req);
3636
if (!res.ok) {
37-
return makeError<NotLoggedInError>(
38-
req,
39-
res,
40-
);
37+
return makeError<NotLoggedInError>(res);
4138
}
4239

4340
const { token } = (await res.json()) as { token?: string };

rest/getSnapshots.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,7 @@ export const getSnapshots = async (
5858
},
5959
};
6060
}
61-
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(
62-
req,
63-
res,
64-
);
61+
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(res);
6562
}
6663

6764
const data = (await res.json()) as PageSnapshot;

rest/getTweetInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const getTweetInfo = async (
5353
},
5454
};
5555
}
56-
return makeError<SessionError | BadRequestError>(req, res);
56+
return makeError<SessionError | BadRequestError>(res);
5757
}
5858

5959
const tweet = (await res.json()) as TweetInfo;

rest/getWebPageTitle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const getWebPageTitle = async (
5353
},
5454
};
5555
}
56-
return makeError<SessionError | BadRequestError>(req, res);
56+
return makeError<SessionError | BadRequestError>(res);
5757
}
5858

5959
const { title } = (await res.json()) as { title: string };

rest/link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const getLinks = async (
5353
};
5454
}
5555

56-
return makeError<NotFoundError | NotLoggedInError>(req, res);
56+
return makeError<NotFoundError | NotLoggedInError>(res);
5757
}
5858

5959
const pages = (await res.json()) as SearchedTitle[];

rest/page-data.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const importPages = async (
4949

5050
const res = await fetch(req);
5151
if (!res.ok) {
52-
return makeError(req, res);
52+
return makeError(res);
5353
}
5454

5555
const { message } = (await res.json()) as { message: string };
@@ -83,9 +83,7 @@ export const exportPages = async <withMetadata extends true | false>(
8383
const res = await fetch(req);
8484

8585
if (!res.ok) {
86-
return makeError<
87-
NotFoundError | NotPrivilegeError | NotLoggedInError
88-
>(req, res);
86+
return makeError<NotFoundError | NotPrivilegeError | NotLoggedInError>(res);
8987
}
9088

9189
const value = (await res.json()) as ExportedData<withMetadata>;

rest/pages.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { getPage, listPages } from "./pages.ts";
2+
import { assertSnapshot } from "../deps/testing.ts";
3+
4+
Deno.test("getPage", async (t) => {
5+
await assertSnapshot(
6+
t,
7+
getPage.toRequest("takker", "テストページ", { followRename: true }),
8+
);
9+
});
10+
Deno.test("listPages", async (t) => {
11+
await assertSnapshot(
12+
t,
13+
listPages.toRequest("takker", { sort: "updated" }),
14+
);
15+
});

rest/pages.ts

Lines changed: 128 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,84 @@ import { BaseOptions, Result, setDefaults } from "./util.ts";
1414
export interface GetPageOption extends BaseOptions {
1515
/** use `followRename` */ followRename?: boolean;
1616
}
17+
18+
const getPage_toRequest: GetPage["toRequest"] = (
19+
project,
20+
title,
21+
options,
22+
) => {
23+
const { sid, hostName, followRename } = setDefaults(options ?? {});
24+
const path = `https://${hostName}/api/pages/${project}/${
25+
encodeTitleURI(title)
26+
}?followRename=${followRename ?? true}`;
27+
return new Request(
28+
path,
29+
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
30+
);
31+
};
32+
33+
const getPage_fromResponse: GetPage["fromResponse"] = async (res) => {
34+
if (!res.ok) {
35+
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(res);
36+
}
37+
const value = (await res.json()) as Page;
38+
return { ok: true, value };
39+
};
40+
41+
export interface GetPage {
42+
/** /api/pages/:project/:title の要求を組み立てる
43+
*
44+
* @param project 取得したいページのproject名
45+
* @param title 取得したいページのtitle 大文字小文字は問わない
46+
* @param options オプション
47+
* @return request
48+
*/
49+
toRequest: (
50+
project: string,
51+
title: string,
52+
options?: GetPageOption,
53+
) => Request;
54+
55+
/** 帰ってきた応答からページのJSONデータを取得する
56+
*
57+
* @param res 応答
58+
* @return ページのJSONデータ
59+
*/
60+
fromResponse: (res: Response) => Promise<
61+
Result<
62+
Page,
63+
NotFoundError | NotLoggedInError | NotMemberError
64+
>
65+
>;
66+
67+
(project: string, title: string, options?: GetPageOption): Promise<
68+
Result<
69+
Page,
70+
NotFoundError | NotLoggedInError | NotMemberError
71+
>
72+
>;
73+
}
74+
1775
/** 指定したページのJSONデータを取得する
1876
*
1977
* @param project 取得したいページのproject名
2078
* @param title 取得したいページのtitle 大文字小文字は問わない
2179
* @param options オプション
2280
*/
23-
export const getPage = async (
24-
project: string,
25-
title: string,
26-
options?: GetPageOption,
27-
): Promise<
28-
Result<
29-
Page,
30-
NotFoundError | NotLoggedInError | NotMemberError
31-
>
32-
> => {
33-
const { sid, hostName, fetch, followRename } = setDefaults(options ?? {});
34-
const req = new Request(
35-
`https://${hostName}/api/pages/${project}/${
36-
encodeTitleURI(title)
37-
}?followRename=${followRename ?? true}`,
38-
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
39-
);
81+
export const getPage: GetPage = async (
82+
project,
83+
title,
84+
options,
85+
) => {
86+
const { fetch } = setDefaults(options ?? {});
87+
const req = getPage_toRequest(project, title, options);
4088
const res = await fetch(req);
41-
if (!res.ok) {
42-
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(
43-
req,
44-
res,
45-
);
46-
}
47-
const value = (await res.json()) as Page;
48-
return { ok: true, value };
89+
return await getPage_fromResponse(res);
4990
};
5091

92+
getPage.toRequest = getPage_toRequest;
93+
getPage.fromResponse = getPage_fromResponse;
94+
5195
/** Options for `listPages()` */
5296
export interface ListPagesOption extends BaseOptions {
5397
/** the sort of page list to return
@@ -74,39 +118,76 @@ export interface ListPagesOption extends BaseOptions {
74118
*/
75119
limit?: number;
76120
}
77-
/** 指定したprojectのページを一覧する
78-
*
79-
* @param project 一覧したいproject
80-
* @param options オプション 取得範囲や並び順を決める
81-
*/
82-
export const listPages = async (
83-
project: string,
84-
options?: ListPagesOption,
85-
): Promise<
86-
Result<
87-
PageList,
88-
NotFoundError | NotLoggedInError | NotMemberError
89-
>
90-
> => {
91-
const { sid, hostName, fetch, sort, limit, skip } = setDefaults(
121+
122+
export interface ListPages {
123+
/** /api/pages/:project の要求を組み立てる
124+
*
125+
* @param project 取得したいページのproject名
126+
* @param options オプション
127+
* @return request
128+
*/
129+
toRequest: (
130+
project: string,
131+
options?: ListPagesOption,
132+
) => Request;
133+
134+
/** 帰ってきた応答からページのJSONデータを取得する
135+
*
136+
* @param res 応答
137+
* @return ページのJSONデータ
138+
*/
139+
fromResponse: (res: Response) => Promise<
140+
Result<
141+
PageList,
142+
NotFoundError | NotLoggedInError | NotMemberError
143+
>
144+
>;
145+
146+
(project: string, options?: ListPagesOption): Promise<
147+
Result<
148+
PageList,
149+
NotFoundError | NotLoggedInError | NotMemberError
150+
>
151+
>;
152+
}
153+
154+
const listPages_toRequest: ListPages["toRequest"] = (project, options) => {
155+
const { sid, hostName, sort, limit, skip } = setDefaults(
92156
options ?? {},
93157
);
94158
const params = new URLSearchParams();
95159
if (sort !== undefined) params.append("sort", sort);
96160
if (limit !== undefined) params.append("limit", `${limit}`);
97161
if (skip !== undefined) params.append("skip", `${skip}`);
98-
const req = new Request(
99-
`https://${hostName}/api/pages/${project}?${params.toString()}`,
162+
const path = `https://${hostName}/api/pages/${project}?${params.toString()}`;
163+
164+
return new Request(
165+
path,
100166
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
101167
);
168+
};
102169

103-
const res = await fetch(req);
170+
const listPages_fromResponse: ListPages["fromResponse"] = async (res) => {
104171
if (!res.ok) {
105-
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(
106-
req,
107-
res,
108-
);
172+
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(res);
109173
}
110174
const value = (await res.json()) as PageList;
111175
return { ok: true, value };
112176
};
177+
178+
/** 指定したprojectのページを一覧する
179+
*
180+
* @param project 一覧したいproject
181+
* @param options オプション 取得範囲や並び順を決める
182+
*/
183+
export const listPages: ListPages = async (
184+
project,
185+
options?,
186+
) => {
187+
const { fetch } = setDefaults(options ?? {});
188+
const res = await fetch(listPages_toRequest(project, options));
189+
return await listPages_fromResponse(res);
190+
};
191+
192+
listPages.toRequest = listPages_toRequest;
193+
listPages.fromResponse = listPages_fromResponse;

rest/profile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const getProfile = async (
1717
);
1818
const response = await fetch(request);
1919
if (!response.ok) {
20-
throw new UnexpectedResponseError({ request, response });
20+
throw new UnexpectedResponseError(response);
2121
}
2222
return (await response.json()) as MemberUser | GuestUser;
2323
};

0 commit comments

Comments
 (0)