Skip to content

Commit

Permalink
Merge pull request #8 from takker99:stop-wrapping
Browse files Browse the repository at this point in the history
feat: Stop convert `Promise<Response>` into Result object and simply add type annotations to `Response` instead
  • Loading branch information
takker99 authored Dec 23, 2024
2 parents 33d0759 + 7b77dd6 commit 9caca3a
Show file tree
Hide file tree
Showing 14 changed files with 562 additions and 300 deletions.
17 changes: 6 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
name: ci

env:
DENO_VERSION: 1.x

on: [push, pull_request]

env:
DENO_VERSION: 2.x

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Deno
uses: denoland/setup-deno@v1
- uses: denoland/setup-deno@v2
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Check fmt
run: deno task fmt --check
- name: Run lint
run: deno task lint
- name: Run type check
run: deno task check
- name: Check all
run: deno task check
13 changes: 6 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
# cf. https://jsr.io/@core/unknownutil/3.18.1/.github/workflows/jsr.yml
name: publish

env:
DENO_VERSION: 1.x
DENO_VERSION: 2.x

on:
push:
tags:
- '*'
- "*"

permissions:
contents: read
id-token: write # The OIDC ID token is used for authentication with JSR.

id-token: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Deno
uses: denoland/setup-deno@v1
- uses: denoland/setup-deno@v2
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Publish on tag
run: deno task publish
run: deno run --allow-env --allow-run=deno --allow-read --allow-write=deno.jsonc jsr:@david/publish-on-tag@0.1.4
41 changes: 41 additions & 0 deletions .github/workflows/update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Update

on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:

env:
DENO_VERSION: 2.x

jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Update dependencies
run: |
deno outdated --update &> ../output.txt
env:
NO_COLOR: 1
- name: Read ../output.txt
id: log
uses: juliangruber/read-file-action@v1
with:
path: ../output.txt
- uses: peter-evans/create-pull-request@v7
with:
commit-message: "chore: Update Deno dependencies"
title: "chore: Update Deno dependencies"
body: |
The output of `deno outdated --update` is
```
${{ steps.log.outputs.content }}
```
branch: update-deno-dependencies
delete-branch: true
sign-commits: true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
docs/
coverage/
26 changes: 15 additions & 11 deletions deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
{
"exclude": [
"docs/",
"coverage/"
],
"exports": "./mod.ts",
"imports": {
"@std/http": "jsr:@std/http@^1.0.12",
"@std/json": "jsr:@std/json@^1.0.1",
"@std/testing": "jsr:@std/testing@^1.0.8"
},
"name": "@takker/gyazo",
"version": "0.0.0",
"tasks": {
// cf. https://github.com/jsr-core/unknownutil/blob/v3.18.1/deno.jsonc
"check": "deno check --remote **/*.ts",
"fmt": "deno fmt",
"lint": "deno lint",
"check:all": "deno task fmt && deno task lint && deno task check",
"update": "deno run --allow-env --allow-read --allow-write=. --allow-run=git,deno --allow-net=jsr.io,registry.npmjs.org jsr:@molt/cli ./*.ts",
"update:commit": "deno task -q update --commit --pre-commit=fmt,lint",
"publish": "deno run --allow-env --allow-run=deno --allow-read --allow-write=deno.jsonc jsr:@david/publish-on-tag@0.1.x"
"check": "deno fmt --check && deno lint && deno doc --lint mod.ts && deno test --doc --parallel --shuffle && deno publish --dry-run",
"coverage": "deno test --allow-read=./ --parallel --shuffle --coverage && deno coverage --html",
"doc": "deno doc --html mod.ts",
"fix": "deno fmt && deno lint --fix && deno doc --lint mod.ts && deno test --doc --parallel --shuffle && deno publish --dry-run --allow-dirty"
},
"imports": { "result": "npm:option-t@^49.1.0/plain_result" },
"exports": "./mod.ts"
"version": "0.0.0"
}
30 changes: 19 additions & 11 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

143 changes: 5 additions & 138 deletions error.ts
Original file line number Diff line number Diff line change
@@ -1,140 +1,7 @@
import { isObject } from "./util.ts";
import { createErr, createOk, type Result } from "result";

export class UnexpectedResponseError extends Error {
name = "UnexpectedResponseError";
status: number;
statusText: string;
body: string;
path: URL;

constructor(
init: { status: number; statusText: string; body: string; path: URL },
) {
super(
`${init.status} ${init.statusText} when fetching ${init.path.toString()}`,
);

this.status = init.status;
this.statusText = init.statusText;
this.body = init.body;
this.path = init.path;

// @ts-ignore only available on V8
if (Error.captureStackTrace) {
// @ts-ignore only available on V8
Error.captureStackTrace(this, UnexpectedResponseError);
}
}
}

