@tetsujs/openapi 0.5.1 → 0.5.3
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 +91 -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/index.d.ts +27 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +40 -3
- package/dist/src/index.js.map +4 -4
- 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/index.ts +85 -1
- 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
|
|
|
@@ -325,6 +362,50 @@ await Bun.write("openapi.json", JSON.stringify(document, null, 2));
|
|
|
325
362
|
|
|
326
363
|
`docsPage()` renders the page on its own, for serving it elsewhere.
|
|
327
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
|
+
|
|
328
409
|
## Warnings
|
|
329
410
|
|
|
330
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/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
|
@@ -847,11 +847,18 @@ class DocsController {
|
|
|
847
847
|
this.json = { ...json, path: documentPath };
|
|
848
848
|
this.ui = options.ui === false ? undefined : { ...ui, path: uiPath };
|
|
849
849
|
}
|
|
850
|
+
tagsOf(tags) {
|
|
851
|
+
if (!this.options.documentSelf || Object.hasOwn(tags, "docs")) {
|
|
852
|
+
return tags;
|
|
853
|
+
}
|
|
854
|
+
return { ...tags, docs: "This documentation" };
|
|
855
|
+
}
|
|
850
856
|
[onMount](app) {
|
|
851
857
|
const generated = openapi(app, {
|
|
852
858
|
info: this.options.info,
|
|
853
859
|
...this.options.servers ? { servers: this.options.servers } : {},
|
|
854
|
-
...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) } : {}
|
|
855
862
|
});
|
|
856
863
|
this.document = generated.document;
|
|
857
864
|
const ui = this.options.ui ?? "scalar";
|
|
@@ -875,7 +882,7 @@ function docs(options) {
|
|
|
875
882
|
return new DocsController(options, options.path ?? "/openapi.json", options.uiPath ?? "/docs");
|
|
876
883
|
}
|
|
877
884
|
function warn(warning) {
|
|
878
|
-
console.warn(`[openapi] ${warning.route}: ${warning.message}`);
|
|
885
|
+
console.warn(warning.route === "" ? `[openapi] ${warning.message}` : `[openapi] ${warning.route}: ${warning.message}`);
|
|
879
886
|
}
|
|
880
887
|
|
|
881
888
|
// packages/openapi/src/index.ts
|
|
@@ -891,6 +898,7 @@ function openapi(app, options) {
|
|
|
891
898
|
declareEnvelopes(entry, envelopes2);
|
|
892
899
|
}
|
|
893
900
|
const occupied = new Map;
|
|
901
|
+
const used = new Map;
|
|
894
902
|
for (const entry of described) {
|
|
895
903
|
const template = toTemplate(entry.path);
|
|
896
904
|
const route = `${entry.method} ${entry.path}`;
|
|
@@ -919,22 +927,51 @@ function openapi(app, options) {
|
|
|
919
927
|
});
|
|
920
928
|
}
|
|
921
929
|
occupied.set(slot, route);
|
|
930
|
+
for (const tag of operation.tags ?? []) {
|
|
931
|
+
if (!used.has(tag)) {
|
|
932
|
+
used.set(tag, route);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
922
935
|
paths[template] = {
|
|
923
936
|
...paths[template],
|
|
924
937
|
[entry.method.toLowerCase()]: operation
|
|
925
938
|
};
|
|
926
939
|
}
|
|
940
|
+
const tags = options.tags ? tagsOf(options.tags, used, warnings) : undefined;
|
|
927
941
|
return {
|
|
928
942
|
document: {
|
|
929
943
|
openapi: "3.1.0",
|
|
930
944
|
info: options.info,
|
|
931
945
|
...options.servers ? { servers: options.servers } : {},
|
|
946
|
+
...tags ? { tags } : {},
|
|
932
947
|
paths,
|
|
933
948
|
...componentsOf(schemes, components.schemas)
|
|
934
949
|
},
|
|
935
950
|
warnings
|
|
936
951
|
};
|
|
937
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
|
+
}
|
|
938
975
|
function componentsOf(securitySchemes, schemas) {
|
|
939
976
|
const components = {
|
|
940
977
|
...Object.keys(securitySchemes).length > 0 ? { securitySchemes } : {},
|
|
@@ -951,5 +988,5 @@ export {
|
|
|
951
988
|
secured
|
|
952
989
|
};
|
|
953
990
|
|
|
954
|
-
//# debugId=
|
|
991
|
+
//# debugId=3A3B60517CA111D464756E2164756E21
|
|
955
992
|
//# sourceMappingURL=index.js.map
|