@zudojs/openapi 1.3.0 → 1.4.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.
|
@@ -1,5 +1,27 @@
|
|
|
1
|
-
import { convertRouteToOpenAPI } from "./routeConverter.core.js";
|
|
1
|
+
import { convertRouteToOpenAPI, toOpenAPIPath } from "./routeConverter.core.js";
|
|
2
2
|
import { OpenAPIRouteError } from "../openApiErrors/openApiError.types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Identity of a route inside the generated document.
|
|
5
|
+
*
|
|
6
|
+
* Keyed on the OpenAPI path template rather than the source path, because
|
|
7
|
+
* that is what the document is keyed on: `/users/:id` and `/users/{id}` are
|
|
8
|
+
* one path item. Keying on the raw spelling let both register, and the
|
|
9
|
+
* second then replaced the first during generation — one operation vanished
|
|
10
|
+
* from the published spec with `validate()` reporting nothing.
|
|
11
|
+
*
|
|
12
|
+
* A path `toOpenAPIPath` cannot express keeps its raw spelling here so the
|
|
13
|
+
* conversion error still surfaces from `scan()`, where it always has.
|
|
14
|
+
*/
|
|
15
|
+
function routeKey(method, path) {
|
|
16
|
+
let template;
|
|
17
|
+
try {
|
|
18
|
+
template = toOpenAPIPath(path);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
template = path;
|
|
22
|
+
}
|
|
23
|
+
return `${method.toLowerCase()}:${template}`;
|
|
24
|
+
}
|
|
3
25
|
/**
|
|
4
26
|
* Collects routes and converts them into OpenAPI operations.
|
|
5
27
|
*
|
|
@@ -12,23 +34,33 @@ export class OpenAPIRouteScannerImpl {
|
|
|
12
34
|
routes = new Map();
|
|
13
35
|
/** Registers a route. */
|
|
14
36
|
addRoute(route) {
|
|
15
|
-
const key =
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
const key = routeKey(route.method, route.path);
|
|
38
|
+
const existing = this.routes.get(key);
|
|
39
|
+
if (existing !== undefined) {
|
|
40
|
+
throw new OpenAPIRouteError(`Route ${route.method.toUpperCase()} ${route.path} is already registered` +
|
|
41
|
+
(existing.path === route.path
|
|
42
|
+
? "."
|
|
43
|
+
: ` as ${existing.method.toUpperCase()} ${existing.path}; both describe the same OpenAPI path.`), {
|
|
44
|
+
metadata: {
|
|
45
|
+
method: route.method,
|
|
46
|
+
path: route.path,
|
|
47
|
+
existingPath: existing.path,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
18
50
|
}
|
|
19
51
|
this.routes.set(key, route);
|
|
20
52
|
}
|
|
21
53
|
/** Registers a route, replacing any existing one for the same method+path. */
|
|
22
54
|
setRoute(route) {
|
|
23
|
-
this.routes.set(
|
|
55
|
+
this.routes.set(routeKey(route.method, route.path), route);
|
|
24
56
|
}
|
|
25
57
|
/** True when a route is registered for this method and path. */
|
|
26
58
|
hasRoute(method, path) {
|
|
27
|
-
return this.routes.has(
|
|
59
|
+
return this.routes.has(routeKey(method, path));
|
|
28
60
|
}
|
|
29
61
|
/** Removes a route. Returns whether one was removed. */
|
|
30
62
|
removeRoute(method, path) {
|
|
31
|
-
return this.routes.delete(
|
|
63
|
+
return this.routes.delete(routeKey(method, path));
|
|
32
64
|
}
|
|
33
65
|
/** Number of registered routes. */
|
|
34
66
|
get size() {
|
|
@@ -11,6 +11,8 @@ import type { OpenAPISchema } from "../openApiTypes/openApiTypes.core.js";
|
|
|
11
11
|
*
|
|
12
12
|
* Field names as of `@zudojs/schema@0.1.0`:
|
|
13
13
|
* object `_config.shape`, `_config.requiredKeys` (a Set), `_config.unknownKeys`
|
|
14
|
+
* (`"strip" | "strict" | "passthrough"`; absent means the parser's
|
|
15
|
+
* own `?? "strip"` default, so it is read with that same default)
|
|
14
16
|
* array `_config.itemSchema`, `_config.min`, `_config.max`, `_config.length`
|
|
15
17
|
* (no `max` means the parser's implicit ceiling, which is emitted)
|
|
16
18
|
* string `_config.min|max|length|pattern|format`
|
|
@@ -309,14 +309,19 @@ function convertSchemaNode(schema, state) {
|
|
|
309
309
|
if (forced || !acceptsMissingKey(value))
|
|
310
310
|
required.push(key);
|
|
311
311
|
}
|
|
312
|
-
|
|
312
|
+
// `ObjectSchema` applies `?? "strip"` internally, so an absent
|
|
313
|
+
// `unknownKeys` is the same contract as an explicit `.strip()` and
|
|
314
|
+
// must document the same. Only `strict` emits
|
|
315
|
+
// `additionalProperties: false`: that is OpenAPI for "reject the
|
|
316
|
+
// payload", while strip accepts it and discards the extra key, so
|
|
317
|
+
// emitting it for strip made a generated client refuse what the
|
|
318
|
+
// service accepts.
|
|
319
|
+
const unknownKeys = c["unknownKeys"] ?? "strip";
|
|
313
320
|
return {
|
|
314
321
|
type: "object",
|
|
315
322
|
...(Object.keys(properties).length > 0 ? { properties } : {}),
|
|
316
323
|
...(required.length > 0 ? { required } : {}),
|
|
317
|
-
...(unknownKeys === "
|
|
318
|
-
? { additionalProperties: false }
|
|
319
|
-
: {}),
|
|
324
|
+
...(unknownKeys === "strict" ? { additionalProperties: false } : {}),
|
|
320
325
|
};
|
|
321
326
|
}
|
|
322
327
|
case "record": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/openapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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,8 +26,8 @@
|
|
|
26
26
|
"!dist/.tsbuildinfo"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@zudojs/constants": "1.1.
|
|
30
|
-
"@zudojs/errors": "1.
|
|
29
|
+
"@zudojs/constants": "1.1.1",
|
|
30
|
+
"@zudojs/errors": "1.2.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^26.4.1",
|