@zudojs/openapi 1.4.0 → 1.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 +83 -2
- package/dist/index.d.ts +10 -6
- package/dist/index.js +8 -4
- package/dist/openApiConstants/index.d.ts +1 -1
- package/dist/openApiConstants/index.js +1 -1
- package/dist/openApiConstants/openApiConstants.core.d.ts +5 -0
- package/dist/openApiConstants/openApiConstants.core.js +5 -0
- package/dist/openApiFromRoutes/index.d.ts +11 -0
- package/dist/openApiFromRoutes/index.js +10 -0
- package/dist/openApiFromRoutes/openApiFromRoutes.document.d.ts +43 -0
- package/dist/openApiFromRoutes/openApiFromRoutes.document.js +64 -0
- package/dist/openApiFromRoutes/openApiFromRoutes.type.d.ts +56 -0
- package/dist/openApiFromRoutes/openApiFromRoutes.type.js +5 -0
- package/dist/openApiHttp/openApiHttpAdapter.core.d.ts +28 -1
- package/dist/openApiHttp/openApiHttpAdapter.core.js +40 -1
- package/dist/openApiRouting/index.d.ts +3 -1
- package/dist/openApiRouting/index.js +2 -0
- package/dist/openApiRouting/routeContent.core.d.ts +22 -0
- package/dist/openApiRouting/routeContent.core.js +101 -0
- package/dist/openApiRouting/routeConverter.core.d.ts +11 -5
- package/dist/openApiRouting/routeConverter.core.js +23 -28
- package/dist/openApiRouting/routeMetadata.type.d.ts +36 -2
- package/dist/openApiRouting/routeScanner.core.d.ts +7 -3
- package/dist/openApiRouting/routeScanner.core.js +7 -3
- package/dist/openApiRouting/routeSchema.core.d.ts +19 -0
- package/dist/openApiRouting/routeSchema.core.js +93 -0
- package/dist/openApiSchema/index.d.ts +1 -0
- package/dist/openApiSchema/index.js +1 -0
- package/dist/openApiSchema/schemaInput.core.d.ts +28 -0
- package/dist/openApiSchema/schemaInput.core.js +36 -0
- package/dist/openApiSchema/schemaInput.type.d.ts +48 -0
- package/dist/openApiSchema/schemaInput.type.js +5 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -51,8 +51,12 @@ const yaml = manager.toYAML();
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Every documented response reaches the document — `4xx`, `5xx` and `default`
|
|
54
|
-
included. `:id` becomes `{id}`,
|
|
55
|
-
|
|
54
|
+
included. `:id` becomes `{id}`, every template slot is documented as a
|
|
55
|
+
required path parameter (declared or not), and `params` / `query` /
|
|
56
|
+
`headers` / `body` / response `schema` fields accept `@zudojs/schema` schemas
|
|
57
|
+
— see [Generating from a route table](#generating-from-a-route-table). To
|
|
58
|
+
generate from an `@zudojs/http` router instead of adding routes by hand, use
|
|
59
|
+
that package's `generateOpenAPIDocument(router)` or `mountOpenAPI(router)`.
|
|
56
60
|
|
|
57
61
|
Generation is idempotent: call `generate()` as often as you like.
|
|
58
62
|
|
|
@@ -134,6 +138,83 @@ scheme is read after stripping the control characters browsers ignore, so
|
|
|
134
138
|
`customCss` containing `</style>` throws — that sequence ends the style block
|
|
135
139
|
and lets the rest be parsed as HTML.
|
|
136
140
|
|
|
141
|
+
## Generating from a route table
|
|
142
|
+
|
|
143
|
+
`createOpenAPIDocumentFromRoutes(routes, options)` builds a document from a
|
|
144
|
+
list of structural `OpenAPIRouteDescriptor`s, so any route source can be
|
|
145
|
+
documented from what it actually registers. `@zudojs/http`'s
|
|
146
|
+
`generateOpenAPIDocument(router)` and `mountOpenAPI(router)` feed it the
|
|
147
|
+
router's routes; other sources (such as `@zudojs/api` operations) build the
|
|
148
|
+
same descriptors.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { createOpenAPIDocumentFromRoutes } from "@zudojs/openapi";
|
|
152
|
+
import { objectSchema, stringSchema } from "@zudojs/schema";
|
|
153
|
+
|
|
154
|
+
const user = objectSchema({ id: stringSchema().uuid(), name: stringSchema() });
|
|
155
|
+
|
|
156
|
+
const document = createOpenAPIDocumentFromRoutes(
|
|
157
|
+
[
|
|
158
|
+
{ method: "GET", path: "/users/:id", summary: "Get a user", tags: ["users"],
|
|
159
|
+
responses: { "200": { schema: user }, "404": { description: "No such user" } } },
|
|
160
|
+
{ method: "POST", path: "/users", operationId: "users.create",
|
|
161
|
+
body: objectSchema({ name: stringSchema() }),
|
|
162
|
+
responses: { "201": { schema: user } } },
|
|
163
|
+
],
|
|
164
|
+
{
|
|
165
|
+
info: { title: "Users API", version: "1.0.0" },
|
|
166
|
+
securitySchemes: { bearer: { type: "http", scheme: "bearer" } },
|
|
167
|
+
security: [{ bearer: [] }],
|
|
168
|
+
schemas: { User: user }, // components; converted when they are @zudojs/schema schemas
|
|
169
|
+
validate: true,
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
An `OpenAPIRouteDescriptor` is `RouteOpenAPIMetadata` plus `method` and
|
|
175
|
+
`path`, flattened:
|
|
176
|
+
|
|
177
|
+
| Field | Meaning |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| `method` | Any case; must be `get put post delete options head patch trace` |
|
|
180
|
+
| `path` | `/users/:id` or `/users/{id}`; optional/regex/wildcard segments are resolved by the source |
|
|
181
|
+
| `summary`, `description`, `operationId`, `tags`, `deprecated`, `servers`, `externalDocs` | Copied to the operation |
|
|
182
|
+
| `security` | Operation security; `[]` marks it public (overrides document security) |
|
|
183
|
+
| `params`, `query`, `headers`, `cookies` | One object schema each; every property becomes a parameter (`required` from the schema; path parameters always required) |
|
|
184
|
+
| `body` | A schema (sent as `application/json`, required) or `{ schema, contentType?, required?, description?, example? }`; a raw `requestBody` wins |
|
|
185
|
+
| `responses` | Per status / `NXX` / `default`: a Response Object, or `{ schema, description?, contentType?, headers?, example? }` (description defaults to the reason phrase, e.g. "Not Found") |
|
|
186
|
+
| `parameters` | Explicit parameters, highest precedence |
|
|
187
|
+
| `inferredParameters` | Parameters the source derived itself (e.g. a regex constraint), lowest precedence |
|
|
188
|
+
| `hidden` | `true` leaves the operation out |
|
|
189
|
+
|
|
190
|
+
Every path template slot is documented as a required string parameter even
|
|
191
|
+
when nothing declares it, and a declared path parameter the template does
|
|
192
|
+
not contain is dropped with a warning rather than producing an invalid
|
|
193
|
+
document. The same metadata fields work in `manager.addRoute({ method, path,
|
|
194
|
+
metadata: { openapi } })`. Conversion warnings reach `onRouteWarning`
|
|
195
|
+
(one message at a time), `onSchemaWarning` under the name `"routes"`, and
|
|
196
|
+
`manager.routeWarnings()`, each prefixed with the route (`DELETE /users/:id: ...`).
|
|
197
|
+
|
|
198
|
+
No response status is invented. A route that documents no responses (or
|
|
199
|
+
`responses: {}`) gets a spec-valid `default` response described as
|
|
200
|
+
`"Undocumented response"` (`UNDOCUMENTED_RESPONSE_DESCRIPTION`) and a route
|
|
201
|
+
warning, so the omission shows up instead of a `204` DELETE being published
|
|
202
|
+
as `200`:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const document = createOpenAPIDocumentFromRoutes(
|
|
206
|
+
[{ method: "DELETE", path: "/users/:id" }],
|
|
207
|
+
{ info, onRouteWarning: (message) => logger.warn(message) },
|
|
208
|
+
);
|
|
209
|
+
// paths["/users/{id}"].delete.responses → { default: { description: "Undocumented response" } }
|
|
210
|
+
// warned: "DELETE /users/:id: no responses are documented; ..."
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`createOpenAPIManagerFromRoutes(routes, options)` returns the manager instead
|
|
214
|
+
(for `toResponse` / `toUIResponse`); `manager.setRoutes(routes)` replaces its
|
|
215
|
+
whole route set, rejecting duplicates before anything changes, and
|
|
216
|
+
`routeDescriptorToRouteInfo` converts a descriptor to `addRoute`'s shape.
|
|
217
|
+
|
|
137
218
|
## Branding
|
|
138
219
|
|
|
139
220
|
ReDoc, Scalar and several other viewers read a logo from the non-standard
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* API contract and documentation engine for the Zudojs framework.
|
|
5
5
|
*
|
|
6
6
|
* Generates OpenAPI specifications from application routes, schemas,
|
|
7
|
-
* and metadata. Supports OpenAPI 3.0 and 3.1.
|
|
7
|
+
* and metadata. Supports OpenAPI 3.0 and 3.1. A route table from any source
|
|
8
|
+
* becomes a document through `createOpenAPIDocumentFromRoutes`;
|
|
9
|
+
* `@zudojs/http` uses it to document a router's registered routes.
|
|
8
10
|
*
|
|
9
11
|
* @example
|
|
10
12
|
* ```ts
|
|
@@ -42,11 +44,13 @@ export { OpenAPIDocumentBuilder, createOpenAPIDocumentBuilder, type OpenAPIDocum
|
|
|
42
44
|
export { OpenAPIRegistryImpl } from "./openApiRegistry/index.js";
|
|
43
45
|
export type { OpenAPIRegistry, OpenAPIRoute, OpenAPIComponentRegistration, } from "./openApiRegistry/index.js";
|
|
44
46
|
export { OpenAPIError, OpenAPIValidationError, OpenAPIDocumentError, OpenAPIComponentError, OpenAPIComponentConflictError, OpenAPIReferenceError, OpenAPIRouteError, OpenAPISchemaError, OpenAPISerializationError, OpenAPIVersionError, OpenAPIOperationError, createOpenAPIError, isOpenAPIError, formatIssuePath, type OpenAPIErrorOptions, type OpenAPIValidationIssue, } from "./openApiErrors/index.js";
|
|
45
|
-
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants/index.js";
|
|
46
|
-
export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, } from "./openApiRouting/index.js";
|
|
47
|
-
export type { RouteMetadata, RouteOpenAPIMetadata, RouteParameterMetadata, RouteInfo, OpenAPIHttpMethod, } from "./openApiRouting/index.js";
|
|
48
|
-
export {
|
|
49
|
-
export type {
|
|
47
|
+
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, UNDOCUMENTED_RESPONSE_DESCRIPTION, } from "./openApiConstants/index.js";
|
|
48
|
+
export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, buildOperationParameters, buildOperationRequestBody, buildOperationResponses, describeResponseKey, } from "./openApiRouting/index.js";
|
|
49
|
+
export type { RouteMetadata, RouteOpenAPIMetadata, RouteParameterMetadata, RouteInfo, OpenAPIHttpMethod, OpenAPISchemaInput, OpenAPIRouteBody, OpenAPIRouteResponse, RouteConversionOptions, } from "./openApiRouting/index.js";
|
|
50
|
+
export { createOpenAPIDocumentFromRoutes, createOpenAPIManagerFromRoutes, routeDescriptorToRouteInfo, } from "./openApiFromRoutes/index.js";
|
|
51
|
+
export type { OpenAPIRouteDescriptor, OpenAPIDocumentFromRoutesOptions, } from "./openApiFromRoutes/index.js";
|
|
52
|
+
export { convertSchema, createSchemaConverter, isVersion31, SchemaRegistryImpl, createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, isSchemaDefinition, resolveSchemaInput, } from "./openApiSchema/index.js";
|
|
53
|
+
export type { SchemaInputOptions, SchemaConverter, SchemaConversionResult, SchemaConversionOptions, SchemaRegistry, SchemaRegistryOptions, ComponentSection, } from "./openApiSchema/index.js";
|
|
50
54
|
export { OpenAPIValidatorImpl, createOpenAPIValidator, } from "./openApiValidation/index.js";
|
|
51
55
|
export type { OpenAPIValidator, OpenAPIValidationResult, } from "./openApiValidation/index.js";
|
|
52
56
|
export { toOpenAPIJSON, toOpenAPIYAML, } from "./openApiSerialization/openApiSerializer.core.js";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* API contract and documentation engine for the Zudojs framework.
|
|
5
5
|
*
|
|
6
6
|
* Generates OpenAPI specifications from application routes, schemas,
|
|
7
|
-
* and metadata. Supports OpenAPI 3.0 and 3.1.
|
|
7
|
+
* and metadata. Supports OpenAPI 3.0 and 3.1. A route table from any source
|
|
8
|
+
* becomes a document through `createOpenAPIDocumentFromRoutes`;
|
|
9
|
+
* `@zudojs/http` uses it to document a router's registered routes.
|
|
8
10
|
*
|
|
9
11
|
* @example
|
|
10
12
|
* ```ts
|
|
@@ -46,11 +48,13 @@ export { OpenAPIRegistryImpl } from "./openApiRegistry/index.js";
|
|
|
46
48
|
/* ─── Errors ────────────────────────────────────────────────────────────── */
|
|
47
49
|
export { OpenAPIError, OpenAPIValidationError, OpenAPIDocumentError, OpenAPIComponentError, OpenAPIComponentConflictError, OpenAPIReferenceError, OpenAPIRouteError, OpenAPISchemaError, OpenAPISerializationError, OpenAPIVersionError, OpenAPIOperationError, createOpenAPIError, isOpenAPIError, formatIssuePath, } from "./openApiErrors/index.js";
|
|
48
50
|
/* ─── Constants ─────────────────────────────────────────────────────────── */
|
|
49
|
-
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants/index.js";
|
|
51
|
+
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, UNDOCUMENTED_RESPONSE_DESCRIPTION, } from "./openApiConstants/index.js";
|
|
50
52
|
/* ─── Routing ───────────────────────────────────────────────────────────── */
|
|
51
|
-
export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, } from "./openApiRouting/index.js";
|
|
53
|
+
export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, buildOperationParameters, buildOperationRequestBody, buildOperationResponses, describeResponseKey, } from "./openApiRouting/index.js";
|
|
54
|
+
/* ─── Generation from a route table ────────────────────────────────────── */
|
|
55
|
+
export { createOpenAPIDocumentFromRoutes, createOpenAPIManagerFromRoutes, routeDescriptorToRouteInfo, } from "./openApiFromRoutes/index.js";
|
|
52
56
|
/* ─── Schema conversion ─────────────────────────────────────────────────── */
|
|
53
|
-
export { convertSchema, createSchemaConverter, isVersion31, SchemaRegistryImpl, createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, } from "./openApiSchema/index.js";
|
|
57
|
+
export { convertSchema, createSchemaConverter, isVersion31, SchemaRegistryImpl, createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, isSchemaDefinition, resolveSchemaInput, } from "./openApiSchema/index.js";
|
|
54
58
|
/* ─── Validation ────────────────────────────────────────────────────────── */
|
|
55
59
|
export { OpenAPIValidatorImpl, createOpenAPIValidator, } from "./openApiValidation/index.js";
|
|
56
60
|
/* ─── Serialization ─────────────────────────────────────────────────────── */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/openapi/openApiConstants
|
|
3
3
|
*/
|
|
4
|
-
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants.core.js";
|
|
4
|
+
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, UNDOCUMENTED_RESPONSE_DESCRIPTION, } from "./openApiConstants.core.js";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/openapi/openApiConstants
|
|
3
3
|
*/
|
|
4
|
-
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants.core.js";
|
|
4
|
+
export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, UNDOCUMENTED_RESPONSE_DESCRIPTION, } from "./openApiConstants.core.js";
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -39,6 +39,11 @@ export declare const STATUS_CODE_CATEGORIES: {
|
|
|
39
39
|
export declare const RESPONSE_KEY_PATTERN: RegExp;
|
|
40
40
|
/** OpenAPI path template parameter, e.g. `{orderId}`. */
|
|
41
41
|
export declare const PATH_TEMPLATE_PARAMETER: RegExp;
|
|
42
|
+
/**
|
|
43
|
+
* Description of the `default` response emitted for an operation that
|
|
44
|
+
* documents no responses. No status code is invented for it.
|
|
45
|
+
*/
|
|
46
|
+
export declare const UNDOCUMENTED_RESPONSE_DESCRIPTION = "Undocumented response";
|
|
42
47
|
/**
|
|
43
48
|
* Default server URL.
|
|
44
49
|
*/
|
|
@@ -46,6 +46,11 @@ export const STATUS_CODE_CATEGORIES = {
|
|
|
46
46
|
export const RESPONSE_KEY_PATTERN = /^(default|[1-5](XX|\d{2}))$/;
|
|
47
47
|
/** OpenAPI path template parameter, e.g. `{orderId}`. */
|
|
48
48
|
export const PATH_TEMPLATE_PARAMETER = /\{([^{}]+)\}/g;
|
|
49
|
+
/**
|
|
50
|
+
* Description of the `default` response emitted for an operation that
|
|
51
|
+
* documents no responses. No status code is invented for it.
|
|
52
|
+
*/
|
|
53
|
+
export const UNDOCUMENTED_RESPONSE_DESCRIPTION = "Undocumented response";
|
|
49
54
|
/**
|
|
50
55
|
* Default server URL.
|
|
51
56
|
*/
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/openapi/openApiFromRoutes
|
|
3
|
+
*
|
|
4
|
+
* Generates a document from a route table described by transport-neutral
|
|
5
|
+
* {@link OpenAPIRouteDescriptor}s, so any route source — an `@zudojs/http`
|
|
6
|
+
* router, an `@zudojs/api` registry, a hand-written list — can be documented
|
|
7
|
+
* from what it actually registers.
|
|
8
|
+
*/
|
|
9
|
+
export type { OpenAPIRouteDescriptor, OpenAPIDocumentFromRoutesOptions, } from "./openApiFromRoutes.type.js";
|
|
10
|
+
export { createOpenAPIDocumentFromRoutes, createOpenAPIManagerFromRoutes, routeDescriptorToRouteInfo, } from "./openApiFromRoutes.document.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/openapi/openApiFromRoutes
|
|
3
|
+
*
|
|
4
|
+
* Generates a document from a route table described by transport-neutral
|
|
5
|
+
* {@link OpenAPIRouteDescriptor}s, so any route source — an `@zudojs/http`
|
|
6
|
+
* router, an `@zudojs/api` registry, a hand-written list — can be documented
|
|
7
|
+
* from what it actually registers.
|
|
8
|
+
*/
|
|
9
|
+
export { createOpenAPIDocumentFromRoutes, createOpenAPIManagerFromRoutes, routeDescriptorToRouteInfo, } from "./openApiFromRoutes.document.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document generation from a route table.
|
|
3
|
+
*/
|
|
4
|
+
import type { OpenAPIDocument } from "../openApiTypes/openApiTypes.core.js";
|
|
5
|
+
import type { RouteInfo } from "../openApiRouting/routeMetadata.type.js";
|
|
6
|
+
import { OpenAPIManager } from "../openApiHttp/openApiHttpAdapter.core.js";
|
|
7
|
+
import type { OpenAPIDocumentFromRoutesOptions, OpenAPIRouteDescriptor } from "./openApiFromRoutes.type.js";
|
|
8
|
+
/**
|
|
9
|
+
* Splits a descriptor into the manager's `{ method, path, metadata }` shape.
|
|
10
|
+
*
|
|
11
|
+
* @throws {OpenAPIRouteError} When the method has no OpenAPI path item field.
|
|
12
|
+
*/
|
|
13
|
+
export declare function routeDescriptorToRouteInfo(descriptor: OpenAPIRouteDescriptor): RouteInfo;
|
|
14
|
+
/**
|
|
15
|
+
* Creates an {@link OpenAPIManager} holding `routes`, ready to generate,
|
|
16
|
+
* serialize and serve (`toResponse`, `toUIResponse`). Refresh it later with
|
|
17
|
+
* `manager.setRoutes(descriptors.map(routeDescriptorToRouteInfo))`.
|
|
18
|
+
*
|
|
19
|
+
* @throws {OpenAPIRouteError} On an unsupported method or a duplicate route.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createOpenAPIManagerFromRoutes(routes: readonly OpenAPIRouteDescriptor[], options: OpenAPIDocumentFromRoutesOptions): OpenAPIManager;
|
|
22
|
+
/**
|
|
23
|
+
* Generates an OpenAPI document from a list of route descriptors.
|
|
24
|
+
*
|
|
25
|
+
* This is the transport-neutral entry point: `@zudojs/http` feeds it the
|
|
26
|
+
* routes a router actually has registered, and any other source can feed it
|
|
27
|
+
* the same structural {@link OpenAPIRouteDescriptor}s.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* const document = createOpenAPIDocumentFromRoutes(
|
|
31
|
+
* [{ method: "GET", path: "/users/:id", summary: "Get a user",
|
|
32
|
+
* responses: { "200": { schema: userSchema } } }],
|
|
33
|
+
* { info: { title: "Users", version: "1.0.0" }, validate: true },
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @throws {OpenAPIRouteError} On an unsupported method, an inexpressible
|
|
38
|
+
* path or a duplicate route.
|
|
39
|
+
* @throws {OpenAPIValidationError} When `validate` is set and the document
|
|
40
|
+
* is invalid.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createOpenAPIDocumentFromRoutes(routes: readonly OpenAPIRouteDescriptor[], options: OpenAPIDocumentFromRoutesOptions): OpenAPIDocument;
|
|
43
|
+
//# sourceMappingURL=openApiFromRoutes.document.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document generation from a route table.
|
|
3
|
+
*/
|
|
4
|
+
import { isOpenAPIMethod } from "../openApiRouting/routeConverter.core.js";
|
|
5
|
+
import { isSchemaDefinition } from "../openApiSchema/schemaInput.core.js";
|
|
6
|
+
import { OpenAPIManager } from "../openApiHttp/openApiHttpAdapter.core.js";
|
|
7
|
+
import { OpenAPIRouteError } from "../openApiErrors/openApiError.types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Splits a descriptor into the manager's `{ method, path, metadata }` shape.
|
|
10
|
+
*
|
|
11
|
+
* @throws {OpenAPIRouteError} When the method has no OpenAPI path item field.
|
|
12
|
+
*/
|
|
13
|
+
export function routeDescriptorToRouteInfo(descriptor) {
|
|
14
|
+
const { method, path, ...openapi } = descriptor;
|
|
15
|
+
if (typeof method !== "string" || !isOpenAPIMethod(method)) {
|
|
16
|
+
throw new OpenAPIRouteError(`HTTP method "${String(method)}" of route ${String(path)} has no OpenAPI path item field.`, { metadata: { method, path } });
|
|
17
|
+
}
|
|
18
|
+
return { method: method.toLowerCase(), path, metadata: { openapi } };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates an {@link OpenAPIManager} holding `routes`, ready to generate,
|
|
22
|
+
* serialize and serve (`toResponse`, `toUIResponse`). Refresh it later with
|
|
23
|
+
* `manager.setRoutes(descriptors.map(routeDescriptorToRouteInfo))`.
|
|
24
|
+
*
|
|
25
|
+
* @throws {OpenAPIRouteError} On an unsupported method or a duplicate route.
|
|
26
|
+
*/
|
|
27
|
+
export function createOpenAPIManagerFromRoutes(routes, options) {
|
|
28
|
+
const { validate: _validate, securitySchemes, schemas, ...managerOptions } = options;
|
|
29
|
+
const manager = new OpenAPIManager({ ...managerOptions, cacheTtlMs: 0 });
|
|
30
|
+
for (const [name, scheme] of Object.entries(securitySchemes ?? {})) {
|
|
31
|
+
manager.addSecurityScheme(name, scheme);
|
|
32
|
+
}
|
|
33
|
+
for (const [name, schema] of Object.entries(schemas ?? {})) {
|
|
34
|
+
if (isSchemaDefinition(schema))
|
|
35
|
+
manager.addSchema(name, schema);
|
|
36
|
+
else
|
|
37
|
+
manager.addRawSchema(name, schema);
|
|
38
|
+
}
|
|
39
|
+
return manager.setRoutes(routes.map(routeDescriptorToRouteInfo));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generates an OpenAPI document from a list of route descriptors.
|
|
43
|
+
*
|
|
44
|
+
* This is the transport-neutral entry point: `@zudojs/http` feeds it the
|
|
45
|
+
* routes a router actually has registered, and any other source can feed it
|
|
46
|
+
* the same structural {@link OpenAPIRouteDescriptor}s.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* const document = createOpenAPIDocumentFromRoutes(
|
|
50
|
+
* [{ method: "GET", path: "/users/:id", summary: "Get a user",
|
|
51
|
+
* responses: { "200": { schema: userSchema } } }],
|
|
52
|
+
* { info: { title: "Users", version: "1.0.0" }, validate: true },
|
|
53
|
+
* );
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @throws {OpenAPIRouteError} On an unsupported method, an inexpressible
|
|
57
|
+
* path or a duplicate route.
|
|
58
|
+
* @throws {OpenAPIValidationError} When `validate` is set and the document
|
|
59
|
+
* is invalid.
|
|
60
|
+
*/
|
|
61
|
+
export function createOpenAPIDocumentFromRoutes(routes, options) {
|
|
62
|
+
return createOpenAPIManagerFromRoutes(routes, options).generate(options.validate ?? false);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=openApiFromRoutes.document.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-neutral input for generating a document from a route table.
|
|
3
|
+
*/
|
|
4
|
+
import type { OpenAPIInfo, OpenAPISecurityScheme } from "../openApiTypes/openApiTypes.core.js";
|
|
5
|
+
import type { RouteOpenAPIMetadata } from "../openApiRouting/routeMetadata.type.js";
|
|
6
|
+
import type { OpenAPIManagerOptions } from "../openApiHttp/openApiHttpAdapter.core.js";
|
|
7
|
+
/**
|
|
8
|
+
* One operation, described structurally so that any route source can feed
|
|
9
|
+
* {@link createOpenAPIDocumentFromRoutes}: an `@zudojs/http` router, an
|
|
10
|
+
* `@zudojs/api` operation registry, or a hand-written list.
|
|
11
|
+
*
|
|
12
|
+
* It is {@link RouteOpenAPIMetadata} plus the operation's method and path,
|
|
13
|
+
* flattened into one object:
|
|
14
|
+
*
|
|
15
|
+
* - `method` — any case (`"GET"`, `"post"`); it must be one an OpenAPI path
|
|
16
|
+
* item can carry (`get put post delete options head patch trace`).
|
|
17
|
+
* - `path` — `/users/:id` or `/users/{id}`. Optional segments, typed or
|
|
18
|
+
* regex-constrained `:name(...)` segments and wildcards have no OpenAPI
|
|
19
|
+
* spelling; the source resolves them (see `inferredParameters`) before
|
|
20
|
+
* handing the path over.
|
|
21
|
+
* - `summary`, `description`, `operationId`, `tags`, `deprecated`,
|
|
22
|
+
* `security` (`[]` marks a public operation), `servers`, `externalDocs`.
|
|
23
|
+
* - `params`, `query`, `headers`, `cookies` — one object schema each; every
|
|
24
|
+
* property becomes a parameter. A `@zudojs/schema` schema is converted, any
|
|
25
|
+
* other object is used as an OpenAPI schema.
|
|
26
|
+
* - `body` — a schema, or `{ schema, contentType?, required?, description? }`.
|
|
27
|
+
* - `responses` — keyed by status, `NXX` range or `default`; each entry is a
|
|
28
|
+
* Response Object or `{ schema, description?, contentType?, headers? }`.
|
|
29
|
+
* - `parameters` — explicit parameters, highest precedence.
|
|
30
|
+
* - `inferredParameters` — parameters the source derived itself, lowest
|
|
31
|
+
* precedence.
|
|
32
|
+
* - `hidden: true` — leave the operation out of the document.
|
|
33
|
+
*
|
|
34
|
+
* Every path template slot is documented even when nothing declares it.
|
|
35
|
+
*/
|
|
36
|
+
export interface OpenAPIRouteDescriptor extends RouteOpenAPIMetadata {
|
|
37
|
+
/** HTTP method, any case. */
|
|
38
|
+
readonly method: string;
|
|
39
|
+
/** Route path, `/users/:id` or `/users/{id}`. */
|
|
40
|
+
readonly path: string;
|
|
41
|
+
}
|
|
42
|
+
/** Options for {@link createOpenAPIDocumentFromRoutes}. */
|
|
43
|
+
export interface OpenAPIDocumentFromRoutesOptions extends Omit<OpenAPIManagerOptions, "info" | "cacheTtlMs" | "now"> {
|
|
44
|
+
/** Document metadata (title and version are required by the spec). */
|
|
45
|
+
readonly info: OpenAPIInfo;
|
|
46
|
+
/** Validate the result and throw `OpenAPIValidationError` if invalid. */
|
|
47
|
+
readonly validate?: boolean;
|
|
48
|
+
/** Registered under `components.securitySchemes`. */
|
|
49
|
+
readonly securitySchemes?: Readonly<Record<string, OpenAPISecurityScheme>>;
|
|
50
|
+
/**
|
|
51
|
+
* Component schemas, registered under `components.schemas`. A
|
|
52
|
+
* `@zudojs/schema` schema is converted; any other object is used as-is.
|
|
53
|
+
*/
|
|
54
|
+
readonly schemas?: Readonly<Record<string, unknown>>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=openApiFromRoutes.type.d.ts.map
|
|
@@ -17,8 +17,20 @@ export interface OpenAPIManagerOptions {
|
|
|
17
17
|
* invalidates the cache regardless.
|
|
18
18
|
*/
|
|
19
19
|
readonly cacheTtlMs?: number;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Reports what a schema conversion could not express: component schemas
|
|
22
|
+
* under their component name, route-declared schemas and parameters under
|
|
23
|
+
* `"routes"` once per generation.
|
|
24
|
+
*/
|
|
21
25
|
readonly onSchemaWarning?: (name: string, warnings: readonly string[]) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Receives each route warning of a generation as it is produced, each
|
|
28
|
+
* prefixed with the route (`DELETE /users/:id: ...`): route-declared
|
|
29
|
+
* schemas or parameters that could not be expressed, and operations that
|
|
30
|
+
* document no responses. The same messages are returned by
|
|
31
|
+
* {@link OpenAPIManager.routeWarnings}.
|
|
32
|
+
*/
|
|
33
|
+
readonly onRouteWarning?: (message: string) => void;
|
|
22
34
|
/** Supplies the clock, for tests. */
|
|
23
35
|
readonly now?: () => number;
|
|
24
36
|
/**
|
|
@@ -59,6 +71,9 @@ export declare class OpenAPIManager {
|
|
|
59
71
|
private readonly logo;
|
|
60
72
|
/** True when `branding` was a caller-supplied logo rather than the default. */
|
|
61
73
|
private readonly customLogo;
|
|
74
|
+
private readonly onSchemaWarning;
|
|
75
|
+
private readonly onRouteWarning;
|
|
76
|
+
private lastRouteWarnings;
|
|
62
77
|
private cachedDocument?;
|
|
63
78
|
private cachedAt;
|
|
64
79
|
/** Whether the cached document was produced by a validating generate. */
|
|
@@ -76,6 +91,12 @@ export declare class OpenAPIManager {
|
|
|
76
91
|
addRoute(route: RouteInfo): this;
|
|
77
92
|
/** Registers a route, replacing any existing one for the same method+path. */
|
|
78
93
|
setRoute(route: RouteInfo): this;
|
|
94
|
+
/**
|
|
95
|
+
* Replaces every registered route with `routes`, for keeping the document
|
|
96
|
+
* in step with a live route table. Duplicates are rejected before anything
|
|
97
|
+
* is replaced, so a failed call leaves the previous routes in place.
|
|
98
|
+
*/
|
|
99
|
+
setRoutes(routes: readonly RouteInfo[]): this;
|
|
79
100
|
/** Removes a route. Returns whether one was removed. */
|
|
80
101
|
removeRoute(method: OpenAPIHttpMethod, path: string): boolean;
|
|
81
102
|
/**
|
|
@@ -89,6 +110,12 @@ export declare class OpenAPIManager {
|
|
|
89
110
|
addRawSchema(name: string, schema: OpenAPISchema): this;
|
|
90
111
|
/** Conversion warnings, keyed by component name. */
|
|
91
112
|
schemaWarnings(): ReadonlyMap<string, readonly string[]>;
|
|
113
|
+
/**
|
|
114
|
+
* The route warnings of the last generation, each prefixed with the route
|
|
115
|
+
* (`GET /users/:id: ...`): what could not be expressed for route-declared
|
|
116
|
+
* schemas and parameters, and operations that document no responses.
|
|
117
|
+
*/
|
|
118
|
+
routeWarnings(): readonly string[];
|
|
92
119
|
/**
|
|
93
120
|
* Builds the document from the registered routes and components.
|
|
94
121
|
*
|
|
@@ -24,6 +24,9 @@ export class OpenAPIManager {
|
|
|
24
24
|
logo;
|
|
25
25
|
/** True when `branding` was a caller-supplied logo rather than the default. */
|
|
26
26
|
customLogo;
|
|
27
|
+
onSchemaWarning;
|
|
28
|
+
onRouteWarning;
|
|
29
|
+
lastRouteWarnings = [];
|
|
27
30
|
cachedDocument;
|
|
28
31
|
cachedAt = 0;
|
|
29
32
|
/** Whether the cached document was produced by a validating generate. */
|
|
@@ -39,6 +42,8 @@ export class OpenAPIManager {
|
|
|
39
42
|
version: options.version ?? DEFAULT_OPENAPI_VERSION,
|
|
40
43
|
onWarning: options.onSchemaWarning,
|
|
41
44
|
});
|
|
45
|
+
this.onSchemaWarning = options.onSchemaWarning;
|
|
46
|
+
this.onRouteWarning = options.onRouteWarning;
|
|
42
47
|
this.cacheTtlMs = options.cacheTtlMs ?? DOCUMENT_CACHE_TTL_MS;
|
|
43
48
|
this.now = options.now ?? (() => Date.now());
|
|
44
49
|
this.logo =
|
|
@@ -95,6 +100,20 @@ export class OpenAPIManager {
|
|
|
95
100
|
this.scanner.setRoute(route);
|
|
96
101
|
return this.invalidateCache();
|
|
97
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Replaces every registered route with `routes`, for keeping the document
|
|
105
|
+
* in step with a live route table. Duplicates are rejected before anything
|
|
106
|
+
* is replaced, so a failed call leaves the previous routes in place.
|
|
107
|
+
*/
|
|
108
|
+
setRoutes(routes) {
|
|
109
|
+
const next = new OpenAPIRouteScannerImpl();
|
|
110
|
+
for (const route of routes)
|
|
111
|
+
next.addRoute(route);
|
|
112
|
+
this.scanner.clear();
|
|
113
|
+
for (const route of routes)
|
|
114
|
+
this.scanner.addRoute(route);
|
|
115
|
+
return this.invalidateCache();
|
|
116
|
+
}
|
|
98
117
|
/** Removes a route. Returns whether one was removed. */
|
|
99
118
|
removeRoute(method, path) {
|
|
100
119
|
const removed = this.scanner.removeRoute(method, path);
|
|
@@ -122,6 +141,14 @@ export class OpenAPIManager {
|
|
|
122
141
|
schemaWarnings() {
|
|
123
142
|
return this.schemas.warnings();
|
|
124
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* The route warnings of the last generation, each prefixed with the route
|
|
146
|
+
* (`GET /users/:id: ...`): what could not be expressed for route-declared
|
|
147
|
+
* schemas and parameters, and operations that document no responses.
|
|
148
|
+
*/
|
|
149
|
+
routeWarnings() {
|
|
150
|
+
return this.lastRouteWarnings;
|
|
151
|
+
}
|
|
125
152
|
/* ── Generation ──────────────────────────────────────────────────────── */
|
|
126
153
|
/**
|
|
127
154
|
* Builds the document from the registered routes and components.
|
|
@@ -134,9 +161,21 @@ export class OpenAPIManager {
|
|
|
134
161
|
// hidden, so `removeRoute()` had no effect once a document had been
|
|
135
162
|
// generated.
|
|
136
163
|
this.registry.clearRoutes();
|
|
137
|
-
|
|
164
|
+
const routeWarnings = [];
|
|
165
|
+
const scanned = this.scanner.scan({
|
|
166
|
+
version: this.registry.version,
|
|
167
|
+
onWarning: (message) => {
|
|
168
|
+
routeWarnings.push(message);
|
|
169
|
+
this.onRouteWarning?.(message);
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
for (const route of scanned) {
|
|
138
173
|
this.registry.setRoute(route);
|
|
139
174
|
}
|
|
175
|
+
this.lastRouteWarnings = Object.freeze(routeWarnings);
|
|
176
|
+
if (routeWarnings.length > 0) {
|
|
177
|
+
this.onSchemaWarning?.("routes", this.lastRouteWarnings);
|
|
178
|
+
}
|
|
140
179
|
// Brand the document unless the caller supplied a logo or opted out.
|
|
141
180
|
const info = this.registry.getInfo();
|
|
142
181
|
if (this.logo && !info["x-logo"]) {
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Route metadata, conversion, and scanning for OpenAPI generation.
|
|
5
5
|
*/
|
|
6
|
-
export type { RouteMetadata, RouteOpenAPIMetadata, RouteParameterMetadata, RouteInfo, OpenAPIHttpMethod, } from "./routeMetadata.type.js";
|
|
6
|
+
export type { RouteMetadata, RouteOpenAPIMetadata, RouteParameterMetadata, RouteInfo, OpenAPIHttpMethod, OpenAPISchemaInput, OpenAPIRouteBody, OpenAPIRouteResponse, RouteConversionOptions, } from "./routeMetadata.type.js";
|
|
7
7
|
export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, } from "./routeConverter.core.js";
|
|
8
8
|
export { OpenAPIRouteScannerImpl } from "./routeScanner.core.js";
|
|
9
|
+
export { buildOperationParameters } from "./routeSchema.core.js";
|
|
10
|
+
export { buildOperationRequestBody, buildOperationResponses, describeResponseKey, } from "./routeContent.core.js";
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, } from "./routeConverter.core.js";
|
|
7
7
|
export { OpenAPIRouteScannerImpl } from "./routeScanner.core.js";
|
|
8
|
+
export { buildOperationParameters } from "./routeSchema.core.js";
|
|
9
|
+
export { buildOperationRequestBody, buildOperationResponses, describeResponseKey, } from "./routeContent.core.js";
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request body and response construction for route conversion.
|
|
3
|
+
*
|
|
4
|
+
* Both accept schemas as well as raw OpenAPI objects, so a route that
|
|
5
|
+
* declares `body: userSchema` documents exactly the payload it parses.
|
|
6
|
+
*/
|
|
7
|
+
import type { OpenAPIRequestBody, OpenAPIResponses } from "../openApiTypes/openApiTypes.core.js";
|
|
8
|
+
import type { RouteConversionOptions, RouteOpenAPIMetadata } from "./routeMetadata.type.js";
|
|
9
|
+
/** A default description for a response key: `404` → "Not Found". */
|
|
10
|
+
export declare function describeResponseKey(key: string): string;
|
|
11
|
+
/** Builds the Request Body Object from `requestBody` or `body`. */
|
|
12
|
+
export declare function buildOperationRequestBody(meta: RouteOpenAPIMetadata | undefined, context: string, options?: RouteConversionOptions): OpenAPIRequestBody | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Builds the `responses` object for an operation.
|
|
15
|
+
*
|
|
16
|
+
* Every documented response is carried through; an entry with a `schema` is
|
|
17
|
+
* expanded into a Response Object. A route that documents none gets a
|
|
18
|
+
* `default` "Undocumented response" (`responses` is required) and a warning
|
|
19
|
+
* through `options.onWarning`. No status is invented.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildOperationResponses(meta: RouteOpenAPIMetadata | undefined, context: string, options?: RouteConversionOptions): OpenAPIResponses;
|
|
22
|
+
//# sourceMappingURL=routeContent.core.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request body and response construction for route conversion.
|
|
3
|
+
*
|
|
4
|
+
* Both accept schemas as well as raw OpenAPI objects, so a route that
|
|
5
|
+
* declares `body: userSchema` documents exactly the payload it parses.
|
|
6
|
+
*/
|
|
7
|
+
import { HttpStatus } from "@zudojs/constants";
|
|
8
|
+
import { isSchemaDefinition, resolveSchemaInput, } from "../openApiSchema/schemaInput.core.js";
|
|
9
|
+
import { DEFAULT_MEDIA_TYPE, UNDOCUMENTED_RESPONSE_DESCRIPTION } from "../openApiConstants/openApiConstants.core.js";
|
|
10
|
+
const REASON_PHRASES = new Map(Object.entries(HttpStatus).map(([name, code]) => [
|
|
11
|
+
String(code),
|
|
12
|
+
name
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.split("_")
|
|
15
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
16
|
+
.join(" "),
|
|
17
|
+
]));
|
|
18
|
+
const RANGE_DESCRIPTIONS = {
|
|
19
|
+
"1XX": "Informational",
|
|
20
|
+
"2XX": "Success",
|
|
21
|
+
"3XX": "Redirection",
|
|
22
|
+
"4XX": "Client error",
|
|
23
|
+
"5XX": "Server error",
|
|
24
|
+
default: "Unexpected response",
|
|
25
|
+
};
|
|
26
|
+
/** A default description for a response key: `404` → "Not Found". */
|
|
27
|
+
export function describeResponseKey(key) {
|
|
28
|
+
return REASON_PHRASES.get(key) ?? RANGE_DESCRIPTIONS[key] ?? `Response ${key}`;
|
|
29
|
+
}
|
|
30
|
+
function isDescriptorWithSchema(value) {
|
|
31
|
+
return (typeof value === "object" &&
|
|
32
|
+
value !== null &&
|
|
33
|
+
!isSchemaDefinition(value) &&
|
|
34
|
+
Object.hasOwn(value, "schema"));
|
|
35
|
+
}
|
|
36
|
+
function mediaTypes(contentType, media) {
|
|
37
|
+
const types = contentType === undefined
|
|
38
|
+
? [DEFAULT_MEDIA_TYPE]
|
|
39
|
+
: typeof contentType === "string" ? [contentType] : contentType;
|
|
40
|
+
return Object.fromEntries(types.map((type) => [type, media]));
|
|
41
|
+
}
|
|
42
|
+
/** Builds the Request Body Object from `requestBody` or `body`. */
|
|
43
|
+
export function buildOperationRequestBody(meta, context, options = {}) {
|
|
44
|
+
if (meta?.requestBody)
|
|
45
|
+
return meta.requestBody;
|
|
46
|
+
if (meta?.body === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const body = isDescriptorWithSchema(meta.body)
|
|
49
|
+
? meta.body
|
|
50
|
+
: { schema: meta.body };
|
|
51
|
+
const schema = resolveSchemaInput(body.schema, `${context} body`, options);
|
|
52
|
+
return {
|
|
53
|
+
...(body.description ? { description: body.description } : {}),
|
|
54
|
+
required: body.required ?? true,
|
|
55
|
+
content: mediaTypes(body.contentType, {
|
|
56
|
+
schema,
|
|
57
|
+
...(body.example !== undefined ? { example: body.example } : {}),
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function toResponse(key, entry, context, options) {
|
|
62
|
+
if (!isDescriptorWithSchema(entry))
|
|
63
|
+
return entry;
|
|
64
|
+
const response = entry;
|
|
65
|
+
const schema = resolveSchemaInput(response.schema, `${context} response ${key}`, options);
|
|
66
|
+
return {
|
|
67
|
+
description: response.description ?? describeResponseKey(key),
|
|
68
|
+
...(response.headers ? { headers: response.headers } : {}),
|
|
69
|
+
content: mediaTypes(response.contentType, {
|
|
70
|
+
schema,
|
|
71
|
+
...(response.example !== undefined ? { example: response.example } : {}),
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Builds the `responses` object for an operation.
|
|
77
|
+
*
|
|
78
|
+
* Every documented response is carried through; an entry with a `schema` is
|
|
79
|
+
* expanded into a Response Object. A route that documents none gets a
|
|
80
|
+
* `default` "Undocumented response" (`responses` is required) and a warning
|
|
81
|
+
* through `options.onWarning`. No status is invented.
|
|
82
|
+
*/
|
|
83
|
+
export function buildOperationResponses(meta, context, options = {}) {
|
|
84
|
+
const declared = meta?.responses;
|
|
85
|
+
if (!declared || Object.keys(declared).length === 0) {
|
|
86
|
+
options.onWarning?.(`${context}: no responses are documented; emitted "default: ${UNDOCUMENTED_RESPONSE_DESCRIPTION}".`);
|
|
87
|
+
const fallback = { description: UNDOCUMENTED_RESPONSE_DESCRIPTION };
|
|
88
|
+
return Object.freeze({ default: Object.freeze(fallback) });
|
|
89
|
+
}
|
|
90
|
+
const responses = {};
|
|
91
|
+
for (const [key, entry] of Object.entries(declared)) {
|
|
92
|
+
Object.defineProperty(responses, key, {
|
|
93
|
+
value: toResponse(key, entry, context, options),
|
|
94
|
+
enumerable: true,
|
|
95
|
+
writable: true,
|
|
96
|
+
configurable: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze(responses);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=routeContent.core.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OpenAPIOperation, OpenAPIResponses } from "../openApiTypes/openApiTypes.core.js";
|
|
2
|
-
import type { OpenAPIHttpMethod, RouteMetadata } from "./routeMetadata.type.js";
|
|
2
|
+
import type { OpenAPIHttpMethod, RouteConversionOptions, RouteMetadata } from "./routeMetadata.type.js";
|
|
3
3
|
/**
|
|
4
4
|
* Maps Zudojs HTTP methods to OpenAPI methods.
|
|
5
5
|
*/
|
|
@@ -26,14 +26,20 @@ export declare function extractPathParameters(path: string): readonly string[];
|
|
|
26
26
|
/**
|
|
27
27
|
* Builds the `responses` object for an operation.
|
|
28
28
|
*
|
|
29
|
-
* Every documented response is carried through.
|
|
30
|
-
*
|
|
29
|
+
* Every documented response is carried through. A route that documents none
|
|
30
|
+
* gets a `default` "Undocumented response" and a warning through
|
|
31
|
+
* `options.onWarning`; no `200` is invented.
|
|
31
32
|
*/
|
|
32
|
-
export declare function buildResponses(metadata?: RouteMetadata): OpenAPIResponses;
|
|
33
|
+
export declare function buildResponses(metadata?: RouteMetadata, options?: RouteConversionOptions): OpenAPIResponses;
|
|
33
34
|
/**
|
|
34
35
|
* Converts a route with metadata into an OpenAPI operation.
|
|
36
|
+
*
|
|
37
|
+
* Every path template slot is documented as a required path parameter even
|
|
38
|
+
* when the route declares none; schemas declared as `params`, `query`,
|
|
39
|
+
* `headers`, `cookies`, `body` or response `schema` are converted for
|
|
40
|
+
* `options.version`.
|
|
35
41
|
*/
|
|
36
|
-
export declare function convertRouteToOpenAPI(method: string, path: string, metadata?: RouteMetadata): {
|
|
42
|
+
export declare function convertRouteToOpenAPI(method: string, path: string, metadata?: RouteMetadata, options?: RouteConversionOptions): {
|
|
37
43
|
method: OpenAPIHttpMethod;
|
|
38
44
|
path: string;
|
|
39
45
|
operation: OpenAPIOperation;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildOperationParameters } from "./routeSchema.core.js";
|
|
2
|
+
import { buildOperationRequestBody, buildOperationResponses, } from "./routeContent.core.js";
|
|
1
3
|
import { OpenAPIRouteError } from "../openApiErrors/openApiError.types.js";
|
|
2
4
|
import { PATH_TEMPLATE_PARAMETER } from "../openApiConstants/openApiConstants.core.js";
|
|
3
5
|
/**
|
|
@@ -71,56 +73,49 @@ export function extractPathParameters(path) {
|
|
|
71
73
|
}
|
|
72
74
|
return names;
|
|
73
75
|
}
|
|
74
|
-
function toParameter(parameter) {
|
|
75
|
-
return {
|
|
76
|
-
name: parameter.name,
|
|
77
|
-
in: parameter.in,
|
|
78
|
-
...(parameter.description ? { description: parameter.description } : {}),
|
|
79
|
-
// A path parameter is required by the specification, so declaring one
|
|
80
|
-
// that is not required is a document that cannot validate.
|
|
81
|
-
required: parameter.in === "path" ? true : (parameter.required ?? false),
|
|
82
|
-
...(parameter.deprecated ? { deprecated: true } : {}),
|
|
83
|
-
...(parameter.schema !== undefined ? { schema: parameter.schema } : {}),
|
|
84
|
-
...(parameter.example !== undefined ? { example: parameter.example } : {}),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
76
|
/**
|
|
88
77
|
* Builds the `responses` object for an operation.
|
|
89
78
|
*
|
|
90
|
-
* Every documented response is carried through.
|
|
91
|
-
*
|
|
79
|
+
* Every documented response is carried through. A route that documents none
|
|
80
|
+
* gets a `default` "Undocumented response" and a warning through
|
|
81
|
+
* `options.onWarning`; no `200` is invented.
|
|
92
82
|
*/
|
|
93
|
-
export function buildResponses(metadata) {
|
|
94
|
-
|
|
95
|
-
if (declared && Object.keys(declared).length > 0) {
|
|
96
|
-
return Object.freeze({ ...declared });
|
|
97
|
-
}
|
|
98
|
-
return Object.freeze({ "200": { description: "OK" } });
|
|
83
|
+
export function buildResponses(metadata, options) {
|
|
84
|
+
return buildOperationResponses(metadata?.openapi, "responses", options);
|
|
99
85
|
}
|
|
100
86
|
/**
|
|
101
87
|
* Converts a route with metadata into an OpenAPI operation.
|
|
88
|
+
*
|
|
89
|
+
* Every path template slot is documented as a required path parameter even
|
|
90
|
+
* when the route declares none; schemas declared as `params`, `query`,
|
|
91
|
+
* `headers`, `cookies`, `body` or response `schema` are converted for
|
|
92
|
+
* `options.version`.
|
|
102
93
|
*/
|
|
103
|
-
export function convertRouteToOpenAPI(method, path, metadata) {
|
|
94
|
+
export function convertRouteToOpenAPI(method, path, metadata, options = {}) {
|
|
104
95
|
if (!isOpenAPIMethod(method)) {
|
|
105
96
|
throw new OpenAPIRouteError(`HTTP method "${method}" has no OpenAPI path item field. ` +
|
|
106
97
|
`Supported: ${ZUDOLIB_TO_OPENAPI_METHODS.join(", ")}.`, { metadata: { method, path } });
|
|
107
98
|
}
|
|
108
99
|
const openApiPath = toOpenAPIPath(path);
|
|
109
100
|
const meta = metadata?.openapi;
|
|
101
|
+
const context = `${method.toUpperCase()} ${path}`;
|
|
102
|
+
const parameters = buildOperationParameters(extractPathParameters(openApiPath), meta, context, options);
|
|
103
|
+
const requestBody = buildOperationRequestBody(meta, context, options);
|
|
110
104
|
const operation = {
|
|
111
105
|
...(meta?.operationId ? { operationId: meta.operationId } : {}),
|
|
112
106
|
...(meta?.summary ? { summary: meta.summary } : {}),
|
|
113
107
|
...(meta?.description ? { description: meta.description } : {}),
|
|
114
108
|
...(meta?.tags?.length ? { tags: [...meta.tags] } : {}),
|
|
115
109
|
...(meta?.deprecated !== undefined ? { deprecated: meta.deprecated } : {}),
|
|
116
|
-
...(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
...(parameters.length > 0 ? { parameters } : {}),
|
|
111
|
+
...(requestBody ? { requestBody } : {}),
|
|
112
|
+
// An empty list is meaningful: it marks the operation public, overriding
|
|
113
|
+
// document-level security. Dropping it documented a public route as
|
|
114
|
+
// requiring every global scheme.
|
|
115
|
+
...(meta?.security !== undefined ? { security: [...meta.security] } : {}),
|
|
121
116
|
...(meta?.servers?.length ? { servers: [...meta.servers] } : {}),
|
|
122
117
|
...(meta?.externalDocs ? { externalDocs: meta.externalDocs } : {}),
|
|
123
|
-
responses:
|
|
118
|
+
responses: buildOperationResponses(meta, context, options),
|
|
124
119
|
};
|
|
125
120
|
return {
|
|
126
121
|
method: method.toLowerCase(),
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* use.
|
|
9
9
|
*/
|
|
10
10
|
import type { OpenAPIExternalDocumentation, OpenAPIRequestBody, OpenAPIResponse, OpenAPISecurityRequirement, OpenAPIServer } from "../openApiTypes/openApiTypes.core.js";
|
|
11
|
+
import type { OpenAPIRouteBody, OpenAPIRouteResponse, OpenAPISchemaInput } from "../openApiSchema/schemaInput.type.js";
|
|
12
|
+
export type { OpenAPIRouteBody, OpenAPIRouteResponse, OpenAPISchemaInput, RouteConversionOptions, } from "../openApiSchema/schemaInput.type.js";
|
|
11
13
|
/** HTTP methods an OpenAPI path item can carry. */
|
|
12
14
|
export type OpenAPIHttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
|
|
13
15
|
/**
|
|
@@ -19,6 +21,7 @@ export interface RouteParameterMetadata {
|
|
|
19
21
|
readonly description?: string;
|
|
20
22
|
readonly required?: boolean;
|
|
21
23
|
readonly deprecated?: boolean;
|
|
24
|
+
/** An {@link OpenAPISchemaInput}; a `@zudojs/schema` schema is converted. */
|
|
22
25
|
readonly schema?: unknown;
|
|
23
26
|
readonly example?: unknown;
|
|
24
27
|
}
|
|
@@ -31,13 +34,44 @@ export interface RouteOpenAPIMetadata {
|
|
|
31
34
|
readonly description?: string;
|
|
32
35
|
readonly tags?: readonly string[];
|
|
33
36
|
readonly deprecated?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Explicit parameters. They take precedence over parameters derived from
|
|
39
|
+
* `params` / `query` / `headers` / `cookies` with the same name and
|
|
40
|
+
* location, which in turn take precedence over `inferredParameters`.
|
|
41
|
+
*/
|
|
34
42
|
readonly parameters?: readonly RouteParameterMetadata[];
|
|
43
|
+
/**
|
|
44
|
+
* Path parameters as one object schema; each property becomes an
|
|
45
|
+
* `in: "path"` parameter. Template slots it does not cover are still
|
|
46
|
+
* documented, as required strings.
|
|
47
|
+
*/
|
|
48
|
+
readonly params?: OpenAPISchemaInput;
|
|
49
|
+
/** Query parameters as one object schema; one parameter per property. */
|
|
50
|
+
readonly query?: OpenAPISchemaInput;
|
|
51
|
+
/** Request headers as one object schema; one parameter per property. */
|
|
52
|
+
readonly headers?: OpenAPISchemaInput;
|
|
53
|
+
/** Cookies as one object schema; one parameter per property. */
|
|
54
|
+
readonly cookies?: OpenAPISchemaInput;
|
|
55
|
+
/**
|
|
56
|
+
* Parameters the route source inferred on its own (for example a regular
|
|
57
|
+
* expression constraint on a path segment). Lowest precedence: any
|
|
58
|
+
* declared parameter with the same name and location replaces one.
|
|
59
|
+
*/
|
|
60
|
+
readonly inferredParameters?: readonly RouteParameterMetadata[];
|
|
61
|
+
/**
|
|
62
|
+
* The request body as a schema (`application/json`) or an
|
|
63
|
+
* {@link OpenAPIRouteBody}. Ignored when `requestBody` is set.
|
|
64
|
+
*/
|
|
65
|
+
readonly body?: OpenAPISchemaInput | OpenAPIRouteBody;
|
|
66
|
+
/** A raw Request Body Object; takes precedence over `body`. */
|
|
35
67
|
readonly requestBody?: OpenAPIRequestBody;
|
|
36
68
|
/**
|
|
37
69
|
* Responses keyed by status code, `default`, or a `2XX`-style range.
|
|
38
|
-
* Every entry reaches the document — this is not a 200-only field.
|
|
70
|
+
* Every entry reaches the document — this is not a 200-only field. An
|
|
71
|
+
* entry is a Response Object, or an {@link OpenAPIRouteResponse} when it
|
|
72
|
+
* carries a `schema`.
|
|
39
73
|
*/
|
|
40
|
-
readonly responses?: Readonly<Record<string, OpenAPIResponse>>;
|
|
74
|
+
readonly responses?: Readonly<Record<string, OpenAPIResponse | OpenAPIRouteResponse>>;
|
|
41
75
|
readonly security?: readonly OpenAPISecurityRequirement[];
|
|
42
76
|
readonly servers?: readonly OpenAPIServer[];
|
|
43
77
|
readonly externalDocs?: OpenAPIExternalDocumentation;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OpenAPIRoute } from "../openApiRegistry/openApiRegistry.type.js";
|
|
2
|
-
import type { RouteInfo } from "./routeMetadata.type.js";
|
|
2
|
+
import type { RouteConversionOptions, RouteInfo } from "./routeMetadata.type.js";
|
|
3
3
|
/**
|
|
4
4
|
* Collects routes and converts them into OpenAPI operations.
|
|
5
5
|
*
|
|
@@ -20,8 +20,12 @@ export declare class OpenAPIRouteScannerImpl {
|
|
|
20
20
|
removeRoute(method: string, path: string): boolean;
|
|
21
21
|
/** Number of registered routes. */
|
|
22
22
|
get size(): number;
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Converts every registered route into an OpenAPI operation. `options`
|
|
25
|
+
* sets the version declared schemas are converted for and receives their
|
|
26
|
+
* conversion warnings.
|
|
27
|
+
*/
|
|
28
|
+
scan(options?: RouteConversionOptions): readonly OpenAPIRoute[];
|
|
25
29
|
clear(): void;
|
|
26
30
|
}
|
|
27
31
|
//# sourceMappingURL=routeScanner.core.d.ts.map
|
|
@@ -66,13 +66,17 @@ export class OpenAPIRouteScannerImpl {
|
|
|
66
66
|
get size() {
|
|
67
67
|
return this.routes.size;
|
|
68
68
|
}
|
|
69
|
-
/**
|
|
70
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Converts every registered route into an OpenAPI operation. `options`
|
|
71
|
+
* sets the version declared schemas are converted for and receives their
|
|
72
|
+
* conversion warnings.
|
|
73
|
+
*/
|
|
74
|
+
scan(options) {
|
|
71
75
|
const result = [];
|
|
72
76
|
for (const route of this.routes.values()) {
|
|
73
77
|
if (route.metadata?.openapi?.hidden === true)
|
|
74
78
|
continue;
|
|
75
|
-
const converted = convertRouteToOpenAPI(route.method, route.path, route.metadata);
|
|
79
|
+
const converted = convertRouteToOpenAPI(route.method, route.path, route.metadata, options);
|
|
76
80
|
result.push({
|
|
77
81
|
method: converted.method,
|
|
78
82
|
path: converted.path,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-aware parameter derivation for route conversion: path parameters,
|
|
3
|
+
* query, headers and cookies declared as one object schema each become one
|
|
4
|
+
* parameter per property.
|
|
5
|
+
*/
|
|
6
|
+
import type { OpenAPIParameter } from "../openApiTypes/openApiTypes.core.js";
|
|
7
|
+
import type { RouteConversionOptions, RouteOpenAPIMetadata } from "./routeMetadata.type.js";
|
|
8
|
+
/**
|
|
9
|
+
* Builds an operation's parameter list.
|
|
10
|
+
*
|
|
11
|
+
* `slots` are the path template's parameter names. Layers, lowest precedence
|
|
12
|
+
* first: a required string per slot, `inferredParameters`, the `params` /
|
|
13
|
+
* `query` / `headers` / `cookies` schemas, then explicit `parameters`; a
|
|
14
|
+
* later layer replaces a parameter with the same name and location. A path
|
|
15
|
+
* parameter no slot names is dropped with a warning: emitting it would make
|
|
16
|
+
* a document that cannot validate.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildOperationParameters(slots: readonly string[], meta: RouteOpenAPIMetadata | undefined, context: string, options?: RouteConversionOptions): readonly OpenAPIParameter[];
|
|
19
|
+
//# sourceMappingURL=routeSchema.core.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-aware parameter derivation for route conversion: path parameters,
|
|
3
|
+
* query, headers and cookies declared as one object schema each become one
|
|
4
|
+
* parameter per property.
|
|
5
|
+
*/
|
|
6
|
+
import { resolveSchemaInput } from "../openApiSchema/schemaInput.core.js";
|
|
7
|
+
function isObjectSchema(schema) {
|
|
8
|
+
const type = schema.type;
|
|
9
|
+
if (type === "object")
|
|
10
|
+
return true;
|
|
11
|
+
if (Array.isArray(type))
|
|
12
|
+
return type.includes("object");
|
|
13
|
+
return type === undefined && schema.properties !== undefined;
|
|
14
|
+
}
|
|
15
|
+
/** One parameter per property of an object schema. */
|
|
16
|
+
function parametersFromSchema(input, location, context, options) {
|
|
17
|
+
const schema = resolveSchemaInput(input, context, options);
|
|
18
|
+
if (!isObjectSchema(schema) || schema.properties === undefined) {
|
|
19
|
+
options.onWarning?.(`${context}: ${location} parameters must be declared as an object schema; ignored.`);
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const required = new Set(schema.required ?? []);
|
|
23
|
+
return Object.entries(schema.properties).map(([name, property]) => ({
|
|
24
|
+
name,
|
|
25
|
+
in: location,
|
|
26
|
+
required: location === "path" ? true : required.has(name),
|
|
27
|
+
...(property.description ? { description: property.description } : {}),
|
|
28
|
+
...(property.deprecated ? { deprecated: true } : {}),
|
|
29
|
+
schema: property,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
function toParameter(parameter, context, options) {
|
|
33
|
+
return {
|
|
34
|
+
name: parameter.name,
|
|
35
|
+
in: parameter.in,
|
|
36
|
+
...(parameter.description ? { description: parameter.description } : {}),
|
|
37
|
+
// A path parameter is required by the specification, so declaring one
|
|
38
|
+
// that is not required is a document that cannot validate.
|
|
39
|
+
required: parameter.in === "path" ? true : (parameter.required ?? false),
|
|
40
|
+
...(parameter.deprecated ? { deprecated: true } : {}),
|
|
41
|
+
...(parameter.schema === undefined ? {} : {
|
|
42
|
+
schema: resolveSchemaInput(parameter.schema, `${context} "${parameter.name}"`, options),
|
|
43
|
+
}),
|
|
44
|
+
...(parameter.example !== undefined ? { example: parameter.example } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const SCHEMA_LOCATIONS = [
|
|
48
|
+
["params", "path"],
|
|
49
|
+
["query", "query"],
|
|
50
|
+
["headers", "header"],
|
|
51
|
+
["cookies", "cookie"],
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Builds an operation's parameter list.
|
|
55
|
+
*
|
|
56
|
+
* `slots` are the path template's parameter names. Layers, lowest precedence
|
|
57
|
+
* first: a required string per slot, `inferredParameters`, the `params` /
|
|
58
|
+
* `query` / `headers` / `cookies` schemas, then explicit `parameters`; a
|
|
59
|
+
* later layer replaces a parameter with the same name and location. A path
|
|
60
|
+
* parameter no slot names is dropped with a warning: emitting it would make
|
|
61
|
+
* a document that cannot validate.
|
|
62
|
+
*/
|
|
63
|
+
export function buildOperationParameters(slots, meta, context, options = {}) {
|
|
64
|
+
const merged = new Map();
|
|
65
|
+
const put = (parameter) => void merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
66
|
+
for (const name of slots) {
|
|
67
|
+
put({ name, in: "path", required: true, schema: { type: "string" } });
|
|
68
|
+
}
|
|
69
|
+
for (const parameter of meta?.inferredParameters ?? []) {
|
|
70
|
+
put(toParameter(parameter, context, options));
|
|
71
|
+
}
|
|
72
|
+
for (const [key, location] of SCHEMA_LOCATIONS) {
|
|
73
|
+
const input = meta?.[key];
|
|
74
|
+
if (input === undefined)
|
|
75
|
+
continue;
|
|
76
|
+
for (const parameter of parametersFromSchema(input, location, context, options)) {
|
|
77
|
+
put(parameter);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const parameter of meta?.parameters ?? []) {
|
|
81
|
+
put(toParameter(parameter, context, options));
|
|
82
|
+
}
|
|
83
|
+
const result = [];
|
|
84
|
+
for (const parameter of merged.values()) {
|
|
85
|
+
if (parameter.in === "path" && !slots.includes(parameter.name)) {
|
|
86
|
+
options.onWarning?.(`${context}: path parameter "${parameter.name}" is not in the path template; ignored.`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
result.push(parameter);
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=routeSchema.core.js.map
|
|
@@ -8,4 +8,5 @@ export { convertSchema, createSchemaConverter, isVersion31, } from "./schemaConv
|
|
|
8
8
|
export type { SchemaRegistry, SchemaRegistryOptions, } from "./schemaRegistry.core.js";
|
|
9
9
|
export { SchemaRegistryImpl } from "./schemaRegistry.core.js";
|
|
10
10
|
export { createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, type ComponentSection, } from "./references.core.js";
|
|
11
|
+
export { isSchemaDefinition, resolveSchemaInput, type SchemaInputOptions, } from "./schemaInput.core.js";
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -6,4 +6,5 @@
|
|
|
6
6
|
export { convertSchema, createSchemaConverter, isVersion31, } from "./schemaConverter.core.js";
|
|
7
7
|
export { SchemaRegistryImpl } from "./schemaRegistry.core.js";
|
|
8
8
|
export { createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, } from "./references.core.js";
|
|
9
|
+
export { isSchemaDefinition, resolveSchemaInput, } from "./schemaInput.core.js";
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution of schemas declared on routes and operations.
|
|
3
|
+
*
|
|
4
|
+
* A declared schema is either a `@zudojs/schema` schema, converted here for
|
|
5
|
+
* the target version, or an OpenAPI Schema / Reference Object used as-is.
|
|
6
|
+
*/
|
|
7
|
+
import type { OpenAPISchema } from "../openApiTypes/openApiTypes.core.js";
|
|
8
|
+
/** Options for {@link resolveSchemaInput}. */
|
|
9
|
+
export interface SchemaInputOptions {
|
|
10
|
+
/** Specification version to convert for. Default: 3.1.0. */
|
|
11
|
+
readonly version?: string;
|
|
12
|
+
/** Receives everything a conversion could not express exactly. */
|
|
13
|
+
readonly onWarning?: (message: string) => void;
|
|
14
|
+
}
|
|
15
|
+
/** True when `value` is a `@zudojs/schema` schema (it carries a string `_type`). */
|
|
16
|
+
export declare function isSchemaDefinition(value: unknown): value is {
|
|
17
|
+
readonly _type: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Resolves a declared schema to an OpenAPI schema.
|
|
21
|
+
*
|
|
22
|
+
* A `@zudojs/schema` schema is converted and each conversion warning is
|
|
23
|
+
* reported prefixed with `context`; any other object is taken to be an
|
|
24
|
+
* OpenAPI Schema or Reference Object already. A non-object is reported and
|
|
25
|
+
* becomes `{}`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveSchemaInput(input: unknown, context: string, options?: SchemaInputOptions): OpenAPISchema;
|
|
28
|
+
//# sourceMappingURL=schemaInput.core.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution of schemas declared on routes and operations.
|
|
3
|
+
*
|
|
4
|
+
* A declared schema is either a `@zudojs/schema` schema, converted here for
|
|
5
|
+
* the target version, or an OpenAPI Schema / Reference Object used as-is.
|
|
6
|
+
*/
|
|
7
|
+
import { convertSchema } from "./schemaConverter.core.js";
|
|
8
|
+
/** True when `value` is a `@zudojs/schema` schema (it carries a string `_type`). */
|
|
9
|
+
export function isSchemaDefinition(value) {
|
|
10
|
+
return (typeof value === "object" &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
typeof value._type === "string");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a declared schema to an OpenAPI schema.
|
|
16
|
+
*
|
|
17
|
+
* A `@zudojs/schema` schema is converted and each conversion warning is
|
|
18
|
+
* reported prefixed with `context`; any other object is taken to be an
|
|
19
|
+
* OpenAPI Schema or Reference Object already. A non-object is reported and
|
|
20
|
+
* becomes `{}`.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveSchemaInput(input, context, options = {}) {
|
|
23
|
+
if (isSchemaDefinition(input)) {
|
|
24
|
+
const result = convertSchema(input, { version: options.version });
|
|
25
|
+
for (const warning of result.warnings) {
|
|
26
|
+
options.onWarning?.(`${context}: ${warning}`);
|
|
27
|
+
}
|
|
28
|
+
return result.schema;
|
|
29
|
+
}
|
|
30
|
+
if (typeof input === "object" && input !== null) {
|
|
31
|
+
return input;
|
|
32
|
+
}
|
|
33
|
+
options.onWarning?.(`${context}: expected a schema, got ${typeof input}; emitted {}.`);
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=schemaInput.core.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-bearing inputs a route or operation may declare.
|
|
3
|
+
*/
|
|
4
|
+
import type { OpenAPIHeader, OpenAPIReference, OpenAPISchema } from "../openApiTypes/openApiTypes.core.js";
|
|
5
|
+
import type { SchemaInputOptions } from "./schemaInput.core.js";
|
|
6
|
+
/**
|
|
7
|
+
* A schema a route may declare: a `@zudojs/schema` schema (anything carrying
|
|
8
|
+
* a string `_type`, converted with `convertSchema` for the document's
|
|
9
|
+
* version), or an OpenAPI Schema / Reference Object used as-is.
|
|
10
|
+
*/
|
|
11
|
+
export type OpenAPISchemaInput = OpenAPISchema | OpenAPIReference | {
|
|
12
|
+
readonly _type: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A request body declared by schema rather than as a raw Request Body Object.
|
|
16
|
+
*/
|
|
17
|
+
export interface OpenAPIRouteBody {
|
|
18
|
+
/** The body's schema. */
|
|
19
|
+
readonly schema: OpenAPISchemaInput;
|
|
20
|
+
/** Media type(s) the body is accepted as. Default: `application/json`. */
|
|
21
|
+
readonly contentType?: string | readonly string[];
|
|
22
|
+
/** Whether the body is required. Default: `true`. */
|
|
23
|
+
readonly required?: boolean;
|
|
24
|
+
readonly description?: string;
|
|
25
|
+
readonly example?: unknown;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A response declared by schema rather than as a raw Response Object.
|
|
29
|
+
*
|
|
30
|
+
* An entry in `responses` is read as this shape when it has a `schema` key;
|
|
31
|
+
* otherwise it is an OpenAPI Response Object and passes through unchanged.
|
|
32
|
+
*/
|
|
33
|
+
export interface OpenAPIRouteResponse {
|
|
34
|
+
/** The response body's schema. */
|
|
35
|
+
readonly schema: OpenAPISchemaInput;
|
|
36
|
+
/** Default: the status code's reason phrase, e.g. "Not Found". */
|
|
37
|
+
readonly description?: string;
|
|
38
|
+
/** Media type(s) the body is sent as. Default: `application/json`. */
|
|
39
|
+
readonly contentType?: string | readonly string[];
|
|
40
|
+
readonly headers?: Readonly<Record<string, OpenAPIHeader>>;
|
|
41
|
+
readonly example?: unknown;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Options for converting a route into an operation: the version declared
|
|
45
|
+
* schemas are converted for, and a sink for what they could not express.
|
|
46
|
+
*/
|
|
47
|
+
export type RouteConversionOptions = SchemaInputOptions;
|
|
48
|
+
//# sourceMappingURL=schemaInput.type.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/openapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "OpenAPI 3.0 and 3.1 specification generation, validation, and serialization for Zudojs applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
"!dist/.tsbuildinfo"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@zudojs/constants": "1.1.
|
|
30
|
-
"@zudojs/errors": "1.
|
|
29
|
+
"@zudojs/constants": "1.1.2",
|
|
30
|
+
"@zudojs/errors": "1.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@types/node": "^26.
|
|
33
|
+
"@types/node": "^26.6.2",
|
|
34
|
+
"@zudojs/schema": "1.2.0",
|
|
34
35
|
"typescript": "7.0.2",
|
|
35
|
-
"vitest": "^
|
|
36
|
+
"vitest": "^5.0.1"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=24.0.0"
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
"swagger",
|
|
47
48
|
"api-docs"
|
|
48
49
|
],
|
|
49
|
-
"homepage": "https://
|
|
50
|
+
"homepage": "https://zudojs.oyinlola.site/docs/packages-openapi",
|
|
50
51
|
"bugs": {
|
|
51
52
|
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
52
53
|
},
|