Skip to content

Commit b23c7dc

Browse files
committed
Declare each error types
1 parent 47331d5 commit b23c7dc

File tree

5 files changed

+73
-79
lines changed

5 files changed

+73
-79
lines changed

graphql/login.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,22 @@ import { createMessage, type Message } from "@upyo/core";
1919
import { sql } from "drizzle-orm";
2020
import { parseTemplate } from "url-template";
2121
import { Account } from "./account.ts";
22-
import { builder, type ValuesOfEnumType } from "./builder.ts";
22+
import { builder } from "./builder.ts";
2323
import { EMAIL_FROM } from "./email.ts";
2424
import { SessionRef } from "./session.ts";
2525

2626
const logger = getLogger(["hackerspub", "graphql", "login"]);
2727

28-
const LoginErrorKind = builder.enumType("LoginErrorKind", {
29-
values: ["ACCOUNT_NOT_FOUND"] as const,
30-
});
31-
32-
class LoginError extends Error {
33-
public constructor(
34-
public readonly kind: ValuesOfEnumType<typeof LoginErrorKind>,
35-
) {
36-
super(`Login error - ${kind}`);
28+
class AccountNotFoundError extends Error {
29+
public constructor(public readonly query: string) {
30+
super(`Account not found`);
3731
}
3832
}
3933

40-
builder.objectType(LoginError, {
41-
name: "LoginError",
34+
builder.objectType(AccountNotFoundError, {
35+
name: "AccountNotFoundError",
4236
fields: (t) => ({
43-
loginErrorKind: t.field({
44-
type: LoginErrorKind,
45-
resolve: (error) => error.kind,
46-
}),
37+
query: t.exposeString("query"),
4738
}),
4839
});
4940

@@ -77,7 +68,7 @@ builder.mutationFields((t) => ({
7768
loginByUsername: t.field({
7869
type: LoginChallengeRef,
7970
errors: {
80-
types: [LoginError],
71+
types: [AccountNotFoundError],
8172
result: {
8273
name: "LoginSuccess",
8374
},
@@ -108,7 +99,7 @@ builder.mutationFields((t) => ({
10899
where: { username: args.username },
109100
});
110101
if (account == null) {
111-
throw new LoginError("ACCOUNT_NOT_FOUND");
102+
throw new AccountNotFoundError(args.username);
112103
}
113104
const token = await createSigninToken(ctx.kv, account.id);
114105
const messages: Message[] = [];
@@ -140,7 +131,7 @@ builder.mutationFields((t) => ({
140131
loginByEmail: t.field({
141132
type: LoginChallengeRef,
142133
errors: {
143-
types: [LoginError],
134+
types: [AccountNotFoundError],
144135
result: {
145136
name: "LoginSuccess",
146137
},
@@ -185,7 +176,7 @@ builder.mutationFields((t) => ({
185176
});
186177
}
187178
if (account == null) {
188-
throw new LoginError("ACCOUNT_NOT_FOUND");
179+
throw new AccountNotFoundError(args.email);
189180
}
190181
const token = await createSigninToken(ctx.kv, account.id);
191182
const messages: Message[] = [];

graphql/post.ts

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import { unreachable } from "@std/assert";
99
import { assertNever } from "@std/assert/unstable-never";
1010
import { Account } from "./account.ts";
1111
import { Actor } from "./actor.ts";
12-
import { builder, Node, type ValuesOfEnumType } from "./builder.ts";
12+
import { builder, Node } from "./builder.ts";
1313
import { Reactable } from "./reactable.ts";
14+
import { NotAuthenticatedError } from "./session.ts";
1415

1516
const PostVisibility = builder.enumType("PostVisibility", {
1617
values: [
@@ -22,30 +23,16 @@ const PostVisibility = builder.enumType("PostVisibility", {
2223
] as const,
2324
});
2425

25-
const CreateNoteErrorKind = builder.enumType("CreateNoteErrorKind", {
26-
values: [
27-
"NOT_AUTHENTICATED",
28-
"REPLY_TARGET_NOT_FOUND",
29-
"QUOTED_POST_NOT_FOUND",
30-
"NOTE_CREATION_FAILED",
31-
] as const,
32-
});
33-
34-
class CreateNoteError extends Error {
35-
public constructor(
36-
public readonly kind: ValuesOfEnumType<typeof CreateNoteErrorKind>,
37-
) {
38-
super(`Create note error - ${kind}`);
26+
class InvalidInputError extends Error {
27+
public constructor(public readonly inputPath: string) {
28+
super(`Invalid input - ${inputPath}`);
3929
}
4030
}
4131

42-
builder.objectType(CreateNoteError, {
43-
name: "CreateNoteError",
32+
builder.objectType(InvalidInputError, {
33+
name: "InvalidInputError",
4434
fields: (t) => ({
45-
createNoteErrorKind: t.field({
46-
type: CreateNoteErrorKind,
47-
resolve: (error) => error.kind,
48-
}),
35+
inputPath: t.expose("inputPath", { type: "String" }),
4936
}),
5037
});
5138

@@ -486,15 +473,18 @@ builder.relayMutationField(
486473
},
487474
{
488475
errors: {
489-
types: [CreateNoteError],
476+
types: [
477+
NotAuthenticatedError,
478+
InvalidInputError,
479+
],
490480
result: {
491481
name: "CreateNoteSuccess",
492482
},
493483
},
494484
async resolve(_root, args, ctx) {
495485
const session = await ctx.session;
496486
if (session == null) {
497-
throw new CreateNoteError("NOT_AUTHENTICATED");
487+
throw new NotAuthenticatedError();
498488
}
499489
const { visibility, content, language, replyTargetId, quotedPostId } =
500490
args.input;
@@ -505,7 +495,7 @@ builder.relayMutationField(
505495
where: { id: replyTargetId.id },
506496
});
507497
if (replyTarget == null) {
508-
throw new CreateNoteError("REPLY_TARGET_NOT_FOUND");
498+
throw new InvalidInputError("replyTargetId");
509499
}
510500
}
511501
let quotedPost: schema.Post & { actor: schema.Actor } | undefined;
@@ -515,7 +505,7 @@ builder.relayMutationField(
515505
where: { id: quotedPostId.id },
516506
});
517507
if (quotedPost == null) {
518-
throw new CreateNoteError("QUOTED_POST_NOT_FOUND");
508+
throw new InvalidInputError("quotedPostId");
519509
}
520510
}
521511
return await withTransaction(ctx.fedCtx, async (context) => {
@@ -544,7 +534,7 @@ builder.relayMutationField(
544534
{ replyTarget, quotedPost },
545535
);
546536
if (note == null) {
547-
throw new CreateNoteError("NOTE_CREATION_FAILED");
537+
throw new Error("Failed to create note");
548538
}
549539
return note;
550540
});

graphql/schema.graphql

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ input AccountLinkInput {
7878
url: URL!
7979
}
8080

81+
type AccountNotFoundError {
82+
query: String!
83+
}
84+
8185
type AccountNotificationsConnection {
8286
edges: [AccountNotificationsConnectionEdge!]!
8387
pageInfo: PageInfo!
@@ -270,17 +274,6 @@ type ArticleContent implements Node {
270274
url: URL!
271275
}
272276

273-
type CreateNoteError {
274-
createNoteErrorKind: CreateNoteErrorKind!
275-
}
276-
277-
enum CreateNoteErrorKind {
278-
NOTE_CREATION_FAILED
279-
NOT_AUTHENTICATED
280-
QUOTED_POST_NOT_FOUND
281-
REPLY_TARGET_NOT_FOUND
282-
}
283-
284277
input CreateNoteInput {
285278
clientMutationId: ID
286279
content: Markdown!
@@ -375,6 +368,10 @@ type Instance implements Node {
375368
updated: DateTime!
376369
}
377370

371+
type InvalidInputError {
372+
inputPath: String!
373+
}
374+
378375
"""
379376
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
380377
"""
@@ -390,14 +387,6 @@ type LoginChallenge {
390387
token: UUID!
391388
}
392389

393-
type LoginError {
394-
loginErrorKind: LoginErrorKind!
395-
}
396-
397-
enum LoginErrorKind {
398-
ACCOUNT_NOT_FOUND
399-
}
400-
401390
type LoginSuccess {
402391
data: LoginChallenge!
403392
}
@@ -470,14 +459,18 @@ type Mutation {
470459
updateAccount(input: UpdateAccountInput!): UpdateAccountPayload!
471460
}
472461

473-
union MutationCreateNoteResult = CreateNoteError | CreateNoteSuccess
462+
union MutationCreateNoteResult = CreateNoteSuccess | InvalidInputError | NotAuthenticatedError
474463

475-
union MutationLoginByUsernameResult = LoginError | LoginSuccess
464+
union MutationLoginByUsernameResult = AccountNotFoundError | LoginSuccess
476465

477466
interface Node {
478467
id: ID!
479468
}
480469

470+
type NotAuthenticatedError {
471+
notAuthenticated: String!
472+
}
473+
481474
type Note implements Node & Post & Reactable {
482475
actor: Actor!
483476
content: HTML!

graphql/session.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@ import type { Session } from "@hackerspub/models/session";
22
import { Account } from "./account.ts";
33
import { builder } from "./builder.ts";
44

5+
export class NotAuthenticatedError extends Error {
6+
public constructor() {
7+
super("Not authenticated");
8+
}
9+
}
10+
11+
builder.objectType(NotAuthenticatedError, {
12+
name: "NotAuthenticatedError",
13+
fields: (t) => ({
14+
notAuthenticated: t.string({
15+
resolve: () => "",
16+
}),
17+
}),
18+
});
19+
520
export const SessionRef = builder.objectRef<Session>("Session");
621

722
SessionRef.implement({

web-next/src/routes/sign/index.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
import { useLingui } from "~/lib/i18n/macro.d.ts";
2222
import { Button } from "../../components/ui/button.tsx";
2323
import type {
24-
LoginErrorKind,
2524
signByEmailMutation,
2625
} from "./__generated__/signByEmailMutation.graphql.ts";
2726
import type {
@@ -33,8 +32,8 @@ import type { signCompleteMutation } from "./__generated__/signCompleteMutation.
3332
const signByEmailMutation = graphql`
3433
mutation signByEmailMutation($locale: Locale!, $email: String!, $verifyUrl: URITemplate!) {
3534
loginByEmail(locale: $locale, email: $email, verifyUrl: $verifyUrl) {
36-
__typename
3735
... on LoginSuccess {
36+
__typename
3837
data {
3938
account {
4039
name
@@ -44,8 +43,8 @@ const signByEmailMutation = graphql`
4443
token
4544
}
4645
}
47-
... on LoginError {
48-
loginErrorKind
46+
... on AccountNotFoundError {
47+
__typename
4948
}
5049
}
5150
}
@@ -54,8 +53,8 @@ const signByEmailMutation = graphql`
5453
const signByUsernameMutation = graphql`
5554
mutation signByUsernameMutation($locale: Locale!, $username: String!, $verifyUrl: URITemplate!) {
5655
loginByUsername(locale: $locale, username: $username, verifyUrl: $verifyUrl) {
57-
__typename
5856
... on LoginSuccess {
57+
__typename
5958
data {
6059
account {
6160
name
@@ -65,8 +64,8 @@ const signByUsernameMutation = graphql`
6564
token
6665
}
6766
}
68-
... on LoginError {
69-
loginErrorKind
67+
... on AccountNotFoundError {
68+
__typename
7069
}
7170
}
7271
}
@@ -93,14 +92,20 @@ const setSessionCookie = async (sessionId: Uuid) => {
9392
return true;
9493
};
9594

95+
const enum LoginError {
96+
ACCOUNT_NOT_FOUND,
97+
UNKNOWN,
98+
}
99+
96100
export default function SignPage() {
97101
const { t, i18n } = useLingui();
98102
let emailInput: HTMLInputElement | undefined;
99103
let codeInput: HTMLInputElement | undefined;
100104
const [challenging, setChallenging] = createSignal(false);
101105
const [email, setEmail] = createSignal("");
102106
const [errorCode, setErrorCode] = createSignal<
103-
LoginErrorKind | "UNKNOWN" | undefined
107+
| LoginError
108+
| undefined
104109
>(
105110
undefined,
106111
);
@@ -170,17 +175,17 @@ export default function SignPage() {
170175
setToken(data.data.token);
171176
codeInput?.focus();
172177
} else if (
173-
data.__typename === "LoginError"
178+
data.__typename === "AccountNotFoundError"
174179
) {
175-
setErrorCode(data.loginErrorKind);
180+
setErrorCode(LoginError.ACCOUNT_NOT_FOUND);
176181
} else {
177182
onError();
178183
}
179184
}
180185

181186
function onError() {
182187
setChallenging(false);
183-
setErrorCode("UNKNOWN");
188+
setErrorCode(LoginError.UNKNOWN);
184189
setToken(undefined);
185190
}
186191

@@ -193,7 +198,7 @@ export default function SignPage() {
193198
}
194199

195200
switch (currentErrorCode) {
196-
case "ACCOUNT_NOT_FOUND":
201+
case LoginError.ACCOUNT_NOT_FOUND:
197202
return t`No such account in Hackers' Pub—please try again.`;
198203
case undefined:
199204
case null:

0 commit comments

Comments
 (0)