Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions rest/__snapshots__/pages.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const snapshot = {};

snapshot[`getPage 1`] = `
Request {
bodyUsed: false,
headers: Headers {},
method: "GET",
redirect: "follow",
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..."
}
`;

snapshot[`listPages 1`] = `
Request {
bodyUsed: false,
headers: Headers {},
method: "GET",
redirect: "follow",
url: "https://scrapbox.io/api/pages/takker?sort=updated"
}
`;
21 changes: 21 additions & 0 deletions rest/__snapshots__/project.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const snapshot = {};

snapshot[`getProject 1`] = `
Request {
bodyUsed: false,
headers: Headers {},
method: "GET",
redirect: "follow",
url: "https://scrapbox.io/api/projects/takker"
}
`;

snapshot[`listProjects 1`] = `
Request {
bodyUsed: false,
headers: Headers {},
method: "GET",
redirect: "follow",
url: "https://scrapbox.io/api/projects?ids=dummy-id1&ids=dummy-id2"
}
`;
12 changes: 3 additions & 9 deletions rest/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@ import { tryToErrorLike } from "../is.ts";
/** 想定されない応答が帰ってきたときに投げる例外 */
export class UnexpectedResponseError extends Error {
name = "UnexpectedResponseError";
request: Request;
response: Response;

constructor(
init: { request: Request; response: Response },
public response: Response,
) {
super(
`${init.response.status} ${init.response.statusText} when fetching ${init.request.url}`,
`${response.status} ${response.statusText} when fetching ${response.url}`,
);

this.request = init.request.clone();
this.response = init.response.clone();

// @ts-ignore only available on V8
if (Error.captureStackTrace) {
// @ts-ignore only available on V8
Expand All @@ -27,14 +22,13 @@ export class UnexpectedResponseError extends Error {

/** 失敗した要求からエラー情報を取り出す */
export const makeError = async <T extends ErrorLike>(
req: Request,
res: Response,
): Promise<{ ok: false; value: T }> => {
const response = res.clone();
const text = await response.text();
const value = tryToErrorLike(text);
if (!value) {
throw new UnexpectedResponseError({ request: req, response });
throw new UnexpectedResponseError(response);
}
return {
ok: false,
Expand Down
5 changes: 1 addition & 4 deletions rest/getGyazoToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ export const getGyazoToken = async (

const res = await fetch(req);
if (!res.ok) {
return makeError<NotLoggedInError>(
req,
res,
);
return makeError<NotLoggedInError>(res);
}

const { token } = (await res.json()) as { token?: string };
Expand Down
5 changes: 1 addition & 4 deletions rest/getSnapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ export const getSnapshots = async (
},
};
}
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(
req,
res,
);
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(res);
}

const data = (await res.json()) as PageSnapshot;
Expand Down
2 changes: 1 addition & 1 deletion rest/getTweetInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const getTweetInfo = async (
},
};
}
return makeError<SessionError | BadRequestError>(req, res);
return makeError<SessionError | BadRequestError>(res);
}

const tweet = (await res.json()) as TweetInfo;
Expand Down
2 changes: 1 addition & 1 deletion rest/getWebPageTitle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const getWebPageTitle = async (
},
};
}
return makeError<SessionError | BadRequestError>(req, res);
return makeError<SessionError | BadRequestError>(res);
}

const { title } = (await res.json()) as { title: string };
Expand Down
2 changes: 1 addition & 1 deletion rest/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const getLinks = async (
};
}

return makeError<NotFoundError | NotLoggedInError>(req, res);
return makeError<NotFoundError | NotLoggedInError>(res);
}

const pages = (await res.json()) as SearchedTitle[];
Expand Down
6 changes: 2 additions & 4 deletions rest/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const importPages = async (

const res = await fetch(req);
if (!res.ok) {
return makeError(req, res);
return makeError(res);
}

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

if (!res.ok) {
return makeError<
NotFoundError | NotPrivilegeError | NotLoggedInError
>(req, res);
return makeError<NotFoundError | NotPrivilegeError | NotLoggedInError>(res);
}

const value = (await res.json()) as ExportedData<withMetadata>;
Expand Down
15 changes: 15 additions & 0 deletions rest/pages.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getPage, listPages } from "./pages.ts";
import { assertSnapshot } from "../deps/testing.ts";

Deno.test("getPage", async (t) => {
await assertSnapshot(
t,
getPage.toRequest("takker", "テストページ", { followRename: true }),
);
});
Deno.test("listPages", async (t) => {
await assertSnapshot(
t,
listPages.toRequest("takker", { sort: "updated" }),
);
});
175 changes: 128 additions & 47 deletions rest/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,84 @@ import { BaseOptions, Result, setDefaults } from "./util.ts";
export interface GetPageOption extends BaseOptions {
/** use `followRename` */ followRename?: boolean;
}

const getPage_toRequest: GetPage["toRequest"] = (
project,
title,
options,
) => {
const { sid, hostName, followRename } = setDefaults(options ?? {});
const path = `https://${hostName}/api/pages/${project}/${
encodeTitleURI(title)
}?followRename=${followRename ?? true}`;
return new Request(
path,
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
);
};

