@tetsujs/openapi 0.4.1 → 0.5.0

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
@@ -30,6 +30,38 @@ reported before the first request.
30
30
  prefix, guard it with hooks, or leave it out in production. Its own two
31
31
  routes are left out of the document.
32
32
 
33
+ ### The page runs someone else's code on your origin
34
+
35
+ The page loads its renderer — Scalar, Swagger UI or Redoc — from
36
+ jsDelivr and runs it on the application's origin, with that origin's
37
+ cookies. If a session cookie lives there, a renderer that is not what it
38
+ should be acts as the signed-in user. The defaults are pinned to an exact
39
+ version with a Subresource Integrity hash, so a file the CDN changes is
40
+ refused by the browser; that does not make the renderer yours, and what it
41
+ loads in turn is not covered.
42
+
43
+ Where the origin carries a session, serve the document alone:
44
+
45
+ ```ts
46
+ docs({ info, ui: false });
47
+ ```
48
+
49
+ `/openapi.json` is served and there is no page. For a page anyway, host
50
+ the renderer yourself or pin your own copy, with its hash:
51
+
52
+ ```ts
53
+ docs({
54
+ info,
55
+ assets: {
56
+ script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.1/dist/browser/standalone.js",
57
+ integrity: { script: "sha384-…" },
58
+ },
59
+ });
60
+ ```
61
+
62
+ `curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A` prints
63
+ the hash, to be prefixed with `sha384-`.
64
+
33
65
  ## Options
34
66
 
35
67
  | Option | Default | |
@@ -38,11 +70,12 @@ routes are left out of the document.
38
70
  | `servers` | — | where the API is reachable |
39
71
  | `path` | `/openapi.json` | where the document is served |
40
72
  | `uiPath` | `/docs` | where the page is served |
41
- | `ui` | `"scalar"` | `"scalar"`, `"swagger-ui"` or `"redoc"` |
73
+ | `ui` | `"scalar"` | `"scalar"`, `"swagger-ui"`, `"redoc"`, or `false` for no page |
42
74
  | `title` | the document's title | the page's title |
43
- | `assets` | the renderer's CDN | pinned or self-hosted renderer URLs |
75
+ | `assets` | the renderer on jsDelivr, pinned | your own renderer URLs, with `integrity` hashes |
44
76
  | `documentSelf` | `false` | include `/openapi.json` and `/docs` in the document |
45
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) |
46
79
 
47
80
  ## What goes into the document
48
81
 
@@ -106,6 +139,95 @@ The `error` code is a constant in each schema, so a generated client can
106
139
  tell failures apart by it. A status the route declares itself is kept, and
107
140
  the framework's failures for the same status are listed next to it.
108
141
 
