first commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# error-codes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The error-codes module owns the stable `AUTH.*` code registry and the
|
||||
`AuthError` class used by every production failure path inside
|
||||
`@suaveplan/api-plugin-auth`. Consumers branch on the namespaced string
|
||||
literal (e.g. `error.code === "AUTH.UNAUTHENTICATED"`) — the registry
|
||||
exists so the strings never drift across the plugin, its consumers, and
|
||||
the OpenAPI error responses surfaced by `@suaveplan/api`.
|
||||
|
||||
## Features
|
||||
|
||||
- 10 namespaced `AUTH.*` codes built via `@suaveplan/error`'s
|
||||
`defineCodes("AUTH", […])` — type-safe at compile time and stable at
|
||||
runtime.
|
||||
- `AuthError` extends `SuaveplanError<AuthErrorCode>` so
|
||||
`instanceof AuthError` narrows `error.code` to the union.
|
||||
- Codes carry HTTP status via `context.statusCode` — the
|
||||
`@suaveplan/api` error-handler reads that field and translates to the
|
||||
matching response code (401 / 403 / 500 / 503).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { AUTH_CODES, AuthError } from "@suaveplan/api-plugin-auth/error-codes/error-codes";
|
||||
|
||||
throw new AuthError(AUTH_CODES.UNAUTHENTICATED, "Login required", {
|
||||
context: { statusCode: 401 },
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Pass `context.required` on a role-check failure so callers and the
|
||||
OpenAPI 403 response can show which roles satisfy the gate. Carry the
|
||||
underlying ZodError as `cause` on input-validation failures so the
|
||||
toJSON pipeline preserves it on the wire:
|
||||
|
||||
```ts
|
||||
import { AUTH_CODES, AuthError } from "@suaveplan/api-plugin-auth/error-codes/error-codes";
|
||||
import z from "zod";
|
||||
|
||||
const parsed = z.object({ email: z.string().email() }).safeParse(input);
|
||||
if (!parsed.success) {
|
||||
throw new AuthError(AUTH_CODES.INVALID_INPUT, "Invalid sign-up body", {
|
||||
cause: parsed.error,
|
||||
context: { statusCode: 400, issues: parsed.error.issues },
|
||||
});
|
||||
}
|
||||
|
||||
const principal = c.get("user");
|
||||
if (!principal) {
|
||||
throw new AuthError(AUTH_CODES.UNAUTHENTICATED, "Login required", {
|
||||
context: { statusCode: 401 },
|
||||
});
|
||||
}
|
||||
const required = ["admin", "owner"] as const;
|
||||
if (!principal.roles.some((r) => required.includes(r as never))) {
|
||||
throw new AuthError(AUTH_CODES.INSUFFICIENT_ROLE, "Forbidden", {
|
||||
context: { statusCode: 403, required: [...required] },
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AUTH_CODES`
|
||||
|
||||
`Record<AuthCodeKey, "AUTH.<KEY>">` — frozen mapping built with
|
||||
`defineCodes`. Keys: `UNAUTHENTICATED`, `INSUFFICIENT_ROLE`,
|
||||
`EMAIL_NOT_VERIFIED`, `SESSION_RESOLVE_FAILED`,
|
||||
`PRINCIPAL_MAPPING_FAILED`, `BETTER_AUTH_INIT_FAILED`, `INVALID_INPUT`,
|
||||
`ROUTE_PREFIX_COLLISION`, `SCHEMA_APPLY_FAILED`, `SERVICE_NOT_AVAILABLE`.
|
||||
|
||||
### `AuthErrorCode`
|
||||
|
||||
Discriminated string union, `"AUTH.UNAUTHENTICATED" | …` — the type of
|
||||
`AUTH_CODES[key]`.
|
||||
|
||||
### `class AuthError extends SuaveplanError<AuthErrorCode>`
|
||||
|
||||
- `new AuthError(code, message, options?)` — `code` is an `AuthErrorCode`;
|
||||
`options` is the `SuaveplanErrorOptions` shape (`{ context?, cause?,
|
||||
traceId?, clock? }`). The constructor is `public` so any plugin module
|
||||
may throw directly.
|
||||
- `error.authCode` — convenience field that narrows to `AuthErrorCode`
|
||||
even when the value is treated as a plain `SuaveplanError`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The full code list lives in this file. Adding a new code requires
|
||||
authoring a new OpenSpec change so the spec delta, registry, and call
|
||||
sites move together.
|
||||
- `context.statusCode` is non-normative on `SuaveplanError` (the field
|
||||
is read by `@suaveplan/api`'s error-handler); the auth plugin always
|
||||
attaches it for codes that map cleanly to an HTTP response so the
|
||||
served error matches the route's OpenAPI schema.
|
||||
- `SERVICE_NOT_AVAILABLE` lists missing service names in
|
||||
`context.missing` so operators reading logs / span events can see
|
||||
exactly which `ctx.getService(...)` call returned `undefined`.
|
||||
Reference in New Issue
Block a user