@tetsujs/openapi 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,6 +75,7 @@ the hash, to be prefixed with `sha384-`.
75
75
  | `assets` | the renderer on jsDelivr, pinned | your own renderer URLs, with `integrity` hashes |
76
76
  | `documentSelf` | `false` | include `/openapi.json` and `/docs` in the document |
77
77
  | `onWarning` | `console.warn` | receives what could not be described |
78
+ | `errors` | the framework's envelope | an error format of your own — see [below](#an-error-format-of-your-own) |
78
79
 
79
80
  ## What goes into the document
80
81
 
@@ -151,6 +152,99 @@ When every alternative of a status is an envelope, the union carries a
151
152
  `discriminator` on `error`, with the mapping from each code to its
152
153
  definition, and a generated client narrows on the code.
153
154
 
155
+ A status is described by what answers with it. The route's part is the
156
+ `description` of the schema it declares — `.describe()` in Zod and
157
+ ArkType, `v.description()` in Valibot, `{ description }` in TypeBox — for
158
+ any status, a `200` as much as a `404`; a union described as a whole is
159
+ one description, one described branch by branch is one per branch. A
160
+ hook's part is its `documented()` description, the framework's its own.
161
+ One description is the status's description; several are a list, each
162
+ led by its code:
163
+
164
+ ```md
165
+ - `ACCOUNT_DISABLED`: this account is disabled
166
+ - `CAPTCHA_FAILED`: the captcha token is missing or did not pass
167
+ ```
168
+
169
+ A status nothing describes keeps its reason phrase — `Not Found`,
170
+ `Successful response`.
171
+
172
+ ## An error format of your own
173
+
174
+ Every failure reaches the application's `onError` hooks — a thrown
175
+ `HttpError`, a validation or body failure, a `404`, a `405`, a rate limit's
176
+ refusal — so one hook answers all of them in your format:
177
+
178
+ ```ts
179
+ const inOurFormat = hook.onError(({ error }) => {
180
+ if (!(error instanceof HttpError)) return undefined;
181
+
182
+ const { status, error: code, ...rest } = error.body as ErrorBody;
183
+
184
+ return Response.json({ code, ...rest }, { status });
185
+ });
186
+ ```
187
+
188
+ The document is generated from the routes, not from that hook, so it is
189
+ told the same format with `errors`:
190
+
191
+ ```ts
192
+ const errors: ErrorFormat = {
193
+ schema: ({ error, message, fields }) => ({
194
+ type: "object",
195
+ required: ["code", "message", ...Object.keys(fields)],
196
+ properties: {
197
+ code: error ? { type: "string", const: error } : { type: "string" },
198
+ message: { type: "string", ...(message ? { examples: [message] } : {}) },
199
+ ...fields,
200
+ },
201
+ }),
202
+ discriminator: "code",
203
+ };
204
+
205
+ createApp({
206
+ hooks: { onError: [inOurFormat] },
207
+ routes: [api, docs({ info, errors })],
208
+ });
209
+ ```
210
+
211
+ `schema` describes one failure from what the generator knows of it: its
212
+ status, its code when known, an example message, and in `fields` what it
213
+ carries besides — `issues` for a validation failure, `retryAfter` for a
214
+ rate limit, a hook's own `fields`. It is used for the framework's failures
215
+ and the hooks' refusals alike.
216
+
217
+ `discriminator` names the top-level field that holds the code. Statuses of
218
+ envelopes are then discriminated on it, and a route's own envelope is
219
+ recognized by it — a `{ code: "ITEM_NOT_FOUND", … }` the route declares is
220
+ the same definition as a hook's. A format that nests its code has no
221
+ top-level field to name; it gives `code` instead, reading the code from a
222
+ route's schema:
223
+
224
+ ```ts
225
+ code: (schema) => {
226
+ const error = schema.properties?.error;
227
+ const code = typeof error === "object" ? error.properties?.code : undefined;
228
+
229
+ return typeof code === "object" && typeof code.const === "string" ? code.const : undefined;
230
+ },
231
+ ```
232
+
233
+ The hook and `errors` describe one format in two places — the core knows
234
+ nothing about documents, and a function cannot be read for the shape it
235
+ returns — so keep a test that holds them together: provoke a failure and
236
+ check its body against the document, with the JSON Schema validator of
237
+ your choice.
238
+
239
+ ```ts
240
+ test("a validation failure is what the document says", async () => {
241
+ const res = await request("/items", { method: "POST", body: "{}" });
242
+ const schema = document.components?.schemas?.ValidationFailed;
243
+
244
+ expect(validator.validate(schema, await res.json())).toBe(true);
245
+ });
246
+ ```
247
+
154
248
  ## Documenting hooks