142
+ Every envelope — the framework's, a hook's, or one the route declares
143
+ itself — is one definition in `components` per status and code, named
144
+ after the code (`ITEM_NOT_FOUND` is `ItemNotFound`), so a generated client
145
+ gets one type per failure. A schema is recognized as an envelope when its
146
+ `error` is required and a single string. When the route and a hook both
147
+ describe one code, the route's definition is kept; if the other one has
148
+ different fields, the generator warns. A union the route declares joins
149
+ the other failures of its status as one flat `anyOf`.
150
+
151
+ When every alternative of a status is an envelope, the union carries a
152
+ `discriminator` on `error`, with the mapping from each code to its
153
+ definition, and a generated client narrows on the code.
154
+
155
+ ## An error format of your own
156
+
157
+ Every failure reaches the application's `onError` hooks — a thrown
158
+ `HttpError`, a validation or body failure, a `404`, a `405`, a rate limit's
159
+ refusal — so one hook answers all of them in your format:
160
+
161
+ ```ts
162
+ const inOurFormat = hook.onError(({ error }) => {
163
+ if (!(error instanceof HttpError)) return undefined;
164
+
165
+ const { status, error: code, ...rest } = error.body as ErrorBody;
166
+
167
+ return Response.json({ code, ...rest }, { status });
168
+ });
169
+ ```
170
+
171
+ The document is generated from the routes, not from that hook, so it is
172
+ told the same format with `errors`:
173
+
174
+ ```ts
175
+ const errors: ErrorFormat = {
176
+ schema: ({ error, message, fields }) => ({
177
+ type: "object",
178
+ required: ["code", "message", ...Object.keys(fields)],
179
+ properties: {
180
+ code: error ? { type: "string", const: error } : { type: "string" },
181
+ message: { type: "string", ...(message ? { examples: [message] } : {}) },
182
+ ...fields,
183
+ },
184
+ }),
185
+ discriminator: "code",
186
+ };
187
+
188
+ createApp({
189
+ hooks: { onError: [inOurFormat] },
190
+ routes: [api, docs({ info, errors })],
191
+ });
192
+ ```
193
+
194
+ `schema` describes one failure from what the generator knows of it: its
195
+ status, its code when known, an example message, and in `fields` what it
196
+ carries besides — `issues` for a validation failure, `retryAfter` for a
197
+ rate limit, a hook's own `fields`. It is used for the framework's failures
198
+ and the hooks' refusals alike.
199
+
200
+ `discriminator` names the top-level field that holds the code. Statuses of
201
+ envelopes are then discriminated on it, and a route's own envelope is
202
+ recognized by it — a `{ code: "ITEM_NOT_FOUND", … }` the route declares is
203
+ the same definition as a hook's. A format that nests its code has no
204
+ top-level field to name; it gives `code` instead, reading the code from a
205
+ route's schema:
206
+
207
+ ```ts
208
+ code: (schema) => {
209
+ const error = schema.properties?.error;
210
+ const code = typeof error === "object" ? error.properties?.code : undefined;
211
+
212
+ return typeof code === "object" && typeof code.const === "string" ? code.const : undefined;
213
+ },
214
+ ```
215
+
216
+ The hook and `errors` describe one format in two places — the core knows
217
+ nothing about documents, and a function cannot be read for the shape it
218
+ returns — so keep a test that holds them together: provoke a failure and
219
+ check its body against the document, with the JSON Schema validator of
220
+ your choice.
221
+
222
+ ```ts
223
+ test("a validation failure is what the document says", async () => {
224
+ const res = await request("/items", { method: "POST", body: "{}" });
225
+ const schema = document.components?.schemas?.ValidationFailed;
226
+
227
+ expect(validator.validate(schema, await res.json())).toBe(true);
228
+ });
229
+ ```
230
+
109
231
  ## Documenting hooks
110
232
 
111
233
  A hook that answers by itself — an auth check, a limiter — can say so, and
@@ -127,10 +249,25 @@ export const auth = secured(
127
249
  );
128
250
 
129
251
  export const guard = documented(hook.beforeParse(check), {
130
- responses: [{ status: 429, description: "Rate limit exceeded", error: "RATE_LIMITED" }],
252
+ responses: [
253
+ {
254
+ status: 429,
255
+ description: "Rate limit exceeded",
256
+ error: "RATE_LIMITED",
257
+ fields: { retryAfter: { type: "integer", minimum: 0 } },
258
+ headers: { "retry-after": { schema: { type: "integer", minimum: 0 } } },
259
+ },
260
+ ],
131
261
  });
