@fluojs/openapi 1.0.3 → 2.0.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.ko.md +80 -11
- package/README.md +80 -11
- package/dist/decorators.d.ts +7 -5
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +9 -7
- package/dist/openapi-module.d.ts +26 -8
- package/dist/openapi-module.d.ts.map +1 -1
- package/dist/openapi-module.js +39 -44
- package/dist/path-item.d.ts +62 -0
- package/dist/path-item.d.ts.map +1 -0
- package/dist/path-item.js +53 -0
- package/dist/schema-bounds.d.ts +9 -0
- package/dist/schema-bounds.d.ts.map +1 -0
- package/dist/schema-bounds.js +175 -0
- package/dist/schema-builder.d.ts +3 -6
- package/dist/schema-builder.d.ts.map +1 -1
- package/dist/schema-builder.js +27 -14
- package/dist/swagger-ui.d.ts +29 -0
- package/dist/swagger-ui.d.ts.map +1 -0
- package/dist/swagger-ui.js +67 -0
- package/package.json +8 -7
package/dist/openapi-module.js
CHANGED
|
@@ -3,32 +3,40 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
3
3
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
4
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
5
5
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
6
|
-
import { Controller, Get, NotFoundException, createHandlerMapping } from '@fluojs/http';
|
|
7
6
|
import { Inject } from '@fluojs/core';
|
|
7
|
+
import { Controller, createHandlerMapping, Get, NotFoundException } from '@fluojs/http';
|
|
8
8
|
import { defineModule } from '@fluojs/runtime';
|
|
9
9
|
import { OpenApiHandlerRegistry } from './handler-registry.js';
|
|
10
10
|
import { buildOpenApiDocument } from './schema-builder.js';
|
|
11
11
|
import { cloneSnapshotValue, createFrozenSnapshot } from './snapshot.js';
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const SWAGGER_UI_BUNDLE_JS_URL = `${SWAGGER_UI_DIST_BASE_URL}/swagger-ui-bundle.js`;
|
|
12
|
+
import { createSwaggerUiHtml, resolveSwaggerUiAssets } from './swagger-ui.js';
|
|
13
|
+
const DEFAULT_DOCUMENT_PATH = '/openapi.json';
|
|
14
|
+
const DEFAULT_UI_PATH = '/docs';
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
17
|
+
* Routes owned by one `OpenApiModule` registration.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* Paths use the normal `@fluojs/http` route grammar and are normalized before
|
|
21
|
+
* registration. The defaults remain `/openapi.json` and `/docs`.
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
* Public options for `OpenApiModule.forRoot(...)
|
|
25
|
+
* Public document and route options for `OpenApiModule.forRoot(...)`.
|
|
23
26
|
*
|
|
24
27
|
* @remarks
|
|
25
28
|
* Keep README examples for full controller/module workflows. These options are
|
|
26
29
|
* intended to document the runtime hooks that shape the generated document.
|
|
27
30
|
*/
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Async OpenAPI registration options with routes fixed before module compilation.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* `documentPath` and `uiPath` belong to the outer registration because HTTP
|
|
37
|
+
* routes are compiled before the injected options factory resolves.
|
|
38
|
+
*/
|
|
39
|
+
|
|
32
40
|
function cloneRecord(record) {
|
|
33
41
|
if (!record) {
|
|
34
42
|
return undefined;
|
|
@@ -43,6 +51,7 @@ function snapshotOpenApiModuleOptions(options) {
|
|
|
43
51
|
return createFrozenSnapshot({
|
|
44
52
|
defaultErrorResponsesPolicy: options.defaultErrorResponsesPolicy,
|
|
45
53
|
descriptors: options.descriptors ? cloneSnapshotValue(options.descriptors) : undefined,
|
|
54
|
+
documentPath: options.documentPath,
|
|
46
55
|
documentTransform: options.documentTransform,
|
|
47
56
|
extraModels: options.extraModels ? [...options.extraModels] : undefined,
|
|
48
57
|
securitySchemes: cloneRecord(options.securitySchemes),
|
|
@@ -52,38 +61,17 @@ function snapshotOpenApiModuleOptions(options) {
|
|
|
52
61
|
} : undefined,
|
|
53
62
|
title: options.title,
|
|
54
63
|
ui: options.ui,
|
|
64
|
+
uiPath: options.uiPath,
|
|
55
65
|
version: options.version
|
|
56
66
|
});
|
|
57
67
|
}
|
|
58
|
-
function
|
|
68
|
+
function resolveOpenApiRouteOptions(options) {
|
|
69
|
+
const normalizePath = path => `/${path.split('/').filter(Boolean).join('/')}`;
|
|
59
70
|
return {
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
documentPath: normalizePath(options.documentPath ?? DEFAULT_DOCUMENT_PATH),
|
|
72
|
+
uiPath: normalizePath(options.uiPath ?? DEFAULT_UI_PATH)
|
|
62
73
|
};
|
|
63
74
|
}
|
|
64
|
-
function createSwaggerUiHtml(title, assets) {
|
|
65
|
-
return `<!doctype html>
|
|
66
|
-
<html lang="en">
|
|
67
|
-
<head>
|
|
68
|
-
<meta charset="utf-8" />
|
|
69
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
70
|
-
<title>${escapeHtml(title)}</title>
|
|
71
|
-
<link rel="stylesheet" href="${escapeHtml(assets.cssUrl)}" />
|
|
72
|
-
</head>
|
|
73
|
-
<body>
|
|
74
|
-
<div id="swagger-ui"></div>
|
|
75
|
-
<script src="${escapeHtml(assets.jsBundleUrl)}" crossorigin></script>
|
|
76
|
-
<script>
|
|
77
|
-
const specUrl = window.location.pathname.replace(/\/docs\/?$/, '/openapi.json');
|
|
78
|
-
const swaggerUi = SwaggerUIBundle({
|
|
79
|
-
url: specUrl,
|
|
80
|
-
dom_id: '#swagger-ui'
|
|
81
|
-
});
|
|
82
|
-
void swaggerUi;
|
|
83
|
-
</script>
|
|
84
|
-
</body>
|
|
85
|
-
</html>`;
|
|
86
|
-
}
|
|
87
75
|
function isOpenApiModuleOptions(value) {
|
|
88
76
|
if (typeof value !== 'object' || value === null) {
|
|
89
77
|
return false;
|
|
@@ -123,10 +111,11 @@ export class OpenApiModule {
|
|
|
123
111
|
* ```
|
|
124
112
|
*/
|
|
125
113
|
static forRoot(options) {
|
|
114
|
+
const snapshot = snapshotOpenApiModuleOptions(options);
|
|
126
115
|
return this.createModule({
|
|
127
116
|
scope: 'singleton',
|
|
128
|
-
useValue:
|
|
129
|
-
});
|
|
117
|
+
useValue: snapshot
|
|
118
|
+
}, resolveOpenApiRouteOptions(snapshot));
|
|
130
119
|
}
|
|
131
120
|
|
|
132
121
|
/**
|
|
@@ -138,7 +127,9 @@ export class OpenApiModule {
|
|
|
138
127
|
* @example
|
|
139
128
|
* ```ts
|
|
140
129
|
* OpenApiModule.forRootAsync({
|
|
130
|
+
* documentPath: '/openapi/internal.json',
|
|
141
131
|
* inject: [ConfigService],
|
|
132
|
+
* uiPath: '/docs/internal',
|
|
142
133
|
* useFactory: (config) => ({
|
|
143
134
|
* title: config.get('APP_NAME'),
|
|
144
135
|
* version: config.get('APP_VERSION'),
|
|
@@ -147,13 +138,17 @@ export class OpenApiModule {
|
|
|
147
138
|
* ```
|
|
148
139
|
*/
|
|
149
140
|
static forRootAsync(options) {
|
|
141
|
+
const routes = resolveOpenApiRouteOptions(options);
|
|
150
142
|
return this.createModule({
|
|
151
143
|
inject: options.inject,
|
|
152
144
|
scope: 'singleton',
|
|
153
|
-
useFactory: async (...deps) => snapshotOpenApiModuleOptions(
|
|
154
|
-
|
|
145
|
+
useFactory: async (...deps) => snapshotOpenApiModuleOptions({
|
|
146
|
+
...(await options.useFactory(...deps)),
|
|
147
|
+
...routes
|
|
148
|
+
})
|
|
149
|
+
}, routes);
|
|
155
150
|
}
|
|
156
|
-
static createModule(optionsProvider) {
|
|
151
|
+
static createModule(optionsProvider, routes) {
|
|
157
152
|
let _initProto, _initClass;
|
|
158
153
|
const openApiModuleOptionsToken = Symbol('fluo.openapi.module-options');
|
|
159
154
|
const openApiDocumentToken = Symbol('fluo.openapi.document');
|
|
@@ -163,12 +158,12 @@ export class OpenApiModule {
|
|
|
163
158
|
({
|
|
164
159
|
e: [_initProto],
|
|
165
160
|
c: [_OpenApiController, _initClass]
|
|
166
|
-
} = _applyDecs(this, [Controller(''), Inject(openApiDocumentToken, openApiModuleOptionsToken)], [[Get(
|
|
161
|
+
} = _applyDecs(this, [Controller(''), Inject(openApiDocumentToken, openApiModuleOptionsToken)], [[Get(routes.documentPath), 2, "getDocument"], [Get(routes.uiPath), 2, "getSwaggerUi"]]));
|
|
167
162
|
}
|
|
168
163
|
constructor(document, options) {
|
|
164
|
+
_initProto(this);
|
|
169
165
|
this.document = document;
|
|
170
166
|
this.options = options;
|
|
171
|
-
_initProto(this);
|
|
172
167
|
}
|
|
173
168
|
getDocument() {
|
|
174
169
|
return cloneSnapshotValue(this.document);
|
|
@@ -178,7 +173,7 @@ export class OpenApiModule {
|
|
|
178
173
|
throw new NotFoundException('Swagger UI is disabled.');
|
|
179
174
|
}
|
|
180
175
|
context.response.setHeader('content-type', 'text/html; charset=utf-8');
|
|
181
|
-
return createSwaggerUiHtml(this.options.title, resolveSwaggerUiAssets(this.options));
|
|
176
|
+
return createSwaggerUiHtml(this.options.title, resolveSwaggerUiAssets(this.options.swaggerUiAssets), routes);
|
|
182
177
|
}
|
|
183
178
|
static {
|
|
184
179
|
_initClass();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { OpenApiOperationObject, OpenApiParameterObject } from './schema-builder.js';
|
|
2
|
+
/** Standard OpenAPI 3.1 operation keys accepted on a Path Item Object. */
|
|
3
|
+
export type OpenApiOperationMethod = 'delete' | 'get' | 'head' | 'options' | 'patch' | 'post' | 'put' | 'trace';
|
|
4
|
+
interface OpenApiReferenceObject {
|
|
5
|
+
$ref: string;
|
|
6
|
+
summary?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
interface OpenApiServerObject {
|
|
10
|
+
url: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
variables?: Record<string, {
|
|
13
|
+
default: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
enum?: string[];
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* OpenAPI Path Item Object containing standard operations, fixed fields, and specification extensions.
|
|
20
|
+
*/
|
|
21
|
+
export interface OpenApiPathItemObject {
|
|
22
|
+
$ref?: string;
|
|
23
|
+
summary?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
servers?: OpenApiServerObject[];
|
|
26
|
+
parameters?: (OpenApiParameterObject | OpenApiReferenceObject)[];
|
|
27
|
+
delete?: OpenApiOperationObject;
|
|
28
|
+
get?: OpenApiOperationObject;
|
|
29
|
+
head?: OpenApiOperationObject;
|
|
30
|
+
options?: OpenApiOperationObject;
|
|
31
|
+
patch?: OpenApiOperationObject;
|
|
32
|
+
post?: OpenApiOperationObject;
|
|
33
|
+
put?: OpenApiOperationObject;
|
|
34
|
+
trace?: OpenApiOperationObject;
|
|
35
|
+
[extension: `x-${string}`]: unknown;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve one Fluo descriptor method to its standard OpenAPI operation key.
|
|
39
|
+
*
|
|
40
|
+
* @param method Descriptor method supplied by HTTP route metadata.
|
|
41
|
+
* @param path OpenAPI path used to identify invalid descriptor input.
|
|
42
|
+
* @returns The corresponding standard OpenAPI operation key.
|
|
43
|
+
* @throws {TypeError} When Fluo cannot author the descriptor method as an OpenAPI operation.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveDescriptorOperationMethod(method: string, path: string): OpenApiOperationMethod;
|
|
46
|
+
/**
|
|
47
|
+
* Determine whether a Path Item key is a standard OpenAPI 3.1 operation.
|
|
48
|
+
*
|
|
49
|
+
* @param key Path Item key to inspect.
|
|
50
|
+
* @returns Whether the key is a standard operation key.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isOpenApiOperationMethod(key: string): key is OpenApiOperationMethod;
|
|
53
|
+
/**
|
|
54
|
+
* Validate every transformed Path Item against the OpenAPI 3.1 key policy.
|
|
55
|
+
*
|
|
56
|
+
* @param paths Final document paths after any caller transform.
|
|
57
|
+
* @returns Nothing when every Path Item key is valid.
|
|
58
|
+
* @throws {TypeError} When a Path Item contains an unknown non-extension key.
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateOpenApiPathItemKeys(paths: Readonly<Record<string, object>>): void;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=path-item.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-item.d.ts","sourceRoot":"","sources":["../src/path-item.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE1F,0EAA0E;AAC1E,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;AAEhH,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,mBAAmB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,CAAC,sBAAsB,GAAG,sBAAsB,CAAC,EAAE,CAAC;IACjE,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC,GAAG,CAAC,EAAE,sBAAsB,CAAC;IAC7B,IAAI,CAAC,EAAE,sBAAsB,CAAC;IAC9B,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,IAAI,CAAC,EAAE,sBAAsB,CAAC;IAC9B,GAAG,CAAC,EAAE,sBAAsB,CAAC;IAC7B,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAC;CACrC;AAyBD;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,sBAAsB,CAOrG;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,sBAAsB,CAEnF;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAUzF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Standard OpenAPI 3.1 operation keys accepted on a Path Item Object. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenAPI Path Item Object containing standard operations, fixed fields, and specification extensions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const OPENAPI_PATH_ITEM_FIXED_FIELDS = new Set(['$ref', 'description', 'parameters', 'servers', 'summary']);
|
|
8
|
+
const DESCRIPTOR_OPERATION_METHODS = new Map([['DELETE', 'delete'], ['GET', 'get'], ['HEAD', 'head'], ['OPTIONS', 'options'], ['PATCH', 'patch'], ['POST', 'post'], ['PUT', 'put'], ['TRACE', 'trace']]);
|
|
9
|
+
const OPENAPI_OPERATION_METHODS = new Set([...DESCRIPTOR_OPERATION_METHODS.values()]);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve one Fluo descriptor method to its standard OpenAPI operation key.
|
|
13
|
+
*
|
|
14
|
+
* @param method Descriptor method supplied by HTTP route metadata.
|
|
15
|
+
* @param path OpenAPI path used to identify invalid descriptor input.
|
|
16
|
+
* @returns The corresponding standard OpenAPI operation key.
|
|
17
|
+
* @throws {TypeError} When Fluo cannot author the descriptor method as an OpenAPI operation.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveDescriptorOperationMethod(method, path) {
|
|
20
|
+
const operationMethod = DESCRIPTOR_OPERATION_METHODS.get(method);
|
|
21
|
+
if (operationMethod !== undefined) {
|
|
22
|
+
return operationMethod;
|
|
23
|
+
}
|
|
24
|
+
throw new TypeError(`OpenAPI cannot document unsupported HTTP method "${method}" for path "${path}".`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Determine whether a Path Item key is a standard OpenAPI 3.1 operation.
|
|
29
|
+
*
|
|
30
|
+
* @param key Path Item key to inspect.
|
|
31
|
+
* @returns Whether the key is a standard operation key.
|
|
32
|
+
*/
|
|
33
|
+
export function isOpenApiOperationMethod(key) {
|
|
34
|
+
return OPENAPI_OPERATION_METHODS.has(key);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate every transformed Path Item against the OpenAPI 3.1 key policy.
|
|
39
|
+
*
|
|
40
|
+
* @param paths Final document paths after any caller transform.
|
|
41
|
+
* @returns Nothing when every Path Item key is valid.
|
|
42
|
+
* @throws {TypeError} When a Path Item contains an unknown non-extension key.
|
|
43
|
+
*/
|
|
44
|
+
export function validateOpenApiPathItemKeys(paths) {
|
|
45
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
46
|
+
for (const key of Object.keys(pathItem)) {
|
|
47
|
+
if (isOpenApiOperationMethod(key) || OPENAPI_PATH_ITEM_FIXED_FIELDS.has(key) || key.startsWith('x-')) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
throw new TypeError(`OpenAPI Path Item for path "${path}" contains unsupported key "${key}".`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OpenApiDocument } from './schema-builder.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize legacy exclusive-bound and nullable metadata before an OpenAPI 3.1 document is exposed.
|
|
4
|
+
*
|
|
5
|
+
* @param document Generated document, including any final caller transform.
|
|
6
|
+
* @returns A detached document whose schema keywords use OpenAPI 3.1 forms.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeOpenApiDocumentSchemaBounds(document: OpenApiDocument): OpenApiDocument;
|
|
9
|
+
//# sourceMappingURL=schema-bounds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-bounds.d.ts","sourceRoot":"","sources":["../src/schema-bounds.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EAMhB,MAAM,qBAAqB,CAAC;AAuP7B;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAiB/F"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { isOpenApiOperationMethod } from './path-item.js';
|
|
2
|
+
function normalizeSchemaRecord(schemas, path, normalizedSchemas) {
|
|
3
|
+
const normalized = {};
|
|
4
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
5
|
+
normalized[name] = normalizeOpenApiSchemaBounds(schema, `${path}.${name}`, normalizedSchemas);
|
|
6
|
+
}
|
|
7
|
+
return normalized;
|
|
8
|
+
}
|
|
9
|
+
function normalizeSchemaList(schemas, path, normalizedSchemas) {
|
|
10
|
+
return schemas.map((schema, index) => normalizeOpenApiSchemaBounds(schema, `${path}[${String(index)}]`, normalizedSchemas));
|
|
11
|
+
}
|
|
12
|
+
function normalizeOpenApiSchemaBounds(schema, path, normalizedSchemas) {
|
|
13
|
+
const cachedSchema = normalizedSchemas.get(schema);
|
|
14
|
+
if (cachedSchema) {
|
|
15
|
+
return cachedSchema;
|
|
16
|
+
}
|
|
17
|
+
if (schema.nullable === true && schema.type === undefined) {
|
|
18
|
+
const nullableUnion = {};
|
|
19
|
+
const nonNullableSchema = {
|
|
20
|
+
...schema
|
|
21
|
+
};
|
|
22
|
+
delete nonNullableSchema.nullable;
|
|
23
|
+
normalizedSchemas.set(schema, nullableUnion);
|
|
24
|
+
nullableUnion.anyOf = [normalizeOpenApiSchemaBounds(nonNullableSchema, `${path}.anyOf[0]`, normalizedSchemas), {
|
|
25
|
+
type: 'null'
|
|
26
|
+
}];
|
|
27
|
+
return nullableUnion;
|
|
28
|
+
}
|
|
29
|
+
const normalized = {
|
|
30
|
+
...schema
|
|
31
|
+
};
|
|
32
|
+
normalizedSchemas.set(schema, normalized);
|
|
33
|
+
if (typeof schema.nullable === 'boolean') {
|
|
34
|
+
delete normalized.nullable;
|
|
35
|
+
}
|
|
36
|
+
if (schema.nullable === true && schema.type !== undefined) {
|
|
37
|
+
normalized.type = typeof schema.type === 'string' ? schema.type === 'null' ? 'null' : [schema.type, 'null'] : schema.type.includes('null') ? schema.type : [...schema.type, 'null'];
|
|
38
|
+
}
|
|
39
|
+
if (typeof schema.exclusiveMinimum === 'number' && !Number.isFinite(schema.exclusiveMinimum)) {
|
|
40
|
+
throw new TypeError(`OpenAPI schema ${path}.exclusiveMinimum must be a finite number.`);
|
|
41
|
+
}
|
|
42
|
+
if (typeof schema.exclusiveMinimum === 'boolean') {
|
|
43
|
+
delete normalized.exclusiveMinimum;
|
|
44
|
+
if (schema.exclusiveMinimum) {
|
|
45
|
+
if (schema.minimum === undefined || !Number.isFinite(schema.minimum)) {
|
|
46
|
+
throw new TypeError(`OpenAPI schema ${path}.exclusiveMinimum requires a finite minimum.`);
|
|
47
|
+
}
|
|
48
|
+
normalized.exclusiveMinimum = schema.minimum;
|
|
49
|
+
delete normalized.minimum;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (typeof schema.exclusiveMaximum === 'number' && !Number.isFinite(schema.exclusiveMaximum)) {
|
|
53
|
+
throw new TypeError(`OpenAPI schema ${path}.exclusiveMaximum must be a finite number.`);
|
|
54
|
+
}
|
|
55
|
+
if (typeof schema.exclusiveMaximum === 'boolean') {
|
|
56
|
+
delete normalized.exclusiveMaximum;
|
|
57
|
+
if (schema.exclusiveMaximum) {
|
|
58
|
+
if (schema.maximum === undefined || !Number.isFinite(schema.maximum)) {
|
|
59
|
+
throw new TypeError(`OpenAPI schema ${path}.exclusiveMaximum requires a finite maximum.`);
|
|
60
|
+
}
|
|
61
|
+
normalized.exclusiveMaximum = schema.maximum;
|
|
62
|
+
delete normalized.maximum;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (schema.allOf) {
|
|
66
|
+
normalized.allOf = normalizeSchemaList(schema.allOf, `${path}.allOf`, normalizedSchemas);
|
|
67
|
+
}
|
|
68
|
+
if (schema.oneOf) {
|
|
69
|
+
normalized.oneOf = normalizeSchemaList(schema.oneOf, `${path}.oneOf`, normalizedSchemas);
|
|
70
|
+
}
|
|
71
|
+
if (schema.anyOf) {
|
|
72
|
+
normalized.anyOf = normalizeSchemaList(schema.anyOf, `${path}.anyOf`, normalizedSchemas);
|
|
73
|
+
}
|
|
74
|
+
if (schema.not) {
|
|
75
|
+
normalized.not = normalizeOpenApiSchemaBounds(schema.not, `${path}.not`, normalizedSchemas);
|
|
76
|
+
}
|
|
77
|
+
if (schema.properties) {
|
|
78
|
+
normalized.properties = normalizeSchemaRecord(schema.properties, `${path}.properties`, normalizedSchemas);
|
|
79
|
+
}
|
|
80
|
+
if (schema.items) {
|
|
81
|
+
normalized.items = normalizeOpenApiSchemaBounds(schema.items, `${path}.items`, normalizedSchemas);
|
|
82
|
+
}
|
|
83
|
+
if (typeof schema.additionalProperties === 'object') {
|
|
84
|
+
normalized.additionalProperties = normalizeOpenApiSchemaBounds(schema.additionalProperties, `${path}.additionalProperties`, normalizedSchemas);
|
|
85
|
+
}
|
|
86
|
+
return normalized;
|
|
87
|
+
}
|
|
88
|
+
function normalizeContent(content, path, normalizedSchemas) {
|
|
89
|
+
const normalized = {};
|
|
90
|
+
for (const [mediaType, media] of Object.entries(content)) {
|
|
91
|
+
normalized[mediaType] = {
|
|
92
|
+
...media,
|
|
93
|
+
schema: normalizeOpenApiSchemaBounds(media.schema, `${path}.${mediaType}.schema`, normalizedSchemas)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
function normalizeResponses(responses, path, normalizedSchemas) {
|
|
99
|
+
const normalized = {};
|
|
100
|
+
for (const [status, response] of Object.entries(responses)) {
|
|
101
|
+
normalized[status] = {
|
|
102
|
+
...response,
|
|
103
|
+
...(response.content ? {
|
|
104
|
+
content: normalizeContent(response.content, `${path}.${status}.content`, normalizedSchemas)
|
|
105
|
+
} : {})
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return normalized;
|
|
109
|
+
}
|
|
110
|
+
function normalizeOperation(operation, path, normalizedSchemas) {
|
|
111
|
+
return {
|
|
112
|
+
...operation,
|
|
113
|
+
...(operation.parameters ? {
|
|
114
|
+
parameters: operation.parameters.map((parameter, index) => ({
|
|
115
|
+
...parameter,
|
|
116
|
+
schema: normalizeOpenApiSchemaBounds(parameter.schema, `${path}.parameters[${String(index)}].schema`, normalizedSchemas)
|
|
117
|
+
}))
|
|
118
|
+
} : {}),
|
|
119
|
+
...(operation.requestBody ? {
|
|
120
|
+
requestBody: {
|
|
121
|
+
...operation.requestBody,
|
|
122
|
+
content: normalizeContent(operation.requestBody.content, `${path}.requestBody.content`, normalizedSchemas)
|
|
123
|
+
}
|
|
124
|
+
} : {}),
|
|
125
|
+
responses: normalizeResponses(operation.responses, `${path}.responses`, normalizedSchemas)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function normalizePaths(paths, normalizedSchemas) {
|
|
129
|
+
const normalizedPaths = {};
|
|
130
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
131
|
+
const normalizedPathItem = {};
|
|
132
|
+
if (pathItem.parameters) {
|
|
133
|
+
normalizedPathItem.parameters = pathItem.parameters.map((parameter, index) => 'schema' in parameter ? {
|
|
134
|
+
...parameter,
|
|
135
|
+
schema: normalizeOpenApiSchemaBounds(parameter.schema, `paths.${path}.parameters[${String(index)}].schema`, normalizedSchemas)
|
|
136
|
+
} : {
|
|
137
|
+
...parameter
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
for (const [key, value] of Object.entries(pathItem)) {
|
|
141
|
+
if (key === 'parameters') {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (isOpenApiOperationMethod(key)) {
|
|
145
|
+
normalizedPathItem[key] = value ? normalizeOperation(value, `paths.${path}.${key}`, normalizedSchemas) : undefined;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
Reflect.set(normalizedPathItem, key, value);
|
|
149
|
+
}
|
|
150
|
+
normalizedPaths[path] = normalizedPathItem;
|
|
151
|
+
}
|
|
152
|
+
return normalizedPaths;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Normalize legacy exclusive-bound and nullable metadata before an OpenAPI 3.1 document is exposed.
|
|
157
|
+
*
|
|
158
|
+
* @param document Generated document, including any final caller transform.
|
|
159
|
+
* @returns A detached document whose schema keywords use OpenAPI 3.1 forms.
|
|
160
|
+
*/
|
|
161
|
+
export function normalizeOpenApiDocumentSchemaBounds(document) {
|
|
162
|
+
const normalizedSchemas = new WeakMap();
|
|
163
|
+
return {
|
|
164
|
+
...document,
|
|
165
|
+
...(document.components ? {
|
|
166
|
+
components: {
|
|
167
|
+
...document.components,
|
|
168
|
+
...(document.components.schemas ? {
|
|
169
|
+
schemas: normalizeSchemaRecord(document.components.schemas, 'components.schemas', normalizedSchemas)
|
|
170
|
+
} : {})
|
|
171
|
+
}
|
|
172
|
+
} : {}),
|
|
173
|
+
paths: normalizePaths(document.paths, normalizedSchemas)
|
|
174
|
+
};
|
|
175
|
+
}
|
package/dist/schema-builder.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Constructor } from '@fluojs/core';
|
|
2
2
|
import type { HandlerDescriptor } from '@fluojs/http';
|
|
3
|
+
import { type OpenApiPathItemObject } from './path-item.js';
|
|
4
|
+
export type { OpenApiPathItemObject } from './path-item.js';
|
|
3
5
|
/**
|
|
4
6
|
* JSON Schema primitive type names accepted by OpenAPI 3.1 schema objects.
|
|
5
7
|
*/
|
|
@@ -52,6 +54,7 @@ export interface OpenApiSchemaObject {
|
|
|
52
54
|
deprecated?: boolean;
|
|
53
55
|
readOnly?: boolean;
|
|
54
56
|
writeOnly?: boolean;
|
|
57
|
+
/** Legacy compatibility input normalized to an OpenAPI 3.1 null union before emission. */
|
|
55
58
|
nullable?: boolean;
|
|
56
59
|
minimum?: number;
|
|
57
60
|
maximum?: number;
|
|
@@ -147,12 +150,6 @@ export interface OpenApiOperationObject {
|
|
|
147
150
|
requestBody?: OpenApiRequestBodyObject;
|
|
148
151
|
security?: OpenApiSecurityRequirementObject[];
|
|
149
152
|
}
|
|
150
|
-
/**
|
|
151
|
-
* OpenAPI path-item object containing one or more HTTP method operations.
|
|
152
|
-
*/
|
|
153
|
-
export interface OpenApiPathItemObject {
|
|
154
|
-
[method: string]: OpenApiOperationObject | undefined;
|
|
155
|
-
}
|
|
156
153
|
/**
|
|
157
154
|
* Root OpenAPI 3.1.0 document produced by `buildOpenApiDocument(...)`.
|
|
158
155
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-builder.d.ts","sourceRoot":"","sources":["../src/schema-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,cAAc,CAAC;AAErE,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"schema-builder.d.ts","sourceRoot":"","sources":["../src/schema-builder.ts"],"names":[],"mappings":"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"}
|
package/dist/schema-builder.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { getDtoBindingSchema, getDtoValidationSchema } from '@fluojs/core/
|
|
1
|
+
import { getDtoBindingSchema, getDtoValidationSchema } from '@fluojs/core/request-pipeline';
|
|
2
2
|
import { getControllerTags, getMethodApiMetadata } from './decorators.js';
|
|
3
|
+
import { resolveDescriptorOperationMethod, validateOpenApiPathItemKeys } from './path-item.js';
|
|
4
|
+
import { normalizeOpenApiDocumentSchemaBounds } from './schema-bounds.js';
|
|
3
5
|
import { cloneSnapshotValue } from './snapshot.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -46,10 +48,6 @@ import { cloneSnapshotValue } from './snapshot.js';
|
|
|
46
48
|
* OpenAPI operation object emitted for a single HTTP method on a path.
|
|
47
49
|
*/
|
|
48
50
|
|
|
49
|
-
/**
|
|
50
|
-
* OpenAPI path-item object containing one or more HTTP method operations.
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
51
|
/**
|
|
54
52
|
* Root OpenAPI 3.1.0 document produced by `buildOpenApiDocument(...)`.
|
|
55
53
|
*/
|
|
@@ -491,12 +489,9 @@ function ensureComponentSchema(dto, componentSchemas, context) {
|
|
|
491
489
|
const schemaName = getDtoSchemaName(dto, context);
|
|
492
490
|
return ensureComponentSchemaFromEntries(schemaName, collectDtoEntries(dto, context), componentSchemas, context);
|
|
493
491
|
}
|
|
494
|
-
function createParameters(dto, context) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
const entries = collectDtoEntries(dto, context).filter(entry => entry.binding?.metadata.source === 'path' || entry.binding?.metadata.source === 'query' || entry.binding?.metadata.source === 'header' || entry.binding?.metadata.source === 'cookie');
|
|
499
|
-
return entries.map(entry => {
|
|
492
|
+
function createParameters(dto, routePath, context) {
|
|
493
|
+
const entries = dto ? collectDtoEntries(dto, context).filter(entry => entry.binding?.metadata.source === 'path' || entry.binding?.metadata.source === 'query' || entry.binding?.metadata.source === 'header' || entry.binding?.metadata.source === 'cookie') : [];
|
|
494
|
+
const parameters = entries.map(entry => {
|
|
500
495
|
const source = entry.binding.metadata.source;
|
|
501
496
|
const rules = entry.validation?.rules ?? [];
|
|
502
497
|
const inferred = inferPrimitiveTypeFromRules(rules, context) ?? {};
|
|
@@ -509,6 +504,22 @@ function createParameters(dto, context) {
|
|
|
509
504
|
schema
|
|
510
505
|
};
|
|
511
506
|
});
|
|
507
|
+
const documentedPathParameters = new Set(parameters.filter(parameter => parameter.in === 'path').map(parameter => parameter.name));
|
|
508
|
+
for (const match of routePath.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)) {
|
|
509
|
+
const name = match[1];
|
|
510
|
+
if (!documentedPathParameters.has(name)) {
|
|
511
|
+
documentedPathParameters.add(name);
|
|
512
|
+
parameters.push({
|
|
513
|
+
in: 'path',
|
|
514
|
+
name,
|
|
515
|
+
required: true,
|
|
516
|
+
schema: {
|
|
517
|
+
type: 'string'
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return parameters;
|
|
512
523
|
}
|
|
513
524
|
function ensureErrorResponseSchema(componentSchemas) {
|
|
514
525
|
const schemaName = 'ErrorResponse';
|
|
@@ -785,7 +796,7 @@ function hasBearerAuthRequirement(security) {
|
|
|
785
796
|
return Boolean(security?.some(requirement => Object.keys(requirement).includes('bearerAuth')));
|
|
786
797
|
}
|
|
787
798
|
function createOperationObject(descriptor, methodMeta, responses, componentSchemas, security, context, usedOperationIds) {
|
|
788
|
-
const parameters = mergeOperationParameters(createParameters(descriptor.route.request, context), methodMeta?.parameters);
|
|
799
|
+
const parameters = mergeOperationParameters(createParameters(descriptor.route.request, descriptor.route.path, context), methodMeta?.parameters);
|
|
789
800
|
const requestBody = mergeOperationRequestBody(createRequestBody(descriptor.route.request, componentSchemas, context), methodMeta);
|
|
790
801
|
return {
|
|
791
802
|
operationId: resolveUniqueOperationId(normalizeOperationId(descriptor), usedOperationIds),
|
|
@@ -813,11 +824,11 @@ function createOperationObject(descriptor, methodMeta, responses, componentSchem
|
|
|
813
824
|
}
|
|
814
825
|
function buildOperationEntry(descriptor, componentSchemas, defaultErrorResponsesPolicy, context, usedOperationIds) {
|
|
815
826
|
const openApiPath = expressPathToOpenApi(descriptor.route.path);
|
|
816
|
-
const method = descriptor.route.method.toLowerCase();
|
|
817
827
|
const methodMeta = getMethodApiMetadata(descriptor.controllerToken, descriptor.methodName);
|
|
818
828
|
if (methodMeta?.excludeEndpoint === true) {
|
|
819
829
|
return undefined;
|
|
820
830
|
}
|
|
831
|
+
const method = resolveDescriptorOperationMethod(descriptor.route.method, openApiPath);
|
|
821
832
|
const responses = createOperationResponses(descriptor, methodMeta, componentSchemas, defaultErrorResponsesPolicy, context);
|
|
822
833
|
const security = createOperationSecurity(methodMeta);
|
|
823
834
|
const operation = createOperationObject(descriptor, methodMeta, responses, componentSchemas, security, context, usedOperationIds);
|
|
@@ -917,5 +928,7 @@ export function buildOpenApiDocument(options) {
|
|
|
917
928
|
openapi: '3.1.0',
|
|
918
929
|
paths
|
|
919
930
|
};
|
|
920
|
-
|
|
931
|
+
const transformedDocument = options.documentTransform ? options.documentTransform(document) : document;
|
|
932
|
+
validateOpenApiPathItemKeys(transformedDocument.paths);
|
|
933
|
+
return normalizeOpenApiDocumentSchemaBounds(transformedDocument);
|
|
921
934
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type SwaggerUiRoutes = {
|
|
2
|
+
readonly documentPath: string;
|
|
3
|
+
readonly uiPath: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Asset URLs used by the generated Swagger UI HTML page.
|
|
7
|
+
*/
|
|
8
|
+
export interface OpenApiSwaggerUiAssetsOptions {
|
|
9
|
+
cssUrl?: string;
|
|
10
|
+
jsBundleUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve pinned or caller-provided Swagger UI asset URLs.
|
|
14
|
+
*
|
|
15
|
+
* @param assets Optional caller-provided asset URLs.
|
|
16
|
+
* @returns Complete asset URLs for generated Swagger UI HTML.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveSwaggerUiAssets(assets: OpenApiSwaggerUiAssetsOptions | undefined): Required<OpenApiSwaggerUiAssetsOptions>;
|
|
19
|
+
/**
|
|
20
|
+
* Render Swagger UI HTML for one configured OpenAPI document route.
|
|
21
|
+
*
|
|
22
|
+
* @param title Document title rendered into the page.
|
|
23
|
+
* @param assets Complete Swagger UI asset URLs.
|
|
24
|
+
* @param routes Normalized JSON document and UI routes.
|
|
25
|
+
* @returns A standalone Swagger UI HTML document.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSwaggerUiHtml(title: string, assets: Required<OpenApiSwaggerUiAssetsOptions>, routes: SwaggerUiRoutes): string;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=swagger-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swagger-ui.d.ts","sourceRoot":"","sources":["../src/swagger-ui.ts"],"names":[],"mappings":"AAKA,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,6BAA6B,GAAG,SAAS,GAChD,QAAQ,CAAC,6BAA6B,CAAC,CAKzC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,CAAC,6BAA6B,CAAC,EAC/C,MAAM,EAAE,eAAe,GACtB,MAAM,CA6BR"}
|