155
249
 
156
250
  A hook that answers by itself — an auth check, a limiter — can say so, and
@@ -15,6 +15,7 @@
15
15
  import type { App, RouteDef, SchemaConfig, ValidatePath } from "@tetsujs/core";
16
16
  import { onMount } from "@tetsujs/core";
17
17
  import type { OpenApiDocument } from "./document.ts";
18
+ import type { ErrorFormat } from "./errors.ts";
18
19
  import type { GeneratorWarning } from "./index.ts";
19
20
  import type { DocsAssets, DocsUi } from "./page.ts";
20
21
  /** What the documentation controller serves, and from where. */
@@ -23,6 +24,11 @@ export interface DocsOptions<Path extends string, UiPath extends string, Ui exte
23
24
  readonly info: OpenApiDocument["info"];
24
25
  /** Servers the API is reachable at. */
25
26
  readonly servers?: OpenApiDocument["servers"];
27
+ /**
28
+ * How the application's errors look, when not like the framework's
29
+ * envelope — see `errors` on {@link openapi}.
30
+ */
31
+ readonly errors?: ErrorFormat;
26
32
  /** Where the document is served. Defaults to `/openapi.json`. */
27
33
  readonly path?: Path & ValidatePath<Path>;
28
34
  /** Where the page is served. Defaults to `/docs`. */
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAS,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGpD,gEAAgE;AAChE,MAAM,WAAW,WAAW,CAC1B,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,MAAM,EACrB,EAAE,SAAS,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK;IAE1C,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAEvC,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAE9C,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAE1C,qDAAqD;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEhD;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;IAEjB,uEAAuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAE7B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,qBAAa,cAAc,CACzB,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,MAAM,EACrB,IAAI,SAAS,OAAO,GAAG,IAAI;IAezB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAb1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAErE,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,IAAI,SAAS,IAAI,GAC1B,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,GACvD,SAAS,CAAC;IAEd,OAAO,CAAC,QAAQ,CAA8B;IAE9C,OAAO,CAAC,IAAI,CAAqB;IAEjC,YACmB,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EACnD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EAkCf;IAED;;;;;;;;OAQG;IACH,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA0BxB;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,IAAI,CAClB,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,eAAe,EAC3C,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG,OAAO,EACrC,KAAK,CAAC,EAAE,SAAS,MAAM,GAAG,KAAK,GAAG,QAAQ,EAE1C,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,GACrC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC,CAM/D"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAS,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGpD,gEAAgE;AAChE,MAAM,WAAW,WAAW,CAC1B,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,MAAM,EACrB,EAAE,SAAS,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK;IAE1C,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAEvC,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAE9C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAE9B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAE1C,qDAAqD;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEhD;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;IAEjB,uEAAuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAE7B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,qBAAa,cAAc,CACzB,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,MAAM,EACrB,IAAI,SAAS,OAAO,GAAG,IAAI;IAezB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAb1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAErE,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,IAAI,SAAS,IAAI,GAC1B,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,GACvD,SAAS,CAAC;IAEd,OAAO,CAAC,QAAQ,CAA8B;IAE9C,OAAO,CAAC,IAAI,CAAqB;IAEjC,YACmB,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EACnD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EAkCf;IAED;;;;;;;;OAQG;IACH,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA2BxB;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,IAAI,CAClB,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,eAAe,EAC3C,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG,OAAO,EACrC,KAAK,CAAC,EAAE,SAAS,MAAM,GAAG,KAAK,GAAG,QAAQ,EAE1C,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,GACrC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC,CAM/D"}
@@ -15,6 +15,7 @@ export interface JsonSchemaObject {
15
15
  required?: unknown;
16
16
  anyOf?: unknown;
17
17
  oneOf?: unknown;
18
+ description?: unknown;
18
19
  [keyword: string]: unknown;
19
20
  }
20
21
  /** Which shape of a schema to describe: what arrives, or what leaves. */