132
262
  ```
133
263
 
264
+ A response with an `error` code is the framework's envelope, defined once
265
+ in `components` and named after the code. `fields` adds what the hook puts
266
+ next to `status`, `message` and `error`, each always present; `headers`,
267
+ what it sets on the response. Both are JSON Schema written by hand, typed
268
+ keyword by keyword (`JsonSchema`), so a misspelled keyword does not
269
+ compile. A hook whose body is not the envelope passes a `schema` instead.
270
+
134
271
  Hooks from `@tetsujs/rate-limit` are already documented this way. A scheme
135
272
  is identified by its `name`: the same one mounted twice is one
136
273
  requirement, and two different schemes under one name are reported.
@@ -16,6 +16,7 @@
16
16
  * @module
17
17
  */
18
18
  import type { AnyHook, AnySchema } from "@tetsujs/core";
19
+ import type { JsonSchema } from "./json-schema.ts";
19
20
  /**
20
21
  * The definition of a scheme, as OpenAPI spells it.
21
22
  *
@@ -70,6 +71,45 @@ export interface DocumentedResponse {
70
71
  readonly error?: string;
71
72
  /** An example of the envelope's `message`, for the same reason. */
72
73
  readonly message?: string;
74
+ /**
75
+ * Fields the hook adds to the envelope, as JSON Schema by name — the
76
+ * `retryAfter` of `{ ...errorBody(429, "RATE_LIMITED"), retryAfter }`.
77
+ *
78
+ * Each is documented as always present. The envelope's own `status`,
79
+ * `message` and `error` are not redefined by a field of the same name:
80
+ * `error` is what a client discriminates on, and it stays the code the
81
+ * hook declared. Ignored when the response has a `schema`, which says
82
+ * everything about the body itself.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * documented(limiter, {
87
+ * responses: [
88
+ * {
89
+ * status: 429,
90
+ * description: "Too many requests",
91
+ * error: "RATE_LIMITED",
92
+ * fields: { retryAfter: { type: "integer", minimum: 0 } },
93
+ * },
94
+ * ],
95
+ * });
96
+ * ```
97
+ */
98
+ readonly fields?: Readonly<Record<string, JsonSchema>>;
99
+ /**
100
+ * Headers the hook sets on this response, by name — the `retry-after`
101
+ * of a refusal.
102
+ *
103
+ * Documented as possible rather than required: a status several sources
104
+ * answer with is one response in the document, and a header one of them
105
+ * sets is not on the others'.
106
+ */
107
+ readonly headers?: Readonly<Record<string, DocumentedHeader>>;
108
+ }
109
+ /** A header a hook's response carries. */
110
+ export interface DocumentedHeader {
111
+ readonly description?: string;
112
+ readonly schema: JsonSchema;
73
113
  }
