@tetsujs/openapi 0.4.2 → 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 +77 -0
- package/dist/src/controller.d.ts +6 -0
- package/dist/src/controller.d.ts.map +1 -1
- package/dist/src/envelopes.d.ts +9 -11
- package/dist/src/envelopes.d.ts.map +1 -1
- package/dist/src/errors.d.ts +74 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +123 -70
- package/dist/src/index.js.map +8 -7
- package/dist/src/operation.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/controller.ts +8 -0
- package/src/envelopes.ts +84 -58
- package/src/errors.ts +177 -0
- package/src/index.ts +34 -1
- package/src/operation.ts +42 -66
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,82 @@ 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
|
+
## 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
|
+
|
|
154
231
|
## Documenting hooks
|
|
155
232
|
|
|
156
233
|
A hook that answers by itself — an auth check, a limiter — can say so, and
|
package/dist/src/controller.d.ts
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"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"}
|
package/dist/src/envelopes.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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.
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/src/index.js
CHANGED
|
@@ -103,7 +103,7 @@ function failureName(status, error) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// packages/openapi/src/envelopes.ts
|
|
106
|
-
function envelopes(components) {
|
|
106
|
+
function envelopes(components, format) {
|
|
107
107
|
const declared = new Map;
|
|
108
108
|
const defined = new Map;
|
|
109
109
|
const refs = new Map;
|
|
@@ -111,6 +111,7 @@ function envelopes(components) {
|
|
|
111
111
|
const names = new Map;
|
|
112
112
|
const reported = new Set;
|
|
113
113
|
return {
|
|
114
|
+
format,
|
|
114
115
|
declare(status, code, schema) {
|
|
115
116
|
const key = keyOf(status, code);
|
|
116
117
|
if (!declared.has(key)) {
|
|
@@ -149,31 +150,18 @@ function envelopes(components) {
|
|
|
149
150
|
return `${name}${status}`;
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
|
-
function
|
|
153
|
-
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
154
|
-
if (!required.includes("error")) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const error = schema.properties?.error;
|
|
158
|
-
if (typeof error?.const === "string") {
|
|
159
|
-
return error.const;
|
|
160
|
-
}
|
|
161
|
-
if (Array.isArray(error?.enum) && error.enum.length === 1 && typeof error.enum[0] === "string") {
|
|
162
|
-
return error.enum[0];
|
|
163
|
-
}
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
function branchesOf(schema) {
|
|
153
|
+
function branchesOf(schema, code) {
|
|
167
154
|
const keys = Object.keys(schema);
|
|
168
155
|
if (keys.length !== 1) {
|
|
169
156
|
return [schema];
|
|
170
157
|
}
|
|
158
|
+
const within = (branch) => branchesOf(branch, code);
|
|
171
159
|
if (Array.isArray(schema.anyOf)) {
|
|
172
|
-
return schema.anyOf.flatMap(
|
|
160
|
+
return schema.anyOf.flatMap(within);
|
|
173
161
|
}
|
|
174
162
|
if (Array.isArray(schema.oneOf)) {
|
|
175
|
-
const branches = schema.oneOf.flatMap(
|
|
176
|
-
const codes = branches.map(
|
|
163
|
+
const branches = schema.oneOf.flatMap(within);
|
|
164
|
+
const codes = branches.map(code);
|
|
177
165
|
if (codes.every((code) => code !== undefined) && new Set(codes).size === codes.length) {
|
|
178
166
|
return branches;
|
|
179
167
|
}
|
|
@@ -184,17 +172,89 @@ function keyOf(status, code) {
|
|
|
184
172
|
return `${status} ${code}`;
|
|
185
173
|
}
|
|
186
174
|
function differenceOf(kept, other) {
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
175
|
+
const keptShape = shapeOf(kept);
|
|
176
|
+
const otherShape = shapeOf(other);
|
|
177
|
+
const changed = [
|
|
178
|
+
...otherShape.fields.filter((path) => !keptShape.fields.includes(path)),
|
|
179
|
+
...keptShape.fields.filter((path) => !otherShape.fields.includes(path))
|
|
180
|
+
];
|
|
193
181
|
if (changed.length > 0) {
|
|
194
182
|
return `fields: ${changed.join(", ")}`;
|
|
195
183
|
}
|
|
196
|
-
|
|
197
|
-
|
|
184
|
+
return JSON.stringify(keptShape.required) === JSON.stringify(otherShape.required) ? undefined : "which fields are required";
|
|
185
|
+
}
|
|
186
|
+
var shapeDepth = 8;
|
|
187
|
+
function shapeOf(schema) {
|
|
188
|
+
const fields = [];
|
|
189
|
+
const required = [];
|
|
190
|
+
const walk = (node, prefix, depth) => {
|
|
191
|
+
const properties = node.properties;
|
|
192
|
+
if (depth > shapeDepth || properties === null || typeof properties !== "object") {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const requiredHere = Array.isArray(node.required) ? node.required : [];
|
|
196
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
197
|
+
const path = `${prefix}${name}`;
|
|
198
|
+
fields.push(path);
|
|
199
|
+
if (requiredHere.includes(name)) {
|
|
200
|
+
required.push(path);
|
|
201
|
+
}
|
|
202
|
+
if (child !== null && typeof child === "object") {
|
|
203
|
+
walk(child, `${path}.`, depth + 1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
walk(schema, "", 0);
|
|
208
|
+
return { fields: fields.sort(), required: required.sort() };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// packages/openapi/src/errors.ts
|
|
212
|
+
function resolveFormat(format) {
|
|
213
|
+
if (format === undefined) {
|
|
214
|
+
return {
|
|
215
|
+
describe: envelope,
|
|
216
|
+
code: codeIn("error"),
|
|
217
|
+
discriminator: "error"
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const discriminator = format.discriminator;
|
|
221
|
+
const read = format.code;
|
|
222
|
+
return {
|
|
223
|
+
describe: (failure) => format.schema(failure),
|
|
224
|
+
code: read ? (schema) => read(schema) : discriminator === undefined ? () => {
|
|
225
|
+
return;
|
|
226
|
+
} : codeIn(discriminator),
|
|
227
|
+
discriminator
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function envelope(failure) {
|
|
231
|
+
const own = {
|
|
232
|
+
status: { type: "integer", const: failure.status },
|
|
233
|
+
message: failure.message ? { type: "string", examples: [failure.message] } : { type: "string" },
|
|
234
|
+
error: failure.error ? { type: "string", const: failure.error } : { type: "string" }
|
|
235
|
+
};
|
|
236
|
+
const added = Object.keys(failure.fields).filter((name) => !Object.hasOwn(own, name));
|
|
237
|
+
return {
|
|
238
|
+
type: "object",
|
|
239
|
+
required: ["status", "message", "error", ...added],
|
|
240
|
+
properties: { ...failure.fields, ...own }
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function codeIn(field) {
|
|
244
|
+
return (schema) => {
|
|
245
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
246
|
+
if (!required.includes(field)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const value = schema.properties?.[field];
|
|
250
|
+
if (typeof value?.const === "string") {
|
|
251
|
+
return value.const;
|
|
252
|
+
}
|
|
253
|
+
if (Array.isArray(value?.enum) && value.enum.length === 1 && typeof value.enum[0] === "string") {
|
|
254
|
+
return value.enum[0];
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
};
|
|
198
258
|
}
|
|
199
259
|
|
|
200
260
|
// packages/openapi/src/operation.ts
|
|
@@ -315,10 +375,11 @@ function operationOf(entry, options, components, envelopes, ids, warn) {
|
|
|
315
375
|
function declareEnvelopes(entry, envelopes) {
|
|
316
376
|
for (const [status, schema] of declaredResponses(entry)) {
|
|
317
377
|
const described = schema === null ? undefined : emitted(schema, "", () => {});
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
378
|
+
const code = envelopes.format.code;
|
|
379
|
+
for (const branch of described ? branchesOf(described, code) : []) {
|
|
380
|
+
const found = code(branch);
|
|
381
|
+
if (found !== undefined) {
|
|
382
|
+
envelopes.declare(Number(status), found, branch);
|
|
322
383
|
}
|
|
323
384
|
}
|
|
324
385
|
}
|
|
@@ -334,8 +395,8 @@ function declaredResponses(entry) {
|
|
|
334
395
|
return Object.entries(response);
|
|
335
396
|
}
|
|
336
397
|
function branchesFor(schema, status, envelopes, warn) {
|
|
337
|
-
return branchesOf(schema).map((branch) => {
|
|
338
|
-
const code =
|
|
398
|
+
return branchesOf(schema, envelopes.format.code).map((branch) => {
|
|
399
|
+
const code = envelopes.format.code(branch);
|
|
339
400
|
return code === undefined ? branch : envelopes.ref(status, code, branch, warn);
|
|
340
401
|
});
|
|
341
402
|
}
|
|
@@ -475,7 +536,12 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
475
536
|
const schema = entry.def.schema;
|
|
476
537
|
const found = [];
|
|
477
538
|
const envelopeRef = (status, error, message, fields) => {
|
|
478
|
-
const described =
|
|
539
|
+
const described = envelopes.format.describe({
|
|
540
|
+
status,
|
|
541
|
+
...error === undefined ? {} : { error },
|
|
542
|
+
...message === undefined ? {} : { message },
|
|
543
|
+
fields: fields ?? {}
|
|
544
|
+
});
|
|
479
545
|
const ref = error === undefined ? components.ref(failureName(status), described) : envelopes.ref(status, error, described, warn);
|
|
480
546
|
return ref;
|
|
481
547
|
};
|
|
@@ -485,7 +551,12 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
485
551
|
{
|
|
486
552
|
description: "Request failed schema validation",
|
|
487
553
|
schemas: [
|
|
488
|
-
envelopes.ref(options.validationStatus, "VALIDATION_FAILED",
|
|
554
|
+
envelopes.ref(options.validationStatus, "VALIDATION_FAILED", envelopes.format.describe({
|
|
555
|
+
status: options.validationStatus,
|
|
556
|
+
error: "VALIDATION_FAILED",
|
|
557
|
+
message: "Validation failed",
|
|
558
|
+
fields: { issues }
|
|
559
|
+
}), warn)
|
|
489
560
|
]
|
|
490
561
|
}
|
|
491
562
|
]);
|
|
@@ -569,6 +640,10 @@ function unionOf(schemas, envelopes) {
|
|
|
569
640
|
if (schemas.length === 1 && only) {
|
|
570
641
|
return only;
|
|
571
642
|
}
|
|
643
|
+
const propertyName = envelopes.format.discriminator;
|
|
644
|
+
if (propertyName === undefined) {
|
|
645
|
+
return { anyOf: schemas };
|
|
646
|
+
}
|
|
572
647
|
const mapping = {};
|
|
573
648
|
for (const schema of schemas) {
|
|
574
649
|
const ref = schema.$ref;
|
|
@@ -580,7 +655,7 @@ function unionOf(schemas, envelopes) {
|
|
|
580
655
|
}
|
|
581
656
|
return {
|
|
582
657
|
anyOf: schemas,
|
|
583
|
-
discriminator: { propertyName
|
|
658
|
+
discriminator: { propertyName, mapping }
|
|
584
659
|
};
|
|
585
660
|
}
|
|
586
661
|
function distinct(schemas) {
|
|
@@ -600,40 +675,17 @@ function describeStatus(status) {
|
|
|
600
675
|
}
|
|
601
676
|
return status.startsWith("2") ? "Successful response" : errorBody(Number(status)).message;
|
|
602
677
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
message: message ? { type: "string", examples: [message] } : { type: "string" },
|
|
607
|
-
error: error ? { type: "string", const: error } : { type: "string" }
|
|
608
|
-
};
|
|
609
|
-
const added = Object.keys(fields).filter((name) => !Object.hasOwn(own, name));
|
|
610
|
-
return {
|
|
678
|
+
var issues = {
|
|
679
|
+
type: "array",
|
|
680
|
+
items: {
|
|
611
681
|
type: "object",
|
|
612
|
-
required: ["
|
|
613
|
-
properties: { ...fields, ...own }
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
function validationFailed(status) {
|
|
617
|
-
const base = envelope(status, "VALIDATION_FAILED", "Validation failed");
|
|
618
|
-
return {
|
|
619
|
-
...base,
|
|
620
|
-
required: ["status", "message", "error", "issues"],
|
|
682
|
+
required: ["message", "path"],
|
|
621
683
|
properties: {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
type: "array",
|
|
625
|
-
items: {
|
|
626
|
-
type: "object",
|
|
627
|
-
required: ["message", "path"],
|
|
628
|
-
properties: {
|
|
629
|
-
message: { type: "string" },
|
|
630
|
-
path: { type: "array", items: { type: ["string", "number"] } }
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
684
|
+
message: { type: "string" },
|
|
685
|
+
path: { type: "array", items: { type: ["string", "number"] } }
|
|
634
686
|
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
637
689
|
function parseFailure(bodyType) {
|
|
638
690
|
if (bodyType === "text" || bodyType === "stream") {
|
|
639
691
|
return;
|
|
@@ -751,7 +803,8 @@ class DocsController {
|
|
|
751
803
|
[onMount](app) {
|
|
752
804
|
const generated = openapi(app, {
|
|
753
805
|
info: this.options.info,
|
|
754
|
-
...this.options.servers ? { servers: this.options.servers } : {}
|
|
806
|
+
...this.options.servers ? { servers: this.options.servers } : {},
|
|
807
|
+
...this.options.errors ? { errors: this.options.errors } : {}
|
|
755
808
|
});
|
|
756
809
|
this.document = generated.document;
|
|
757
810
|
const ui = this.options.ui ?? "scalar";
|
|
@@ -784,7 +837,7 @@ function openapi(app, options) {
|
|
|
784
837
|
const warnings = [];
|
|
785
838
|
const schemes = {};
|
|
786
839
|
const components = schemaComponents();
|
|
787
|
-
const envelopes2 = envelopes(components);
|
|
840
|
+
const envelopes2 = envelopes(components, resolveFormat(options.errors));
|
|
788
841
|
const ids = operationIds();
|
|
789
842
|
const described = app.entries.filter((entry) => !entry.ws && !entry.def.docs?.hidden);
|
|
790
843
|
for (const entry of described) {
|
|
@@ -851,5 +904,5 @@ export {
|
|
|
851
904
|
secured
|
|
852
905
|
};
|
|
853
906
|
|
|
854
|
-
//# debugId=
|
|
907
|
+
//# debugId=B1D856E7F5B6819164756E2164756E21
|
|
855
908
|
//# sourceMappingURL=index.js.map
|