@@ -1 +1 @@
1
- {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/emit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,yEAAyE;AACzE,KAAK,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,SAAS,GAAE,SAAoB,GAC9B,gBAAgB,GAAG,SAAS,CAkB9B"}
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/emit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,yEAAyE;AACzE,KAAK,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,SAAS,GAAE,SAAoB,GAC9B,gBAAgB,GAAG,SAAS,CAkB9B"}
@@ -20,14 +20,21 @@
20
20
  * read first, before any operation is built, so the winner does not depend
21
21
  * on which route happens to come first in the table.
22
22
  *
23
+ * What an envelope looks like — how one is described, where its code is,
24
+ * what a client discriminates on — is the document's error format, which
25
+ * the registry carries for everything that builds or reads one.
26
+ *
23
27
  * Internal to the package.
24
28
  *
25
29
  * @module
26
30
  */
27
31
  import type { SchemaComponents, SchemaRef } from "./components.ts";
28
32
  import type { JsonSchemaObject } from "./emit.ts";
33
+ import type { ResolvedFormat } from "./errors.ts";
29
34
  /** The definitions of every envelope of one document. */
30
35
  export interface Envelopes {
36
+ /** The document's error format. */
37
+ readonly format: ResolvedFormat;
31
38
  /**
32
39
  * Records a route's definition, before anything is referenced. The first
33
40
  * route to declare a status and code defines it.
@@ -48,16 +55,7 @@ export interface Envelopes {
48
55
  */
49
56
  codeOf(ref: string): string | undefined;
50
57
  }
51
- export declare function envelopes(components: SchemaComponents): Envelopes;
52
- /**
53
- * The error code of a schema that is an envelope: an object whose `error`
54
- * is required and holds one string — a `const`, or an `enum` of one value.
55
- *
56
- * Read off what the validator emitted, not off a declaration: the route
57
- * has no other way to say which of its responses is an envelope, and the
58
- * shape is what a client would branch on anyway.
59
- */
60
- export declare function envelopeCode(schema: JsonSchemaObject): string | undefined;
58
+ export declare function envelopes(components: SchemaComponents, format: ResolvedFormat): Envelopes;
61
59
  /**
62
60
  * The alternatives a schema stands for, as a list the status's own union
63
61
  * can take in.
@@ -70,5 +68,5 @@ export declare function envelopeCode(schema: JsonSchemaObject): string | undefin
70
68
  * beside its union, a `title` or a `$defs`, is left whole: those keywords
71
69
  * belong to the union, and would be lost with it.
72
70
  */
73
- export declare function branchesOf(schema: JsonSchemaObject): JsonSchemaObject[];
71
+ export declare function branchesOf(schema: JsonSchemaObject, code: (schema: JsonSchemaObject) => string | undefined): JsonSchemaObject[];
74
72
  //# sourceMappingURL=envelopes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"envelopes.d.ts","sourceRoot":"","sources":["../../src/envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,yDAAyD;AACzD,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAEtE;;;;;;;OAOG;IACH,GAAG,CACD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,SAAS,CAAC;IAEb;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,wBAAgB,SAAS,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,CAoEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CA0BzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAwBvE"}
1
+ {"version":3,"file":"envelopes.d.ts","sourceRoot":"","sources":["../../src/envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,yDAAyD;AACzD,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAEtE;;;;;;;OAOG;IACH,GAAG,CACD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,SAAS,CAAC;IAEb;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,wBAAgB,SAAS,CACvB,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,cAAc,GACrB,SAAS,CAsEX;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,GACrD,gBAAgB,EAAE,CA0BpB"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The shape the document gives an error.
3
+ *
4
+ * Every failure an application answers with reaches its `onError` hooks,
5
+ * so one hook sets the format of all of them. The document is generated
6
+ * from the routes, not from that hook, and cannot see what it does: an
7
+ * application with a format of its own says so here too, and the framework's
8
+ * failures, the hooks' refusals and the routes' own envelopes are described
9
+ * in it. Keeping the two in step is the application's to do — the core knows
10
+ * nothing about documents, and a function cannot be read for the shape it
11
+ * returns.
12
+ *
13
+ * Without it the format is the framework's envelope,
14
+ * `{ status, message, error }`.
15
+ *
16
+ * @module
17
+ */
18
+ import type { JsonSchemaObject } from "./emit.ts";
19
+ import type { JsonSchema, JsonSchemaKeywords } from "./json-schema.ts";
20
+ /**
21
+ * One failure the document describes, as the generator knows it.
22
+ *
23
+ * This is everything known about a failure before it is answered: its
24
+ * status, its code when the framework or the hook knows it, an example of
25
+ * its message, and what else it carries — the `issues` of a validation
26
+ * failure, the `retryAfter` of a rate limit. Where each goes in the body
27
+ * is the format's to decide.
28
+ */
29
+ export interface DocumentedFailure {
30
+ readonly status: number;
31
+ /** The code a client branches on, when it is known. */
32
+ readonly error?: string;
33
+ /** An example of the message; wording, never a contract. */
34
+ readonly message?: string;
35
+ /** What the failure carries besides, as JSON Schema by name. */
36
+ readonly fields: Readonly<Record<string, JsonSchema>>;
37
+ }
38
+ /** How the application's errors look, for the document to describe them. */
39
+ export interface ErrorFormat {
40
+ /** The body of one failure. */
41
+ readonly schema: (failure: DocumentedFailure) => JsonSchemaKeywords;
42
+ /**
43
+ * The top-level field that holds the code, for a client to discriminate
44
+ * on — `"code"` in `{ code, message }`. A status whose alternatives are
45
+ * all envelopes then carries a `discriminator` on it.
46
+ *
47
+ * None unless named: OpenAPI discriminates on a top-level field only, and
48
+ * a format that nests its code — `{ error: { code } }` — has none to
49
+ * name.
50
+ */
51
+ readonly discriminator?: string;
52
+ /**
53
+ * The code of a schema a route or a hook declared, when it is an
54
+ * envelope in this format — how the document knows a route's own `404`
55
+ * is the same failure as a hook's, and gives both one definition.
56
+ *
57
+ * Read off the JSON Schema the validator emitted. By default, the
58
+ * `const` of the discriminator's field, when there is one; a nested
59
+ * format says where its code is.
60
+ */
61
+ readonly code?: (schema: JsonSchemaKeywords) => string | undefined;
62
+ }
63
+ /** What the generator works with: every part of a format, filled in. */
64
+ export interface ResolvedFormat {
65
+ describe(failure: DocumentedFailure): JsonSchemaObject;
66
+ code(schema: JsonSchemaObject): string | undefined;
67
+ readonly discriminator: string | undefined;
68
+ }
69
+ /**
70
+ * A format as the generator uses it. Absent, the framework's envelope,
71
+ * discriminated on `error`.
72
+ */
73
+ export declare function resolveFormat(format: ErrorFormat | undefined): ResolvedFormat;
74
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;CACvD;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,kBAAkB,CAAC;IAEpE;;;;;;;;OAQG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEhC;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,MAAM,GAAG,SAAS,CAAC;CACpE;AAED,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAAC;IACvD,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,cAAc,CAqB7E"}
@@ -20,11 +20,13 @@
20
20
  */
21
21
  import type { App } from "@tetsujs/core";
22
22
  import type { OpenApiDocument } from "./document.ts";
23
+ import type { ErrorFormat } from "./errors.ts";
23
24
  export type { DocumentedHeader, DocumentedResponse, HookContributions, HookDocs, SecurityAlternatives, SecurityRequirement, SecurityScheme, } from "./annotations.ts";
24
25
  export { documented, secured } from "./annotations.ts";
25
26
  export type { DocsOptions } from "./controller.ts";
26
27
  export { DocsController, docs } from "./controller.ts";
27
28
  export type { ContentMap, DocumentInfo, DocumentServer, HeaderObject, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, } from "./document.ts";
29
+ export type { DocumentedFailure, ErrorFormat } from "./errors.ts";
28
30
  export type { JsonSchema, JsonSchemaKeywords, JsonSchemaType, } from "./json-schema.ts";
29
31
  export type { DocsAssets, DocsPageOptions, DocsUi } from "./page.ts";
30
32
  export { docsPage } from "./page.ts";
@@ -36,6 +38,32 @@ export interface OpenApiOptions {
36
38
  readonly info: OpenApiDocument["info"];
37
39
  /** Servers the API is reachable at. */
38
40
  readonly servers?: OpenApiDocument["servers"];
41
+ /**
42
+ * How the application's errors look, when not like the framework's
43
+ * envelope — for an application whose `onError` hook answers in a format
44
+ * of its own. The framework's failures, the hooks' refusals and the
45
+ * routes' own envelopes are then described in it.
46
+ *
47
+ * @example `{ code, message }`, discriminated on `code`
48
+ * ```ts
49
+ * openapi(app, {
50
+ * info,
51
+ * errors: {
52
+ * schema: ({ error, message, fields }) => ({
53
+ * type: "object",
54
+ * required: ["code", "message", ...Object.keys(fields)],
55
+ * properties: {
56
+ * code: error ? { type: "string", const: error } : { type: "string" },
57
+ * message: { type: "string", ...(message ? { examples: [message] } : {}) },
58
+ * ...fields,
59
+ * },
60
+ * }),
61
+ * discriminator: "code",
62
+ * },
63
+ * });
64
+ * ```
65
+ */
66
+ readonly errors?: ErrorFormat;
39
67
  }
40
68
  /**
41
69
  * What the generator could not describe faithfully.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,eAAe,CAAC;AAKrE,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,eAAe,EACf,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAEvC,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,GAAG,eAAe,CA2F1E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,eAAe,CAAC;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,eAAe,EACf,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClE,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAEvC,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAE9C;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,GAAG,eAAe,CA8F1E"}