74
114
  /**
75
115
  * Requirements of which a hook accepts any one: a session cookie *or* a
@@ -1 +1 @@
1
- {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/annotations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/annotations.ts"],"names":[],"mappings":"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"}
@@ -15,20 +15,34 @@
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. */
21
- export interface DocsOptions<Path extends string, UiPath extends string> {
22
+ export interface DocsOptions<Path extends string, UiPath extends string, Ui extends DocsUi | false = DocsUi | false> {
22
23
  /** Title, version and the rest of the document's `info` block. */
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`. */
29
35
  readonly uiPath?: UiPath & ValidatePath<UiPath>;
30
- /** Which renderer the page bootstraps. Defaults to `"scalar"`. */
31
- readonly ui?: DocsUi;
36
+ /**
37
+ * Which renderer the page bootstraps. Defaults to `"scalar"`.
38
+ *
39
+ * `false` serves the document without a page — what production wants
40
+ * on an origin that carries a session. The page runs its renderer on the
41
+ * application's origin, so a renderer that is not what it should be acts
42
+ * in the signed-in user's name there; the document alone runs nothing.
43
+ * `uiPath`, `title` and `assets` are then unused.
44
+ */
45
+ readonly ui?: Ui;
32
46
  /** Browser title of the page. Defaults to the document's own title. */
33
47
  readonly title?: string;
34
48
  /** Overrides the renderer's CDN URLs, for a pinned or self-hosted copy. */
@@ -56,15 +70,24 @@ export interface DocsOptions<Path extends string, UiPath extends string> {
56
70
  * Instantiated through the factory, never directly — the routes carry the
57
71
  * configured paths in their types, which only the factory can infer.
58
72
  */
59
- export declare class DocsController<Path extends string, UiPath extends string> {
73
+ export declare class DocsController<Path extends string, UiPath extends string, Page extends boolean = true> {
60
74
  private readonly options;
61
75
  /** The document itself, as `application/json`. */
62
76
  readonly json: RouteDef<Path, SchemaConfig, never, undefined, "GET">;
63
- /** The page that renders it. */
64
- readonly ui: RouteDef<UiPath, SchemaConfig, never, undefined, "GET">;
77
+ /** The page that renders it — none with `ui: false`. */
78
+ readonly ui: Page extends true ? RouteDef<UiPath, SchemaConfig, never, undefined, "GET"> : undefined;
65
79
  private document;
66
- constructor(options: DocsOptions<Path, UiPath>, documentPath: string, uiPath: string, page: string);
67
- /** Generates the document of the application this was mounted in. */
80
+ private page;
81
+ constructor(options: DocsOptions<Path, UiPath>, documentPath: string, uiPath: string);
82
+ /**
83
+ * Generates the document of the application this was mounted in, and
84
+ * the page that renders it.
85
+ *
86
+ * The page is built here rather than with the routes because only the
87
+ * application knows where the document ended up: a group this controller
88
+ * is mounted under prefixes its path, and a page pointing at the path as
89
+ * configured would fetch a `404`.
90
+ */
68
91
  [onMount](app: App): void;
69
92
  }
70
93
  /**
@@ -87,5 +110,5 @@ export declare class DocsController<Path extends string, UiPath extends string>
87
110
  * The page loads its renderer from a CDN; see {@link docsPage} for the
88
111
  * trade and for how to point it at a self-hosted copy.
89
112
  */
90
- export declare function docs<const Path extends string = "/openapi.json", const UiPath extends string = "/docs">(options: DocsOptions<Path, UiPath>): DocsController<Path, UiPath>;
113
+ export declare function docs<const Path extends string = "/openapi.json", const UiPath extends string = "/docs", const Ui extends DocsUi | false = "scalar">(options: DocsOptions<Path, UiPath, Ui>): DocsController<Path, UiPath, Ui extends false ? false : true>;
91
114
  //# sourceMappingURL=controller.d.ts.map
@@ -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,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM;IACrE,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,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAErB,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,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM;IAUlE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAT1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAErE,gCAAgC;IAChC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAErE,OAAO,CAAC,QAAQ,CAA8B;IAE9C,YACmB,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EACnD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAgCb;IAED,qEAAqE;IACrE,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAaxB;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,IAAI,CAClB,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,eAAe,EAC3C,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG,OAAO,EACrC,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAYlE"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@
14
14
  *
15
15
  * @module
16
16
  */
17
+ import type { JsonSchema } from "./json-schema.ts";
17
18
  /** Metadata every OpenAPI document must carry. */
18
19
  export interface DocumentInfo {
19
20
  readonly title: string;
@@ -38,9 +39,15 @@ export interface ParameterObject {
38
39
  export type ContentMap = Record<string, {
39
40
  readonly schema?: Record<string, unknown>;
40
41
  }>;
42
+ /** A header a response carries. */
43
+ export interface HeaderObject {
44
+ readonly description?: string;
45
+ readonly schema: JsonSchema;
46
+ }
41
47
  /** One response of an operation. */
42
48
  export interface ResponseObject {
43
49
  readonly description: string;
50
+ readonly headers?: Record<string, HeaderObject>;
44
51
  readonly content?: ContentMap;
45
52
  }
46
53
  /** One endpoint: a method on a path. */
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,MAAM,CAC7B,MAAM,EACN;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAC9C,CAAC;AAEF,oCAAoC;AACpC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;CAC/B;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;KAC9B,CAAC;IACF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACpD;AAED,qDAAqD;AACrD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAE7D,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAE/C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE;QACpB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC5C,CAAC;IAEF,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/document.ts"],"names":[],"mappings":"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"}
@@ -6,13 +6,15 @@
6
6
  * @module
7
7
  */
8
8
  /**
9
- * A JSON Schema as the generator reads it: any keyword, with the two it
9
+ * A JSON Schema as the generator reads it: any keyword, with the ones it
10
10
  * looks into named, so reading them does not depend on the reader's
11
11
  * `noPropertyAccessFromIndexSignature`.
12
12
  */
13
13
  export interface JsonSchemaObject {
14
14
  properties?: unknown;
15
15
  required?: unknown;
16
+ anyOf?: unknown;
17
+ oneOf?: unknown;
16
18
  [keyword: string]: unknown;
17
19
  }
18
20
  /** 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,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,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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Error envelopes, one definition per status and code.
3
+ *
4
+ * An envelope reaches a document from four directions: a route's response
5
+ * map, a hook's annotation, a guard's security requirement, and the
6
+ * framework's own failures. Each used to be described where it arrived —
7
+ * a route's inline, the rest by reference — so one `UNAUTHORIZED` could be
8
+ * listed twice under one status, once as a `$ref` and once as the same
9
+ * shape without a name, and a generated client got a named type next to an
10
+ * anonymous twin.
11
+ *
12
+ * Here every envelope, whoever declared it, becomes a reference to the
13
+ * one definition of its status and code. That is also what makes a status
14
+ * discriminable: a client can branch on `error` only if every code under
15
+ * the status has exactly one shape.
16
+ *
17
+ * The route's definition wins. It is the author's own contract — usually
18
+ * the stricter one, a validator's `additionalProperties: false` — where a
19
+ * hook's is the framework's generic envelope. The routes are therefore
20
+ * read first, before any operation is built, so the winner does not depend
21
+ * on which route happens to come first in the table.
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
+ *
27
+ * Internal to the package.
28
+ *
29
+ * @module
30
+ */
31
+ import type { SchemaComponents, SchemaRef } from "./components.ts";
32
+ import type { JsonSchemaObject } from "./emit.ts";
33
+ import type { ResolvedFormat } from "./errors.ts";
34
+ /** The definitions of every envelope of one document. */
35
+ export interface Envelopes {
36
+ /** The document's error format. */
37
+ readonly format: ResolvedFormat;
38
+ /**
39
+ * Records a route's definition, before anything is referenced. The first
40
+ * route to declare a status and code defines it.
41
+ */
42
+ declare(status: number, code: string, schema: JsonSchemaObject): void;
43
+ /**
44
+ * The reference to the definition of a status and code.
45
+ *
46
+ * `schema` is how the caller describes it. When a definition already
47
+ * exists, that is what is referenced; if the caller's differs from it
48
+ * in fields, `warn` hears about it — once per document, not once per
49
+ * operation the hook runs on.
50
+ */
51
+ ref(status: number, code: string, schema: JsonSchemaObject, warn: (message: string) => void): SchemaRef;
52
+ /**
53
+ * The code a reference stands for, when it is one this handed out — what
54
+ * a discriminator maps back from.
55
+ */
56
+ codeOf(ref: string): string | undefined;
57
+ }
58
+ export declare function envelopes(components: SchemaComponents, format: ResolvedFormat): Envelopes;
59
+ /**
60
+ * The alternatives a schema stands for, as a list the status's own union
61
+ * can take in.
62
+ *
63
+ * `anyOf` nested in `anyOf` says nothing its branches do not say side by
64
+ * side, so a schema that is only an `anyOf` is replaced by its branches.
65
+ * A `oneOf` is different — "exactly one" of two overlapping shapes refuses
66
+ * what both accept — and is taken apart only when its branches cannot
67
+ * overlap: envelopes with a different code each. A schema with anything
68
+ * beside its union, a `title` or a `$defs`, is left whole: those keywords
69
+ * belong to the union, and would be lost with it.
70
+ */
71
+ export declare function branchesOf(schema: JsonSchemaObject, code: (schema: JsonSchemaObject) => string | undefined): JsonSchemaObject[];
72
+ //# sourceMappingURL=envelopes.d.ts.map
@@ -0,0 +1 @@
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,14 @@
20
20
  */
21
21
  import type { App } from "@tetsujs/core";
22
22
  import type { OpenApiDocument } from "./document.ts";
23
- export type { DocumentedResponse, HookContributions, HookDocs, SecurityAlternatives, SecurityRequirement, SecurityScheme, } from "./annotations.ts";
23
+ import type { ErrorFormat } from "./errors.ts";
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
- export type { ContentMap, DocumentInfo, DocumentServer, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, } from "./document.ts";
28
+ export type { ContentMap, DocumentInfo, DocumentServer, HeaderObject, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, } from "./document.ts";
29
+ export type { DocumentedFailure, ErrorFormat } from "./errors.ts";
30
+ export type { JsonSchema, JsonSchemaKeywords, JsonSchemaType, } from "./json-schema.ts";
28
31
  export type { DocsAssets, DocsPageOptions, DocsUi } from "./page.ts";
29
32
  export { docsPage } from "./page.ts";
30
33
  /**
@@ -35,6 +38,32 @@ export interface OpenApiOptions {
35
38
  readonly info: OpenApiDocument["info"];
36
39
  /** Servers the API is reachable at. */
37
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;
38
67
  }
39
68
  /**
40
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;AAIrE,YAAY,EACV,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,eAAe,EACf,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,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,CAsF1E"}
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"}