From c1bddb47c4ae62f3bb31b563d4d07db83a1258cd Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Mon, 8 Jun 2026 21:27:21 +0200 Subject: [PATCH] refactor(web): extract API error classes to api/errors.ts (#65) --- web/src/api/error-message.ts | 2 +- web/src/api/errors.ts | 28 ++++++++++++++++++++++++++++ web/src/api/queries.ts | 30 ++---------------------------- 3 files changed, 31 insertions(+), 29 deletions(-) create mode 100644 web/src/api/errors.ts diff --git a/web/src/api/error-message.ts b/web/src/api/error-message.ts index b4538a3..1ad45e2 100644 --- a/web/src/api/error-message.ts +++ b/web/src/api/error-message.ts @@ -1,4 +1,4 @@ -import { HttpError, InUseError } from "./queries"; +import { HttpError, InUseError } from "./errors"; /** Maps a caught mutation error to an i18n key (+ interpolation opts). The single * source of truth shared by the global toast fallback and every inline display. */ diff --git a/web/src/api/errors.ts b/web/src/api/errors.ts new file mode 100644 index 0000000..32b2a8f --- /dev/null +++ b/web/src/api/errors.ts @@ -0,0 +1,28 @@ +export class HttpError extends Error { + constructor(public readonly status: number) { + super(`HTTP ${status}`); + this.name = "HttpError"; + } +} + +export class FieldRejection extends Error { + constructor(public readonly field: string, public readonly code: string) { + super(`field rejected: ${field}`); + this.name = "FieldRejection"; + } +} + +export class InUseError extends Error { + constructor(public readonly count: number) { + super(`in use: ${count}`); + this.name = "InUseError"; + } +} + +/** Error carrying the HTTP status so callers can branch 422-gate vs 409-illegal. */ +export class VisibilityError extends Error { + constructor(public status: number) { + super(`visibility change failed (${status})`); + this.name = "VisibilityError"; + } +} diff --git a/web/src/api/queries.ts b/web/src/api/queries.ts index b004e4f..d0a37ba 100644 --- a/web/src/api/queries.ts +++ b/web/src/api/queries.ts @@ -2,27 +2,9 @@ import { keepPreviousData, useInfiniteQuery, useMutation, useQuery, useQueryClie import { api } from "./client"; import type { components } from "./schema"; +import { HttpError, FieldRejection, InUseError, VisibilityError } from "./errors"; -export class HttpError extends Error { - constructor(public readonly status: number) { - super(`HTTP ${status}`); - this.name = "HttpError"; - } -} - -export class FieldRejection extends Error { - constructor(public readonly field: string, public readonly code: string) { - super(`field rejected: ${field}`); - this.name = "FieldRejection"; - } -} - -export class InUseError extends Error { - constructor(public readonly count: number) { - super(`in use: ${count}`); - this.name = "InUseError"; - } -} +export { HttpError, FieldRejection, InUseError, VisibilityError } from "./errors"; type UserView = components["schemas"]["UserView"]; type LoginRequest = components["schemas"]["LoginRequest"]; @@ -390,14 +372,6 @@ export function useCreateFieldDefinition() { type Visibility = "draft" | "internal" | "public"; -/** Error carrying the HTTP status so callers can branch 422-gate vs 409-illegal. */ -export class VisibilityError extends Error { - constructor(public status: number) { - super(`visibility change failed (${status})`); - this.name = "VisibilityError"; - } -} - export function useSetVisibility() { const qc = useQueryClient();