From 0d8f263fdfe72c8fbbc857ae9d3e81e93be61a96 Mon Sep 17 00:00:00 2001 From: takker99 <37929109+takker99@users.noreply.github.com> Date: Sat, 24 Dec 2022 13:43:18 +0900 Subject: [PATCH] :boom: Make UnexpectedResponseError have Request and Response --- rest/error.ts | 37 +++++++++++++++------ rest/getGyazoToken.ts | 28 ++++++---------- rest/getSnapshots.ts | 31 +++++++----------- rest/getTweetInfo.ts | 31 +++++------------- rest/getWebPageTitle.ts | 30 +++++------------ rest/link.ts | 27 ++++++---------- rest/page-data.ts | 45 +++++++------------------- rest/pages.ts | 61 +++++++++++----------------------- rest/profile.ts | 16 ++++----- rest/project.ts | 50 +++++++++------------------- rest/replaceLinks.ts | 27 ++++++---------- rest/search.ts | 72 +++++++++++++---------------------------- 12 files changed, 158 insertions(+), 297 deletions(-) diff --git a/rest/error.ts b/rest/error.ts index 3b90966..639f529 100644 --- a/rest/error.ts +++ b/rest/error.ts @@ -1,21 +1,21 @@ +import type { ErrorLike } from "../deps/scrapbox-rest.ts"; +import { tryToErrorLike } from "../is.ts"; + +/** 想定されない応答が帰ってきたときに投げる例外 */ export class UnexpectedResponseError extends Error { name = "UnexpectedResponseError"; - status: number; - statusText: string; - body: string; - path: URL; + request: Request; + response: Response; constructor( - init: { status: number; statusText: string; body: string; path: URL }, + init: { request: Request; response: Response }, ) { super( - `${init.status} ${init.statusText} when fetching ${init.path.toString()}`, + `${init.response.status} ${init.response.statusText} when fetching ${init.request.url}`, ); - this.status = init.status; - this.statusText = init.statusText; - this.body = init.body; - this.path = init.path; + this.request = init.request.clone(); + this.response = init.response.clone(); // @ts-ignore only available on V8 if (Error.captureStackTrace) { @@ -24,3 +24,20 @@ export class UnexpectedResponseError extends Error { } } } + +/** 失敗した要求からエラー情報を取り出す */ +export const makeError = async ( + 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 }); + } + return { + ok: false, + value: value as T, + }; +}; diff --git a/rest/getGyazoToken.ts b/rest/getGyazoToken.ts index c354336..6dcf0e9 100644 --- a/rest/getGyazoToken.ts +++ b/rest/getGyazoToken.ts @@ -1,7 +1,6 @@ import type { NotLoggedInError } from "../deps/scrapbox-rest.ts"; import { cookie } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { BaseOptions, Result, setDefaults } from "./util.ts"; export interface GetGyazoTokenOptions extends BaseOptions { @@ -26,26 +25,19 @@ export const getGyazoToken = async ( > > => { const { sid, hostName, gyazoTeamsName } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/login/gyazo/oauth-upload/token${ - gyazoTeamsName ? `?gyazoTeamsName=${gyazoTeamsName}` : "" - }`; - - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/login/gyazo/oauth-upload/token${ + gyazoTeamsName ? `?gyazoTeamsName=${gyazoTeamsName}` : "" + }`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: await res.text(), - }); - } - return { ok: false, value: value as NotLoggedInError }; + return makeError( + req, + res, + ); } const { token } = (await res.json()) as { token?: string }; diff --git a/rest/getSnapshots.ts b/rest/getSnapshots.ts index 9d3e071..4d97200 100644 --- a/rest/getSnapshots.ts +++ b/rest/getSnapshots.ts @@ -6,10 +6,9 @@ import type { PageSnapshot, Snapshot, } from "../deps/scrapbox-rest.ts"; -import { tryToErrorLike } from "../is.ts"; import { cookie } from "./auth.ts"; import { BaseOptions, Result, setDefaults } from "./util.ts"; -import { UnexpectedResponseError } from "./error.ts"; +import { makeError } from "./error.ts"; /** 不正なfollowingIdを渡されたときに発生するエラー */ export interface InvalidPageSnapshotIdError extends ErrorLike { @@ -39,15 +38,16 @@ export const getSnapshots = async ( > > => { const { sid, hostName, fetch, followingId } = setDefaults(options ?? {}); - const path = `https://${hostName}/api/page-snapshots/${project}/${pageId}/${ - followingId ? `?followingId=${followingId}` : "" - }`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/page-snapshots/${project}/${pageId}/${ + followingId ? `?followingId=${followingId}` : "" + }`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); + if (!res.ok) { if (res.status === 422) { return { @@ -58,19 +58,10 @@ export const getSnapshots = async ( }, }; } - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as (NotFoundError | NotLoggedInError | NotMemberError), - }; + return makeError( + req, + res, + ); } const data = (await res.json()) as PageSnapshot; diff --git a/rest/getTweetInfo.ts b/rest/getTweetInfo.ts index 13ed3f2..ea30d28 100644 --- a/rest/getTweetInfo.ts +++ b/rest/getTweetInfo.ts @@ -5,8 +5,7 @@ import type { TweetInfo, } from "../deps/scrapbox-rest.ts"; import { cookie, getCSRFToken } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { ExtendedOptions, Result, setDefaults } from "./util.ts"; /** 指定したTweetの情報を取得する @@ -27,12 +26,10 @@ export const getTweetInfo = async ( > > => { const { sid, hostName, fetch, csrf } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/embed-text/twitter?url=${ - encodeURIComponent(url.toString()) - }`; - - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/embed-text/twitter?url=${ + encodeURIComponent(url.toString()) + }`, { method: "POST", headers: { @@ -44,6 +41,8 @@ export const getTweetInfo = async ( }, ); + const res = await fetch(req); + if (!res.ok) { if (res.status === 422) { return { @@ -54,21 +53,7 @@ export const getTweetInfo = async ( }, }; } - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as - | SessionError - | BadRequestError, - }; + return makeError(req, res); } const tweet = (await res.json()) as TweetInfo; diff --git a/rest/getWebPageTitle.ts b/rest/getWebPageTitle.ts index 9685ab4..9bf9bc6 100644 --- a/rest/getWebPageTitle.ts +++ b/rest/getWebPageTitle.ts @@ -4,8 +4,7 @@ import type { SessionError, } from "../deps/scrapbox-rest.ts"; import { cookie, getCSRFToken } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { ExtendedOptions, Result, setDefaults } from "./util.ts"; /** 指定したURLのweb pageのtitleをscrapboxのserver経由で取得する @@ -26,12 +25,11 @@ export const getWebPageTitle = async ( > > => { const { sid, hostName, fetch, csrf } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/embed-text/url?url=${ - encodeURIComponent(url.toString()) - }`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/embed-text/url?url=${ + encodeURIComponent(url.toString()) + }`, { method: "POST", headers: { @@ -43,6 +41,8 @@ export const getWebPageTitle = async ( }, ); + const res = await fetch(req); + if (!res.ok) { if (res.status === 422) { return { @@ -53,21 +53,7 @@ export const getWebPageTitle = async ( }, }; } - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as - | SessionError - | BadRequestError, - }; + return makeError(req, res); } const { title } = (await res.json()) as { title: string }; diff --git a/rest/link.ts b/rest/link.ts index 36c9fc8..ff665e7 100644 --- a/rest/link.ts +++ b/rest/link.ts @@ -5,8 +5,7 @@ import type { SearchedTitle, } from "../deps/scrapbox-rest.ts"; import { cookie } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { BaseOptions, Result, setDefaults } from "./util.ts"; /** 不正なfollowingIdを渡されたときに発生するエラー */ @@ -33,15 +32,16 @@ export const getLinks = async ( }, NotFoundError | NotLoggedInError | InvalidFollowingIdError> > => { const { sid, hostName, fetch, followingId } = setDefaults(options ?? {}); - const path = `https://${hostName}/api/pages/${project}/search/titles${ - followingId ? `?followingId=${followingId}` : "" - }`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/pages/${project}/search/titles${ + followingId ? `?followingId=${followingId}` : "" + }`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); + if (!res.ok) { if (res.status === 422) { return { @@ -52,17 +52,10 @@ export const getLinks = async ( }, }; } - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { ok: false, value: value as NotFoundError | NotLoggedInError }; + + return makeError(req, res); } + const pages = (await res.json()) as SearchedTitle[]; return { ok: true, diff --git a/rest/page-data.ts b/rest/page-data.ts index 910222d..a795f34 100644 --- a/rest/page-data.ts +++ b/rest/page-data.ts @@ -7,8 +7,7 @@ import type { NotPrivilegeError, } from "../deps/scrapbox-rest.ts"; import { cookie, getCSRFToken } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { BaseOptions, ExtendedOptions, Result, setDefaults } from "./util.ts"; /** projectにページをインポートする * @@ -35,10 +34,8 @@ export const importPages = async ( }), ); formData.append("name", "undefined"); - const path = `https://${hostName}/api/page-data/import/${project}.json`; - - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/page-data/import/${project}.json`, { method: "POST", headers: { @@ -50,17 +47,9 @@ export const importPages = async ( }, ); + const res = await fetch(req); if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { ok: false, value }; + return makeError(req, res); } const { message } = (await res.json()) as { message: string }; @@ -86,27 +75,17 @@ export const exportPages = async ( > > => { const { sid, hostName, fetch, metadata } = setDefaults(init ?? {}); - const path = - `https://${hostName}/api/page-data/export/${project}.json?metadata=${metadata}`; - const res = await fetch( - path, + + const req = new Request( + `https://${hostName}/api/page-data/export/${project}.json?metadata=${metadata}`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as NotFoundError | NotPrivilegeError | NotLoggedInError, - }; + return makeError< + NotFoundError | NotPrivilegeError | NotLoggedInError + >(req, res); } const value = (await res.json()) as ExportedData; diff --git a/rest/pages.ts b/rest/pages.ts index 5955851..54ac8aa 100644 --- a/rest/pages.ts +++ b/rest/pages.ts @@ -6,8 +6,7 @@ import type { PageList, } from "../deps/scrapbox-rest.ts"; import { cookie } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { encodeTitleURI } from "../title.ts"; import { BaseOptions, Result, setDefaults } from "./util.ts"; @@ -32,30 +31,18 @@ export const getPage = async ( > > => { const { sid, hostName, fetch, followRename } = setDefaults(options ?? {}); - const path = `https://${hostName}/api/pages/${project}/${ - encodeTitleURI(title) - }?followRename=${followRename ?? true}`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/pages/${project}/${ + encodeTitleURI(title) + }?followRename=${followRename ?? true}`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as - | NotFoundError - | NotLoggedInError - | NotMemberError, - }; + return makeError( + req, + res, + ); } const value = (await res.json()) as Page; return { ok: true, value }; @@ -108,29 +95,17 @@ export const listPages = async ( if (sort !== undefined) params.append("sort", sort); if (limit !== undefined) params.append("limit", `${limit}`); if (skip !== undefined) params.append("skip", `${skip}`); - const path = `https://${hostName}/api/pages/${project}?${params.toString()}`; - - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/pages/${project}?${params.toString()}`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + + const res = await fetch(req); if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as - | NotFoundError - | NotLoggedInError - | NotMemberError, - }; + return makeError( + req, + res, + ); } const value = (await res.json()) as PageList; return { ok: true, value }; diff --git a/rest/profile.ts b/rest/profile.ts index b688006..4a68f2a 100644 --- a/rest/profile.ts +++ b/rest/profile.ts @@ -11,17 +11,13 @@ export const getProfile = async ( init?: BaseOptions, ): Promise => { const { sid, hostName, fetch } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/users/me`; - const res = await fetch( - path, + const request = new Request( + `https://${hostName}/api/users/me`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); - if (!res.ok) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: await res.text(), - }); + const response = await fetch(request); + if (!response.ok) { + throw new UnexpectedResponseError({ request, response }); } - return (await res.json()) as MemberUser | GuestUser; + return (await response.json()) as MemberUser | GuestUser; }; diff --git a/rest/project.ts b/rest/project.ts index 2410b47..2ca7189 100644 --- a/rest/project.ts +++ b/rest/project.ts @@ -8,8 +8,7 @@ import type { ProjectResponse, } from "../deps/scrapbox-rest.ts"; import { cookie } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { BaseOptions, Result, setDefaults } from "./util.ts"; /** get the project information @@ -27,26 +26,19 @@ export const getProject = async ( > > => { const { sid, hostName, fetch } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/projects/${project}`; - const res = await fetch( - path, + + const req = new Request( + `https://${hostName}/api/projects/${project}`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); + if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as NotFoundError | NotMemberError | NotLoggedInError, - }; + return makeError( + req, + res, + ); } const value = (await res.json()) as MemberProject | NotMemberProject; @@ -67,26 +59,16 @@ export const listProjects = async ( for (const id of projectIds) { param.append("ids", id); } - const path = `https://${hostName}/api/projects?${param.toString()}`; - const res = await fetch( - path, + + const req = new Request( + `https://${hostName}/api/projects?${param.toString()}`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); + if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as NotLoggedInError, - }; + return makeError(req, res); } const value = (await res.json()) as ProjectResponse; diff --git a/rest/replaceLinks.ts b/rest/replaceLinks.ts index 0627987..5d66942 100644 --- a/rest/replaceLinks.ts +++ b/rest/replaceLinks.ts @@ -4,8 +4,7 @@ import type { NotMemberError, } from "../deps/scrapbox-rest.ts"; import { cookie, getCSRFToken } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { ExtendedOptions, Result, setDefaults } from "./util.ts"; /** 指定したproject内の全てのリンクを書き換える @@ -31,10 +30,9 @@ export const replaceLinks = async ( > > => { const { sid, hostName, fetch, csrf } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/pages/${project}/replace/links`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/pages/${project}/replace/links`, { method: "POST", headers: { @@ -46,20 +44,13 @@ export const replaceLinks = async ( }, ); + const res = await fetch(req); + if (!res.ok) { - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as NotFoundError | NotMemberError | NotLoggedInError, - }; + return makeError( + req, + res, + ); } // messageには"2 pages have been successfully updated!"というような文字列が入っているはず diff --git a/rest/search.ts b/rest/search.ts index d5a8d23..43ec1cd 100644 --- a/rest/search.ts +++ b/rest/search.ts @@ -7,8 +7,7 @@ import type { SearchResult, } from "../deps/scrapbox-rest.ts"; import { cookie } from "./auth.ts"; -import { UnexpectedResponseError } from "./error.ts"; -import { tryToErrorLike } from "../is.ts"; +import { makeError } from "./error.ts"; import { BaseOptions, Result, setDefaults } from "./util.ts"; /** search a project for pages @@ -29,14 +28,15 @@ export const searchForPages = async ( > => { const { sid, hostName, fetch } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/pages/${project}/search/query?q=${ - encodeURIComponent(query) - }`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/pages/${project}/search/query?q=${ + encodeURIComponent(query) + }`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); + if (!res.ok) { if (res.status === 422) { return { @@ -47,19 +47,10 @@ export const searchForPages = async ( }, }; } - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { - ok: false, - value: value as NotFoundError | NotMemberError | NotLoggedInError, - }; + return makeError( + req, + res, + ); } const value = (await res.json()) as SearchResult; @@ -82,14 +73,15 @@ export const searchForJoinedProjects = async ( > => { const { sid, hostName, fetch } = setDefaults(init ?? {}); - const path = `https://${hostName}/api/projects/search/query?q=${ - encodeURIComponent(query) - }`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/projects/search/query?q=${ + encodeURIComponent(query) + }`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); + if (!res.ok) { if (res.status === 422) { return { @@ -100,16 +92,7 @@ export const searchForJoinedProjects = async ( }, }; } - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { ok: false, value: value as NotLoggedInError }; + return makeError(req, res); } const value = (await res.json()) as ProjectSearchResult; @@ -144,13 +127,13 @@ export const searchForWatchList = async ( params.append("ids", projectId); } - const path = - `https://${hostName}/api/projects/search/watch-list?${params.toString()}`; - const res = await fetch( - path, + const req = new Request( + `https://${hostName}/api/projects/search/watch-list?${params.toString()}`, sid ? { headers: { Cookie: cookie(sid) } } : undefined, ); + const res = await fetch(req); + if (!res.ok) { if (res.status === 422) { return { @@ -162,16 +145,7 @@ export const searchForWatchList = async ( }, }; } - const text = await res.text(); - const value = tryToErrorLike(text); - if (!value) { - throw new UnexpectedResponseError({ - path: new URL(path), - ...res, - body: text, - }); - } - return { ok: false, value: value as NotLoggedInError }; + return makeError(req, res); } const value = (await res.json()) as ProjectSearchResult;