Initial Commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# `OPENAPI_UI_SCALAR.*` error-code registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module owns the stable error-code registry for the
|
||||
`@suaveplan/api-openapi-ui-scalar` package. The registry is built via
|
||||
`defineCodes` so every key projects to a frozen `OPENAPI_UI_SCALAR.<NAME>`
|
||||
string the rest of the suite (logger, error bus, OTel attributes) can
|
||||
discriminate on. `OpenapiUiScalarError` extends the shared
|
||||
`SuaveplanError` base so call sites can branch on
|
||||
`instanceof OpenapiUiScalarError` or — more durably — on `error.code`.
|
||||
|
||||
Each code maps to a single production throw site that is exercised in the
|
||||
test suite by a real failure path, not a manual rethrow.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen `OPENAPI_UI_SCALAR_CODES` registry with three stable keys:
|
||||
`ASSETS_MISSING`, `INVALID_OPTIONS`, `ASSET_NOT_REGISTERED`.
|
||||
- Typed `OpenapiUiScalarCode` union derived from the registry values.
|
||||
- `OpenapiUiScalarError` subclass carrying the typed `code` + `scalarCode`
|
||||
twin so consumers can pattern-match without a runtime `as` cast.
|
||||
- Cause chain + context bag honoured via the shared base — useful for
|
||||
wrapping the underlying `ZodError` on `INVALID_OPTIONS`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
OPENAPI_UI_SCALAR_CODES,
|
||||
OpenapiUiScalarError,
|
||||
} from "@suaveplan/api-openapi-ui-scalar/error-codes";
|
||||
|
||||
throw new OpenapiUiScalarError(
|
||||
OPENAPI_UI_SCALAR_CODES.ASSETS_MISSING,
|
||||
"Scalar bundled asset is not present"
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
OPENAPI_UI_SCALAR_CODES,
|
||||
OpenapiUiScalarError,
|
||||
} from "@suaveplan/api-openapi-ui-scalar/error-codes";
|
||||
|
||||
try {
|
||||
parseHostConfig(input);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof OpenapiUiScalarError &&
|
||||
err.code === OPENAPI_UI_SCALAR_CODES.INVALID_OPTIONS
|
||||
) {
|
||||
// Surface the underlying ZodError issues to the operator console.
|
||||
logger.error("Scalar UI host config rejected", {
|
||||
cause: err.cause,
|
||||
issues: err.context?.issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `OPENAPI_UI_SCALAR_CODES` — frozen object with the three code keys.
|
||||
- `OpenapiUiScalarCode` — typed union over the code values.
|
||||
- `OpenapiUiScalarError` — `SuaveplanError<OpenapiUiScalarCode>` subclass.
|
||||
Constructor signature: `(code, message, options?)`. `options` carries
|
||||
`cause` and `context` per the shared error contract.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The registry is intentionally closed — adding a new code requires an
|
||||
OpenSpec change so the audit + dashboards (`openapi_ui_scalar.errors_total`
|
||||
labels) get refreshed in lockstep. The `error-codes.test.ts` conformance
|
||||
file walks every registry entry and asserts a corresponding production
|
||||
throw site lives in the package, so dead codes can never accumulate.
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
OPENAPI_UI_SCALAR_CODES,
|
||||
OpenapiUiScalarError,
|
||||
} from "./error-codes.js";
|
||||
|
||||
describe("OPENAPI_UI_SCALAR codes", () => {
|
||||
it("exposes the three stable codes", { timeout: 5000 }, () => {
|
||||
expect(OPENAPI_UI_SCALAR_CODES.ASSETS_MISSING).toBe(
|
||||
"OPENAPI_UI_SCALAR.ASSETS_MISSING"
|
||||
);
|
||||
expect(OPENAPI_UI_SCALAR_CODES.INVALID_OPTIONS).toBe(
|
||||
"OPENAPI_UI_SCALAR.INVALID_OPTIONS"
|
||||
);
|
||||
expect(OPENAPI_UI_SCALAR_CODES.ASSET_NOT_REGISTERED).toBe(
|
||||
"OPENAPI_UI_SCALAR.ASSET_NOT_REGISTERED"
|
||||
);
|
||||
});
|
||||
|
||||
it("error carries the code in both fields", { timeout: 5000 }, () => {
|
||||
const err = new OpenapiUiScalarError(
|
||||
OPENAPI_UI_SCALAR_CODES.ASSETS_MISSING,
|
||||
"missing",
|
||||
{ context: { mode: "bundled" } }
|
||||
);
|
||||
expect(err).toBeInstanceOf(OpenapiUiScalarError);
|
||||
expect(err.code).toBe(OPENAPI_UI_SCALAR_CODES.ASSETS_MISSING);
|
||||
expect(err.scalarCode).toBe(OPENAPI_UI_SCALAR_CODES.ASSETS_MISSING);
|
||||
expect(err.name).toBe("OpenapiUiScalarError");
|
||||
expect(err.context?.mode).toBe("bundled");
|
||||
});
|
||||
|
||||
it("supports the cause chain on the shared base", { timeout: 5000 }, () => {
|
||||
const root = new Error("root");
|
||||
const err = new OpenapiUiScalarError(
|
||||
OPENAPI_UI_SCALAR_CODES.INVALID_OPTIONS,
|
||||
"boom",
|
||||
{ cause: root }
|
||||
);
|
||||
expect(err.cause).toBe(root);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { SuaveplanErrorOptions } from "@suaveplan/error";
|
||||
import { defineCodes, SuaveplanError } from "@suaveplan/error";
|
||||
|
||||
/**
|
||||
* Frozen `OPENAPI_UI_SCALAR.*` error-code registry for the Scalar UI
|
||||
* adapter. Every code listed here is thrown by a production path in this
|
||||
* package and verified by `error-codes.test.ts` walking the registry.
|
||||
*
|
||||
* - `ASSETS_MISSING` — `host.openapi.uiAssets = "bundled"` was selected
|
||||
* but no bundled Scalar runtime exists under `dist/assets/`. Raised at
|
||||
* handler construction so an air-gapped host fails fast at boot rather
|
||||
* than at first request.
|
||||
* - `INVALID_OPTIONS` — the `ScalarUiOptions` Zod schema rejected the
|
||||
* caller-supplied configuration. Wrapped on top of the underlying
|
||||
* `ZodError` (`.cause`) so callers can inspect the exact field failure.
|
||||
* - `ASSET_NOT_REGISTERED` — internal asset lookup mis-fire for the
|
||||
* bundled-asset registry; surfaced when a callsite asks for an asset
|
||||
* name not present in `SCALAR_ASSETS`.
|
||||
*/
|
||||
export const OPENAPI_UI_SCALAR_CODES = defineCodes("OPENAPI_UI_SCALAR", [
|
||||
"ASSETS_MISSING",
|
||||
"INVALID_OPTIONS",
|
||||
"ASSET_NOT_REGISTERED",
|
||||
] as const);
|
||||
|
||||
/** Union of every code resolved through {@link OPENAPI_UI_SCALAR_CODES}. */
|
||||
export type OpenapiUiScalarCode =
|
||||
(typeof OPENAPI_UI_SCALAR_CODES)[keyof typeof OPENAPI_UI_SCALAR_CODES];
|
||||
|
||||
/**
|
||||
* Concrete error class for the api-openapi-ui-scalar package. Extends
|
||||
* {@link SuaveplanError} so callers can discriminate via
|
||||
* `error instanceof OpenapiUiScalarError` or the stable `error.code`.
|
||||
*/
|
||||
export class OpenapiUiScalarError extends SuaveplanError<OpenapiUiScalarCode> {
|
||||
public readonly scalarCode: OpenapiUiScalarCode;
|
||||
|
||||
public constructor(
|
||||
code: OpenapiUiScalarCode,
|
||||
message: string,
|
||||
options?: SuaveplanErrorOptions
|
||||
) {
|
||||
super(code, message, options);
|
||||
this.name = "OpenapiUiScalarError";
|
||||
this.scalarCode = code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
OPENAPI_UI_SCALAR_CODES,
|
||||
type OpenapiUiScalarCode,
|
||||
OpenapiUiScalarError,
|
||||
} from "./error-codes.js";
|
||||
Reference in New Issue
Block a user