export const isUnexpectedResponseError = (
e: unknown,
): e is UnexpectedResponseError =>
(e instanceof Error) && e.name === "UnexpectedResponseError";

/** 400のときのerror object */
export interface BadRequestError {
name: "BadRequestError";
message: string;
}
/** 401のときのerror object */
export interface UnauthorizedError {
name: "UnauthorizedError";
message: string;
}
/** 403のときのerror object */
export interface NotPrivilegeError {
name: "NotPrivilegeError";
message: string;
}
/** 404のときのerror object */
export interface NotFoundError {
name: "NotFoundError";
message: string;
}
/** 422のときのerror object */
export interface InvalidParameterError {
name: "InvalidParameterError";
message: string;
}
/** 429のときのerror object */
export interface RateLimitError {
name: "RateLimitError";
/**
* Gyazo API Error
*/
export interface GyazoAPIError {
/** error message */
message: string;
}
export type GyazoAPIError =
| BadRequestError
| UnauthorizedError
| NotPrivilegeError
| NotFoundError
| InvalidParameterError
| RateLimitError;

/** responseが正常かどうかを確かめる
*
* @param response 確かめたいResponse
* @return 変換できたらそのobjectを返す
* @throws {UnexpectedResponseError}
*/
export const checkResponse = async (
response: Response,
): Promise<
Result<
string,
GyazoAPIError
>
> => {
const text = await response.text();
if (response.ok) return createOk(text);

if (response.status === 400) {
return createErr({ name: "BadRequestError", message: text });
}

try {
const json: unknown = JSON.parse(text);
if (!isObject(json) || typeof json.message !== "string") {
throw new UnexpectedResponseError({
status: response.status,
statusText: response.statusText,
body: text,
path: new URL(response.url),
});
}

switch (response.status) {
case 401:
return createErr({ name: "UnauthorizedError", message: json.message });
case 403:
return createErr({ name: "NotPrivilegeError", message: json.message });
case 404:
return createErr({ name: "NotFoundError", message: json.message });
case 422:
return createErr({
name: "InvalidParameterError",
message: json.message,
});
case 429:
return createErr({ name: "RateLimitError", message: json.message });
default:
throw new UnexpectedResponseError({
status: response.status,
statusText: response.statusText,
body: text,
path: new URL(response.url),
});
}
} catch (e2: unknown) {
if (e2 instanceof SyntaxError) {
throw new UnexpectedResponseError({
status: response.status,
statusText: response.statusText,
body: text,
path: new URL(response.url),
});
}
// JSONのparse error以外はそのまま投げる
throw e2;
}
};
53 changes: 38 additions & 15 deletions getProfile.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,55 @@
import { type OAuthOptions, setDefaults } from "./util.ts";
import { checkResponse, type GyazoAPIError } from "./error.ts";
import { createOk, isErr, type Result, unwrapOk } from "result";
import type {
ClientErrorStatus,
StatusCode,
SuccessfulStatus,
} from "@std/http/status";
import type { ResponseOfEndpoint } from "./targeted_response.ts";
import type { GyazoAPIError } from "./error.ts";

/** Gyazo account profile */
export interface Profile {
/* email*/
/** email */
email: string;
/* user name */
/** user name */
name: string;
/* user id */
/** user id */
uid: string;
/* user profile image */
/** user profile image */
profile_image: string;
}

/** get user profile
/** get a Gyazo user profile
*
* @param init accessTokeなど
* @see https://gyazo.com/api/docs/user#user
*
* @param init accessToken etc.
*/
export const getProfile = async (
init: OAuthOptions,
): Promise<Result<Profile, GyazoAPIError>> => {
export const getProfile = <R extends Response | undefined>(
init: OAuthOptions<R>,
): Promise<
| ResponseOfEndpoint<
& { 200: Profile }
& Record<ClientErrorStatus, GyazoAPIError>
& Record<Exclude<StatusCode, SuccessfulStatus | ClientErrorStatus>, string>
>
| (undefined extends R ? undefined : never)
> => {
const { accessToken, fetch } = setDefaults(init ?? {});

const path = `https://api.gyazo.com/api/users/me?access_token=${accessToken}`;
const res = await fetch(path);

const checked = await checkResponse(res);
if (isErr(checked)) return checked;
return createOk(JSON.parse(unwrapOk(checked)) as Profile);
return fetch(path) as Promise<
| ResponseOfEndpoint<
& {
200: Profile;
}
& Record<ClientErrorStatus, GyazoAPIError>
& Record<
Exclude<StatusCode, SuccessfulStatus | ClientErrorStatus>,
string
>
>
| (undefined extends R ? undefined : never)
>;
};
Loading

0 comments on commit 9caca3a

Please sign in to comment.