@tetsujs/openapi 0.5.0 → 0.5.2
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 +108 -10
- package/dist/src/controller.d.ts +12 -0
- package/dist/src/controller.d.ts.map +1 -1
- package/dist/src/document.d.ts +17 -0
- package/dist/src/document.d.ts.map +1 -1
- package/dist/src/emit.d.ts +1 -0
- package/dist/src/emit.d.ts.map +1 -1
- package/dist/src/index.d.ts +27 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +103 -19
- package/dist/src/index.js.map +6 -6
- package/dist/src/testing.d.ts +69 -0
- package/dist/src/testing.d.ts.map +1 -0
- package/dist/src/testing.js +203 -0
- package/dist/src/testing.js.map +10 -0
- package/package.json +7 -3
- package/src/controller.ts +28 -1
- package/src/document.ts +19 -0
- package/src/emit.ts +1 -0
- package/src/index.ts +85 -1
- package/src/operation.ts +132 -27
- package/src/testing.ts +453 -0
package/README.md
CHANGED
|
@@ -76,20 +76,57 @@ the hash, to be prefixed with `sha384-`.
|
|
|
76
76
|
| `documentSelf` | `false` | include `/openapi.json` and `/docs` in the document |
|
|
77
77
|
| `onWarning` | `console.warn` | receives what could not be described |
|
|
78
78
|
| `errors` | the framework's envelope | an error format of your own — see [below](#an-error-format-of-your-own) |
|
|
79
|
+
| `tags` | — | what each tag is, in the order the sidebar lists them — see [below](#tags) |
|
|
79
80
|
|
|
80
|
-
##
|
|
81
|
+
## How a route becomes an operation
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
| `
|
|
89
|
-
|
|
|
83
|
+
Every part of an operation comes from somewhere the application already
|
|
84
|
+
wrote, by a rule:
|
|
85
|
+
|
|
86
|
+
| In the document | Comes from | Rule |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| the path | the route's `path`, under its groups' prefixes | `/users/:id` → `/users/{id}` |
|
|
89
|
+
| `operationId` | `docs.operationId`, or the controller's name and the field | `setAvatar` in `controller("Users", …)` → `usersSetAvatar` — see [Operation ids](#operation-ids) |
|
|
90
|
+
| `summary`, `description`, `tags`, `deprecated` | the route's `docs` | as written |
|
|
91
|
+
| parameters | `schema.params`, `query`, `headers`, `cookies` | required as the schema says |
|
|
92
|
+
| the request body | `schema.body` and `bodyType` | media types from `bodyType`; required unless `text` or `stream` |
|
|
93
|
+
| `security` | `secured()` hooks, of every level the route runs under | every hook required together; `anyOf` inside one hook is the alternatives |
|
|
94
|
+
| the statuses | the route's `schema.response`, the hooks' `documented()` and `secured()`, the framework | one schema is `200`, a map its statuses, `null` a status without a body; with no `2xx` declared, `200`; the framework's `422`, `400`, `413`, `500` where they can happen — see [Responses the framework adds](#responses-the-framework-adds) |
|
|
95
|
+
| a status's body | everything answering with that status | one flat `anyOf`, the route's own first, the same body once |
|
|
96
|
+
| an error envelope | any of them | one definition per status and code in `components`, named after the code; the route's own kept |
|
|
97
|
+
| `discriminator` | the envelopes of a status | on `error`, or the format's field, when every alternative is an envelope |
|
|
98
|
+
| a status's description | the schemas' `description`, the hooks', the framework's | one is the description, several a list led by code, none the reason phrase |
|
|
99
|
+
| a status's headers | the hooks' `documented()` headers | possible, not required |
|
|
100
|
+
| the document's `tags` | the `tags` option | in its order, then the tags routes use and it leaves out |
|
|
90
101
|
|
|
91
102
|
`docs: { hidden: true }` leaves a route out of the document; it is served
|
|
92
|
-
as before.
|
|
103
|
+
as before. A route with no `schema.response` is documented as `200` — its
|
|
104
|
+
handler may still answer `204` by returning nothing, which only a declared
|
|
105
|
+
`204: null` says.
|
|
106
|
+
|
|
107
|
+
## Tags
|
|
108
|
+
|
|
109
|
+
A route names its tags in `docs: { tags }`; the document says what each
|
|
110
|
+
one is:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
docs({
|
|
114
|
+
info,
|
|
115
|
+
tags: {
|
|
116
|
+
"sign-in": "Signing in with a code sent by email, and signing out",
|
|
117
|
+
me: "The signed-in user",
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The order of the keys is the order of the sections a renderer lists. A tag
|
|
123
|
+
the routes use and `tags` leaves out is listed after them, in the order
|
|
124
|
+
the routes first use it, and reported — it is usually one tag spelled two
|
|
125
|
+
ways, `sign_in` next to `sign-in`. So is a tag described and used by no
|
|
126
|
+
operation. Without `tags` the document lists none, and nothing is
|
|
127
|
+
reported. An application of several surfaces, each with its own `docs()`,
|
|
128
|
+
gives each one only the tags of its own routes: given all of them, every
|
|
129
|
+
`docs()` reports the tags of the others as unused.
|
|
93
130
|
|
|
94
131
|
## Operation ids
|
|
95
132
|
|
|
@@ -152,6 +189,23 @@ When every alternative of a status is an envelope, the union carries a
|
|
|
152
189
|
`discriminator` on `error`, with the mapping from each code to its
|
|
153
190
|
definition, and a generated client narrows on the code.
|
|
154
191
|
|
|
192
|
+
A status is described by what answers with it. The route's part is the
|
|
193
|
+
`description` of the schema it declares — `.describe()` in Zod and
|
|
194
|
+
ArkType, `v.description()` in Valibot, `{ description }` in TypeBox — for
|
|
195
|
+
any status, a `200` as much as a `404`; a union described as a whole is
|
|
196
|
+
one description, one described branch by branch is one per branch. A
|
|
197
|
+
hook's part is its `documented()` description, the framework's its own.
|
|
198
|
+
One description is the status's description; several are a list, each
|
|
199
|
+
led by its code:
|
|
200
|
+
|
|
201
|
+
```md
|
|
202
|
+
- `ACCOUNT_DISABLED`: this account is disabled
|
|
203
|
+
- `CAPTCHA_FAILED`: the captcha token is missing or did not pass
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
A status nothing describes keeps its reason phrase — `Not Found`,
|
|
207
|
+
`Successful response`.
|
|
208
|
+
|
|
155
209
|
## An error format of your own
|
|
156
210
|
|
|
157
211
|
Every failure reaches the application's `onError` hooks — a thrown
|
|
@@ -308,6 +362,50 @@ await Bun.write("openapi.json", JSON.stringify(document, null, 2));
|
|
|
308
362
|
|
|
309
363
|
`docsPage()` renders the page on its own, for serving it elsewhere.
|
|
310
364
|
|
|
365
|
+
## Testing against the document
|
|
366
|
+
|
|
367
|
+
`@tetsujs/openapi/testing` checks a response a test provoked against the
|
|
368
|
+
operation the document describes:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import { assertDescribed } from "@tetsujs/openapi/testing";
|
|
372
|
+
|
|
373
|
+
const { document } = openapi(app, { info });
|
|
374
|
+
|
|
375
|
+
test("a wrong code is what the document says", async () => {
|
|
376
|
+
const res = await request("/session", { method: "POST", body });
|
|
377
|
+
|
|
378
|
+
await assertDescribed(document, "POST /session", res);
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
It throws unless the status is declared and the body fits one of the
|
|
383
|
+
alternatives its status describes, listing every problem at once:
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
POST /session answered 403, which the document does not describe:
|
|
387
|
+
- error "CAPTCHA_FAILED", which its 403 does not list: ACCOUNT_DISABLED, CSRF_HEADER_REQUIRED
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The operation is named as the test called it, a method and the path it
|
|
391
|
+
requested, matched against the document's templates. Without a validator
|
|
392
|
+
only the top level of the body is compared — required fields, and fields
|
|
393
|
+
that are a `const`, which is where an envelope keeps its code. `validate`
|
|
394
|
+
checks the whole body with the JSON Schema validator of your choice, given
|
|
395
|
+
a schema that stands on its own; `headers` names headers the status must
|
|
396
|
+
declare when the response carries them:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
await assertDescribed(document, "POST /session", res, {
|
|
400
|
+
validate: (schema, body) => ajv.validate(schema, body) || ajv.errorsText(),
|
|
401
|
+
headers: ["retry-after"],
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The core does not check a thrown error against the route's response map:
|
|
406
|
+
a guard's refusal would be reported on every route it runs on. A test asks
|
|
407
|
+
about the one response it provoked.
|
|
408
|
+
|
|
311
409
|
## Warnings
|
|
312
410
|
|
|
313
411
|
A route is still documented when part of it cannot be described, and the
|
package/dist/src/controller.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface DocsOptions<Path extends string, UiPath extends string, Ui exte
|
|
|
29
29
|
* envelope — see `errors` on {@link openapi}.
|
|
30
30
|
*/
|
|
31
31
|
readonly errors?: ErrorFormat;
|
|
32
|
+
/**
|
|
33
|
+
* What each tag is — see `tags` on {@link openapi}. The controller's own
|
|
34
|
+
* tag, `docs`, is described for you when its routes are in the document
|
|
35
|
+
* (`documentSelf`) and you did not describe it.
|
|
36
|
+
*/
|
|
37
|
+
readonly tags?: Readonly<Record<string, string>>;
|
|
32
38
|
/** Where the document is served. Defaults to `/openapi.json`. */
|
|
33
39
|
readonly path?: Path & ValidatePath<Path>;
|
|
34
40
|
/** Where the page is served. Defaults to `/docs`. */
|
|
@@ -79,6 +85,12 @@ export declare class DocsController<Path extends string, UiPath extends string,
|
|
|
79
85
|
private document;
|
|
80
86
|
private page;
|
|
81
87
|
constructor(options: DocsOptions<Path, UiPath>, documentPath: string, uiPath: string);
|
|
88
|
+
/**
|
|
89
|
+
* The tags to describe: the application's, and this controller's own when
|
|
90
|
+
* its routes are in the document — without it, a described set of tags
|
|
91
|
+
* would report `docs` as a tag nobody described.
|
|
92
|
+
*/
|
|
93
|
+
private tagsOf;
|
|
82
94
|
/**
|
|
83
95
|
* Generates the document of the application this was mounted in, and
|
|
84
96
|
* the page that renders it.
|
|
@@ -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,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,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"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"}
|
package/dist/src/document.d.ts
CHANGED
|
@@ -67,12 +67,29 @@ export interface OperationObject {
|
|
|
67
67
|
}
|
|
68
68
|
/** Every operation declared on one path template. */
|
|
69
69
|
export type PathItemObject = Record<string, OperationObject>;
|
|
70
|
+
/**
|
|
71
|
+
* A tag the document describes: its name and what its operations are.
|
|
72
|
+
*
|
|
73
|
+
* Open to the rest of the specification's fields — `externalDocs` — as
|
|
74
|
+
* `info` is: the generator fills in two, and a document a caller builds
|
|
75
|
+
* or extends by hand may carry more.
|
|
76
|
+
*/
|
|
77
|
+
export interface TagObject {
|
|
78
|
+
readonly name: string;
|
|
79
|
+
readonly description?: string;
|
|
80
|
+
readonly [key: string]: unknown;
|
|
81
|
+
}
|
|
70
82
|
/** The generated document. */
|
|
71
83
|
export interface OpenApiDocument {
|
|
72
84
|
readonly openapi: "3.1.0";
|
|
73
85
|
readonly info: DocumentInfo;
|
|
74
86
|
readonly servers?: readonly DocumentServer[];
|
|
75
87
|
readonly paths: Record<string, PathItemObject>;
|
|
88
|
+
/**
|
|
89
|
+
* The tags the operations are grouped under, in the order a renderer
|
|
90
|
+
* lists them. Present only when the generator was given descriptions.
|
|
91
|
+
*/
|
|
92
|
+
readonly tags?: readonly TagObject[];
|
|
76
93
|
/**
|
|
77
94
|
* What the operations refer to rather than repeat: the security schemes
|
|
78
95
|
* the guards registered, and the failure envelopes the framework fills
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/document.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/document.ts"],"names":[],"mappings":"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"}
|
package/dist/src/emit.d.ts
CHANGED
package/dist/src/emit.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type { DocumentedHeader, DocumentedResponse, HookContributions, HookDocs,
|
|
|
25
25
|
export { documented, secured } from "./annotations.ts";
|
|
26
26
|
export type { DocsOptions } from "./controller.ts";
|
|
27
27
|
export { DocsController, docs } from "./controller.ts";
|
|
28
|
-
export type { ContentMap, DocumentInfo, DocumentServer, HeaderObject, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, } from "./document.ts";
|
|
28
|
+
export type { ContentMap, DocumentInfo, DocumentServer, HeaderObject, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, TagObject, } from "./document.ts";
|
|
29
29
|
export type { DocumentedFailure, ErrorFormat } from "./errors.ts";
|
|
30
30
|
export type { JsonSchema, JsonSchemaKeywords, JsonSchemaType, } from "./json-schema.ts";
|
|
31
31
|
export type { DocsAssets, DocsPageOptions, DocsUi } from "./page.ts";
|
|
@@ -64,6 +64,28 @@ export interface OpenApiOptions {
|
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
66
|
readonly errors?: ErrorFormat;
|
|
67
|
+
/**
|
|
68
|
+
* What each tag is, by name — the sections a renderer's sidebar shows,
|
|
69
|
+
* in the order given here.
|
|
70
|
+
*
|
|
71
|
+
* A tag the routes use and this leaves out is listed after these, in the
|
|
72
|
+
* order the routes first use it, without a description; so is reported,
|
|
73
|
+
* as a likely misspelling — `sign_in` for `sign-in` makes a section of its
|
|
74
|
+
* own. So is a tag described here that no operation uses. Without
|
|
75
|
+
* `tags`, the document lists none and nothing is reported.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* openapi(app, {
|
|
80
|
+
* info,
|
|
81
|
+
* tags: {
|
|
82
|
+
* "sign-in": "Signing in with a code sent by email, and signing out",
|
|
83
|
+
* me: "The signed-in user",
|
|
84
|
+
* },
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
readonly tags?: Readonly<Record<string, string>>;
|
|
67
89
|
}
|
|
68
90
|
/**
|
|
69
91
|
* What the generator could not describe faithfully.
|
|
@@ -74,6 +96,10 @@ export interface OpenApiOptions {
|
|
|
74
96
|
* these are returned rather than swallowed.
|
|
75
97
|
*/
|
|
76
98
|
export interface GeneratorWarning {
|
|
99
|
+
/**
|
|
100
|
+
* The route the warning is about, as `GET /path` — empty for a warning
|
|
101
|
+
* about the document as a whole, such as a described tag nothing uses.
|
|
102
|
+
*/
|
|
77
103
|
readonly route: string;
|
|
78
104
|
readonly message: string;
|
|
79
105
|
}
|
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,
|
|
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,EAA6B,MAAM,eAAe,CAAC;AAEhF,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,EACd,SAAS,GACV,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;IAE9B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,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,CA2G1E"}
|
package/dist/src/index.js
CHANGED
|
@@ -511,17 +511,13 @@ function responses(entry, options, contributed, components, envelopes, warn) {
|
|
|
511
511
|
for (const [status, schema] of declaredResponses(entry)) {
|
|
512
512
|
const described = schema === null ? undefined : emitted(schema, "a response", warn);
|
|
513
513
|
add(status, {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
schemas: described ? branchesFor(described, Number(status), envelopes, warn) : []
|
|
514
|
+
placeholder: describeStatus(status),
|
|
515
|
+
said: described ? wordsOf(described, envelopes) : [],
|
|
516
|
+
schemas: described ? branchesFor(unionOnly(described), Number(status), envelopes, warn) : []
|
|
517
517
|
});
|
|
518
518
|
}
|
|
519
519
|
if (![...answers.keys()].some((status) => status.startsWith("2"))) {
|
|
520
|
-
add("200", {
|
|
521
|
-
description: "Successful response",
|
|
522
|
-
placeholder: true,
|
|
523
|
-
schemas: []
|
|
524
|
-
});
|
|
520
|
+
add("200", { placeholder: "Successful response", said: [], schemas: [] });
|
|
525
521
|
}
|
|
526
522
|
for (const [status, answer] of failures(entry, options, contributed, components, envelopes, warn)) {
|
|
527
523
|
add(String(status), answer);
|
|
@@ -549,7 +545,12 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
549
545
|
found.push([
|
|
550
546
|
options.validationStatus,
|
|
551
547
|
{
|
|
552
|
-
|
|
548
|
+
said: [
|
|
549
|
+
{
|
|
550
|
+
code: "VALIDATION_FAILED",
|
|
551
|
+
text: "Request failed schema validation"
|
|
552
|
+
}
|
|
553
|
+
],
|
|
553
554
|
schemas: [
|
|
554
555
|
envelopes.ref(options.validationStatus, "VALIDATION_FAILED", envelopes.format.describe({
|
|
555
556
|
status: options.validationStatus,
|
|
@@ -566,7 +567,9 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
566
567
|
found.push([
|
|
567
568
|
status,
|
|
568
569
|
{
|
|
569
|
-
|
|
570
|
+
said: [
|
|
571
|
+
said(requirement.error, requirement.description ?? `Request did not satisfy ${requirement.name}`)
|
|
572
|
+
],
|
|
570
573
|
schemas: [envelopeRef(status, requirement.error, requirement.message)]
|
|
571
574
|
}
|
|
572
575
|
]);
|
|
@@ -576,7 +579,7 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
576
579
|
found.push([
|
|
577
580
|
response.status,
|
|
578
581
|
{
|
|
579
|
-
|
|
582
|
+
said: [said(response.error, response.description)],
|
|
580
583
|
schemas: described ? branchesFor(described, response.status, envelopes, warn) : [
|
|
581
584
|
envelopeRef(response.status, response.error, response.message, response.fields)
|
|
582
585
|
],
|
|
@@ -590,7 +593,9 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
590
593
|
found.push([
|
|
591
594
|
400,
|
|
592
595
|
{
|
|
593
|
-
|
|
596
|
+
said: [
|
|
597
|
+
said(unparsable.error, "Body could not be parsed in the declared shape")
|
|
598
|
+
],
|
|
594
599
|
schemas: [envelopeRef(400, unparsable.error, unparsable.message)]
|
|
595
600
|
}
|
|
596
601
|
]);
|
|
@@ -598,7 +603,9 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
598
603
|
found.push([
|
|
599
604
|
413,
|
|
600
605
|
{
|
|
601
|
-
|
|
606
|
+
said: [
|
|
607
|
+
said("BODY_TOO_LARGE", "Body exceeded the configured size limit")
|
|
608
|
+
],
|
|
602
609
|
schemas: [
|
|
603
610
|
envelopeRef(413, "BODY_TOO_LARGE", "Body exceeds the configured limit")
|
|
604
611
|
]
|
|
@@ -608,7 +615,9 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
608
615
|
found.push([
|
|
609
616
|
500,
|
|
610
617
|
{
|
|
611
|
-
|
|
618
|
+
said: [
|
|
619
|
+
said("INTERNAL_SERVER_ERROR", "The request failed and nothing mapped the failure")
|
|
620
|
+
],
|
|
612
621
|
schemas: [
|
|
613
622
|
envelopeRef(500, "INTERNAL_SERVER_ERROR", "Internal Server Error")
|
|
614
623
|
]
|
|
@@ -616,9 +625,24 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
616
625
|
]);
|
|
617
626
|
return found;
|
|
618
627
|
}
|
|
628
|
+
function said(code, text) {
|
|
629
|
+
return code === undefined ? { text } : { code, text };
|
|
630
|
+
}
|
|
631
|
+
function wordsOf(schema, envelopes) {
|
|
632
|
+
if (typeof schema.description === "string") {
|
|
633
|
+
return [said(envelopes.format.code(schema), schema.description)];
|
|
634
|
+
}
|
|
635
|
+
return branchesOf(unionOnly(schema), envelopes.format.code).flatMap((branch) => typeof branch.description === "string" ? [said(envelopes.format.code(branch), branch.description)] : []);
|
|
636
|
+
}
|
|
637
|
+
function unionOnly(schema) {
|
|
638
|
+
if (typeof schema.description !== "string" || !Array.isArray(schema.anyOf) && !Array.isArray(schema.oneOf)) {
|
|
639
|
+
return schema;
|
|
640
|
+
}
|
|
641
|
+
const { description: _, ...union } = schema;
|
|
642
|
+
return union;
|
|
643
|
+
}
|
|
619
644
|
function merge(list, envelopes) {
|
|
620
645
|
const schemas = distinct(list.flatMap((answer) => answer.schemas));
|
|
621
|
-
const worded = list.filter((answer) => !answer.placeholder);
|
|
622
646
|
const headers = {};
|
|
623
647
|
for (const answer of list) {
|
|
624
648
|
for (const [name, header] of Object.entries(answer.headers ?? {})) {
|
|
@@ -626,7 +650,7 @@ function merge(list, envelopes) {
|
|
|
626
650
|
}
|
|
627
651
|
}
|
|
628
652
|
return {
|
|
629
|
-
description: (
|
|
653
|
+
description: wording(list),
|
|
630
654
|
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
631
655
|
...schemas.length > 0 ? {
|
|
632
656
|
content: {
|
|
@@ -635,6 +659,29 @@ function merge(list, envelopes) {
|
|
|
635
659
|
} : {}
|
|
636
660
|
};
|
|
637
661
|
}
|
|
662
|
+
function wording(list) {
|
|
663
|
+
const seen = new Set;
|
|
664
|
+
const items = [];
|
|
665
|
+
for (const answer of list) {
|
|
666
|
+
for (const item of answer.said) {
|
|
667
|
+
const key = `${item.code ?? ""}
|
|
668
|
+
${item.text}`;
|
|
669
|
+
if (!seen.has(key)) {
|
|
670
|
+
seen.add(key);
|
|
671
|
+
items.push(item);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const [only] = items;
|
|
676
|
+
if (items.length === 1 && only) {
|
|
677
|
+
return only.text;
|
|
678
|
+
}
|
|
679
|
+
if (items.length > 1) {
|
|
680
|
+
return items.map((item) => item.code === undefined ? `- ${item.text}` : `- \`${item.code}\`: ${item.text}`).join(`
|
|
681
|
+
`);
|
|
682
|
+
}
|
|
683
|
+
return list.find((answer) => answer.placeholder)?.placeholder ?? "";
|
|
684
|
+
}
|
|
638
685
|
function unionOf(schemas, envelopes) {
|
|
639
686
|
const [only] = schemas;
|
|
640
687
|
if (schemas.length === 1 && only) {
|
|
@@ -800,11 +847,18 @@ class DocsController {
|
|
|
800
847
|
this.json = { ...json, path: documentPath };
|
|
801
848
|
this.ui = options.ui === false ? undefined : { ...ui, path: uiPath };
|
|
802
849
|
}
|
|
850
|
+
tagsOf(tags) {
|
|
851
|
+
if (!this.options.documentSelf || Object.hasOwn(tags, "docs")) {
|
|
852
|
+
return tags;
|
|
853
|
+
}
|
|
854
|
+
return { ...tags, docs: "This documentation" };
|
|
855
|
+
}
|
|
803
856
|
[onMount](app) {
|
|
804
857
|
const generated = openapi(app, {
|
|
805
858
|
info: this.options.info,
|
|
806
859
|
...this.options.servers ? { servers: this.options.servers } : {},
|
|
807
|
-
...this.options.errors ? { errors: this.options.errors } : {}
|
|
860
|
+
...this.options.errors ? { errors: this.options.errors } : {},
|
|
861
|
+
...this.options.tags ? { tags: this.tagsOf(this.options.tags) } : {}
|
|
808
862
|
});
|
|
809
863
|
this.document = generated.document;
|
|
810
864
|
const ui = this.options.ui ?? "scalar";
|
|
@@ -828,7 +882,7 @@ function docs(options) {
|
|
|
828
882
|
return new DocsController(options, options.path ?? "/openapi.json", options.uiPath ?? "/docs");
|
|
829
883
|
}
|
|
830
884
|
function warn(warning) {
|
|
831
|
-
console.warn(`[openapi] ${warning.route}: ${warning.message}`);
|
|
885
|
+
console.warn(warning.route === "" ? `[openapi] ${warning.message}` : `[openapi] ${warning.route}: ${warning.message}`);
|
|
832
886
|
}
|
|
833
887
|
|
|
834
888
|
// packages/openapi/src/index.ts
|
|
@@ -844,6 +898,7 @@ function openapi(app, options) {
|
|
|
844
898
|
declareEnvelopes(entry, envelopes2);
|
|
845
899
|
}
|
|
846
900
|
const occupied = new Map;
|
|
901
|
+
const used = new Map;
|
|
847
902
|
for (const entry of described) {
|
|
848
903
|
const template = toTemplate(entry.path);
|
|
849
904
|
const route = `${entry.method} ${entry.path}`;
|
|
@@ -872,22 +927,51 @@ function openapi(app, options) {
|
|
|
872
927
|
});
|
|
873
928
|
}
|
|
874
929
|
occupied.set(slot, route);
|
|
930
|
+
for (const tag of operation.tags ?? []) {
|
|
931
|
+
if (!used.has(tag)) {
|
|
932
|
+
used.set(tag, route);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
875
935
|
paths[template] = {
|
|
876
936
|
...paths[template],
|
|
877
937
|
[entry.method.toLowerCase()]: operation
|
|
878
938
|
};
|
|
879
939
|
}
|
|
940
|
+
const tags = options.tags ? tagsOf(options.tags, used, warnings) : undefined;
|
|
880
941
|
return {
|
|
881
942
|
document: {
|
|
882
943
|
openapi: "3.1.0",
|
|
883
944
|
info: options.info,
|
|
884
945
|
...options.servers ? { servers: options.servers } : {},
|
|
946
|
+
...tags ? { tags } : {},
|
|
885
947
|
paths,
|
|
886
948
|
...componentsOf(schemes, components.schemas)
|
|
887
949
|
},
|
|
888
950
|
warnings
|
|
889
951
|
};
|
|
890
952
|
}
|
|
953
|
+
function tagsOf(described, used, warnings) {
|
|
954
|
+
const tags = Object.entries(described).map(([name, description]) => ({ name, description }));
|
|
955
|
+
for (const [name, route] of used) {
|
|
956
|
+
if (Object.hasOwn(described, name)) {
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
tags.push({ name });
|
|
960
|
+
warnings.push({
|
|
961
|
+
route,
|
|
962
|
+
message: `the tag "${name}" is not in tags; it is listed after them, without a description`
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
for (const name of Object.keys(described)) {
|
|
966
|
+
if (!used.has(name)) {
|
|
967
|
+
warnings.push({
|
|
968
|
+
route: "",
|
|
969
|
+
message: `the tag "${name}" is described in tags, but no operation uses it`
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return tags;
|
|
974
|
+
}
|
|
891
975
|
function componentsOf(securitySchemes, schemas) {
|
|
892
976
|
const components = {
|
|
893
977
|
...Object.keys(securitySchemes).length > 0 ? { securitySchemes } : {},
|
|
@@ -904,5 +988,5 @@ export {
|
|
|
904
988
|
secured
|
|
905
989
|
};
|
|
906
990
|
|
|
907
|
-
//# debugId=
|
|
991
|
+
//# debugId=3A3B60517CA111D464756E2164756E21
|
|
908
992
|
//# sourceMappingURL=index.js.map
|