const getPage_fromResponse: GetPage["fromResponse"] = async (res) => {
if (!res.ok) {
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(res);
}
const value = (await res.json()) as Page;
return { ok: true, value };
};

export interface GetPage {
/** /api/pages/:project/:title の要求を組み立てる
*
* @param project 取得したいページのproject名
* @param title 取得したいページのtitle 大文字小文字は問わない
* @param options オプション
* @return request
*/
toRequest: (
project: string,
title: string,
options?: GetPageOption,
) => Request;

/** 帰ってきた応答からページのJSONデータを取得する
*
* @param res 応答
* @return ページのJSONデータ
*/
fromResponse: (res: Response) => Promise<
Result<
Page,
NotFoundError | NotLoggedInError | NotMemberError
>
>;

(project: string, title: string, options?: GetPageOption): Promise<
Result<
Page,
NotFoundError | NotLoggedInError | NotMemberError
>
>;
}

/** 指定したページのJSONデータを取得する
*
* @param project 取得したいページのproject名
* @param title 取得したいページのtitle 大文字小文字は問わない
* @param options オプション
*/
export const getPage = async (
project: string,
title: string,
options?: GetPageOption,
): Promise<
Result<
Page,
NotFoundError | NotLoggedInError | NotMemberError
>
> => {
const { sid, hostName, fetch, followRename } = setDefaults(options ?? {});
const req = new Request(
`https://${hostName}/api/pages/${project}/${
encodeTitleURI(title)
}?followRename=${followRename ?? true}`,
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
);
export const getPage: GetPage = async (
project,
title,
options,
) => {
const { fetch } = setDefaults(options ?? {});
const req = getPage_toRequest(project, title, options);
const res = await fetch(req);
if (!res.ok) {
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(
req,
res,
);
}
const value = (await res.json()) as Page;
return { ok: true, value };
return await getPage_fromResponse(res);
};

getPage.toRequest = getPage_toRequest;
getPage.fromResponse = getPage_fromResponse;

/** Options for `listPages()` */
export interface ListPagesOption extends BaseOptions {
/** the sort of page list to return
Expand All @@ -74,39 +118,76 @@ export interface ListPagesOption extends BaseOptions {
*/
limit?: number;
}
/** 指定したprojectのページを一覧する
*
* @param project 一覧したいproject
* @param options オプション 取得範囲や並び順を決める
*/
export const listPages = async (
project: string,
options?: ListPagesOption,
): Promise<
Result<
PageList,
NotFoundError | NotLoggedInError | NotMemberError
>
> => {
const { sid, hostName, fetch, sort, limit, skip } = setDefaults(

export interface ListPages {
/** /api/pages/:project の要求を組み立てる
*
* @param project 取得したいページのproject名
* @param options オプション
* @return request
*/
toRequest: (
project: string,
options?: ListPagesOption,
) => Request;

/** 帰ってきた応答からページのJSONデータを取得する
*
* @param res 応答
* @return ページのJSONデータ
*/
fromResponse: (res: Response) => Promise<
Result<
PageList,
NotFoundError | NotLoggedInError | NotMemberError
>
>;

(project: string, options?: ListPagesOption): Promise<
Result<
PageList,
NotFoundError | NotLoggedInError | NotMemberError
>
>;
}

const listPages_toRequest: ListPages["toRequest"] = (project, options) => {
const { sid, hostName, sort, limit, skip } = setDefaults(
options ?? {},
);
const params = new URLSearchParams();
if (sort !== undefined) params.append("sort", sort);
if (limit !== undefined) params.append("limit", `${limit}`);
if (skip !== undefined) params.append("skip", `${skip}`);
const req = new Request(
`https://${hostName}/api/pages/${project}?${params.toString()}`,
const path = `https://${hostName}/api/pages/${project}?${params.toString()}`;

return new Request(
path,
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
);
};

const res = await fetch(req);
const listPages_fromResponse: ListPages["fromResponse"] = async (res) => {
if (!res.ok) {
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(
req,
res,
);
return makeError<NotFoundError | NotLoggedInError | NotMemberError>(res);
}
const value = (await res.json()) as PageList;
return { ok: true, value };
};

/** 指定したprojectのページを一覧する
*
* @param project 一覧したいproject
* @param options オプション 取得範囲や並び順を決める
*/
export const listPages: ListPages = async (
project,
options?,
) => {
const { fetch } = setDefaults(options ?? {});
const res = await fetch(listPages_toRequest(project, options));
return await listPages_fromResponse(res);
};

listPages.toRequest = listPages_toRequest;
listPages.fromResponse = listPages_fromResponse;
2 changes: 1 addition & 1 deletion rest/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const getProfile = async (
);
const response = await fetch(request);
if (!response.ok) {
throw new UnexpectedResponseError({ request, response });
throw new UnexpectedResponseError(response);
}
return (await response.json()) as MemberUser | GuestUser;
};
Loading