pipework 0.261.0 → 0.261.1
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/CHANGELOG.md +4 -0
- package/dist/data/schema/json-schema.d.ts +2 -0
- package/dist/data/schema/json-schema.d.ts.map +1 -1
- package/dist/data/schema/json-schema.js +4 -0
- package/dist/data/schema/json-schema.js.map +1 -1
- package/dist/request/openapi/generate.d.ts +3 -0
- package/dist/request/openapi/generate.d.ts.map +1 -1
- package/dist/request/openapi/generate.js +71 -8
- package/dist/request/openapi/generate.js.map +1 -1
- package/package.json +1 -1
- package/src/data/schema/json-schema.ts +5 -0
- package/src/request/openapi/generate.ts +70 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.261.1
|
|
4
|
+
|
|
5
|
+
- **Fixed: a recursive `check.lazy` no longer gives an OpenAPI document with refs nothing can resolve** (#918). `generateOpenAPI` (and `pipework openapi`) hoists a recursive schema into `components.schemas` (`Schema1`, `Schema2`, …; a `.meta({ id })` keeps its id) and references it as `#/components/schemas/<name>`; the same schema used by many routes is one component. Schemas are now written in OpenAPI 3.0 form: `.nullable()` is `nullable: true` instead of `anyOf [.., { type: 'null' }]`, and no `$schema` key. `components` is present only when something was hoisted. Fastify's request/response validation is unchanged.
|
|
6
|
+
|
|
3
7
|
## 0.261.0
|
|
4
8
|
|
|
5
9
|
- **Added: `MailConfig.unsubscribe.lists` names a list on the unsubscribe pages** (#916). `lists: { 'notice-digest': { name: 'emails about notices you haven’t read' } }` shows the name, escaped, in place of the list id on the confirm and done pages of `unsubscribeHttp()`. A list with no entry shows its id as before; `mail.create` refuses a key that is not a valid list name or an empty name, naming the key.
|
|
@@ -2,4 +2,6 @@ import { z } from 'zod/v4';
|
|
|
2
2
|
import type { ZodStandardJSONSchemaPayload } from 'zod/v4/core';
|
|
3
3
|
export type JsonSchema = ZodStandardJSONSchemaPayload<z.ZodType>;
|
|
4
4
|
export declare function toJsonSchema(schema: z.ZodType): JsonSchema;
|
|
5
|
+
/** OpenAPI 3.0 form: `nullable: true`, no `$schema`; recursion still arrives as a `definitions` block. */
|
|
6
|
+
export declare function toOpenApiSchema(schema: z.ZodType): JsonSchema;
|
|
5
7
|
//# sourceMappingURL=json-schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../../../src/data/schema/json-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAC1B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA;AAE/D,MAAM,MAAM,UAAU,GAAG,4BAA4B,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;AAEhE,wBAAgB,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,UAAU,CAE1D"}
|
|
1
|
+
{"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../../../src/data/schema/json-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAC1B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA;AAE/D,MAAM,MAAM,UAAU,GAAG,4BAA4B,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;AAEhE,wBAAgB,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,UAAU,CAE1D;AAED,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,UAAU,CAE7D"}
|
|
@@ -2,6 +2,10 @@ import { z } from 'zod/v4';
|
|
|
2
2
|
export function toJsonSchema(schema) {
|
|
3
3
|
return collapseSingleRefAllOf(z.toJSONSchema(schema, { target: 'draft-07' }));
|
|
4
4
|
}
|
|
5
|
+
/** OpenAPI 3.0 form: `nullable: true`, no `$schema`; recursion still arrives as a `definitions` block. */
|
|
6
|
+
export function toOpenApiSchema(schema) {
|
|
7
|
+
return collapseSingleRefAllOf(z.toJSONSchema(schema, { target: 'openapi-3.0' }));
|
|
8
|
+
}
|
|
5
9
|
// zod emits a lazy reference as `{ allOf: [{ $ref }] }`; fast-json-stringify recurses
|
|
6
10
|
// forever on that form when the target is recursive, and accepts the bare `$ref`.
|
|
7
11
|
function collapseSingleRefAllOf(node) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-schema.js","sourceRoot":"","sources":["../../../src/data/schema/json-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAK1B,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,OAAO,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAe,CAAA;AAC7F,CAAC;AAED,sFAAsF;AACtF,kFAAkF;AAClF,SAAS,sBAAsB,CAAC,IAAa;IAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;IAChE,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC1D,MAAM,MAAM,GAAG,IAA+B,CAAA;IAC9C,MAAM,GAAG,GAA4B,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,GAAG,KAAK,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAChC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC7D,MAAM,MAAM,GAAY,KAAK,CAAC,CAAC,CAAC,CAAA;IAChC,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACxF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAChC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,CAAA;AAChD,CAAC"}
|
|
1
|
+
{"version":3,"file":"json-schema.js","sourceRoot":"","sources":["../../../src/data/schema/json-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAK1B,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,OAAO,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAe,CAAA;AAC7F,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,OAAO,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAe,CAAA;AAChG,CAAC;AAED,sFAAsF;AACtF,kFAAkF;AAClF,SAAS,sBAAsB,CAAC,IAAa;IAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;IAChE,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC1D,MAAM,MAAM,GAAG,IAA+B,CAAA;IAC9C,MAAM,GAAG,GAA4B,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,GAAG,KAAK,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAChC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC7D,MAAM,MAAM,GAAY,KAAK,CAAC,CAAC,CAAC,CAAA;IAChC,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACxF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAChC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,CAAA;AAChD,CAAC"}
|
|
@@ -8,6 +8,9 @@ export interface OpenAPIDocument {
|
|
|
8
8
|
openapi: '3.0.3';
|
|
9
9
|
info: OpenAPIInfo;
|
|
10
10
|
paths: Record<string, Record<string, unknown>>;
|
|
11
|
+
components?: {
|
|
12
|
+
schemas: Record<string, unknown>;
|
|
13
|
+
};
|
|
11
14
|
}
|
|
12
15
|
/** The body of a validation 422: `{ error: { code, message, fields? } }`. */
|
|
13
16
|
export declare const VALIDATION_ERROR_SCHEMA: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/request/openapi/generate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAGzD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/request/openapi/generate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAGzD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC9C,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CAClD;AAED,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;CAc1B,CAAA;AAEV,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;CAc3B,CAAA;AA2DV,wBAAgB,eAAe,CAC7B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAClC,eAAe,CA4JjB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toOpenApiSchema } from '../../data/schema/json-schema.js';
|
|
2
2
|
/** The body of a validation 422: `{ error: { code, message, fields? } }`. */
|
|
3
3
|
export const VALIDATION_ERROR_SCHEMA = {
|
|
4
4
|
type: 'object',
|
|
@@ -30,8 +30,69 @@ export const TOO_MANY_REQUESTS_SCHEMA = {
|
|
|
30
30
|
},
|
|
31
31
|
required: ['error'],
|
|
32
32
|
};
|
|
33
|
+
const LOCAL_REF = '#/definitions/';
|
|
34
|
+
function rewriteRefs(node, names) {
|
|
35
|
+
if (Array.isArray(node))
|
|
36
|
+
return node.map((n) => rewriteRefs(n, names));
|
|
37
|
+
if (node === null || typeof node !== 'object')
|
|
38
|
+
return node;
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [key, value] of Object.entries(node)) {
|
|
41
|
+
if (key === '$ref' && typeof value === 'string' && value.startsWith(LOCAL_REF)) {
|
|
42
|
+
const local = value.slice(LOCAL_REF.length);
|
|
43
|
+
out[key] = `#/components/schemas/${names.get(local) ?? local}`;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
out[key] = rewriteRefs(value, names);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
/** Converts schemas for one document, hoisting zod's per-schema `definitions` into shared components. */
|
|
52
|
+
function createConverter(components) {
|
|
53
|
+
const seen = new Map();
|
|
54
|
+
let counter = 0;
|
|
55
|
+
const nameFor = (local, taken) => {
|
|
56
|
+
if (/^__schema\d+$/.test(local)) {
|
|
57
|
+
let name;
|
|
58
|
+
do
|
|
59
|
+
name = `Schema${++counter}`;
|
|
60
|
+
while (name in components || taken.has(name));
|
|
61
|
+
return name;
|
|
62
|
+
}
|
|
63
|
+
let name = local;
|
|
64
|
+
for (let n = 2; name in components || taken.has(name); n++)
|
|
65
|
+
name = `${local}_${n}`;
|
|
66
|
+
return name;
|
|
67
|
+
};
|
|
68
|
+
return (schema) => {
|
|
69
|
+
const { definitions, ...rest } = toOpenApiSchema(schema);
|
|
70
|
+
delete rest['$schema'];
|
|
71
|
+
if (definitions === null || typeof definitions !== 'object')
|
|
72
|
+
return rest;
|
|
73
|
+
const key = JSON.stringify(definitions);
|
|
74
|
+
let names = seen.get(key);
|
|
75
|
+
if (names === undefined) {
|
|
76
|
+
const map = new Map();
|
|
77
|
+
const taken = new Set();
|
|
78
|
+
for (const local of Object.keys(definitions)) {
|
|
79
|
+
const name = nameFor(local, taken);
|
|
80
|
+
taken.add(name);
|
|
81
|
+
map.set(local, name);
|
|
82
|
+
}
|
|
83
|
+
for (const [local, def] of Object.entries(definitions)) {
|
|
84
|
+
components[map.get(local)] = rewriteRefs(def, map);
|
|
85
|
+
}
|
|
86
|
+
seen.set(key, map);
|
|
87
|
+
names = map;
|
|
88
|
+
}
|
|
89
|
+
return rewriteRefs(rest, names);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
33
92
|
export function generateOpenAPI(info, routes, options) {
|
|
34
93
|
const paths = {};
|
|
94
|
+
const components = {};
|
|
95
|
+
const convert = createConverter(components);
|
|
35
96
|
for (const route of routes) {
|
|
36
97
|
const { method, path, handler } = route;
|
|
37
98
|
const meta = handler.meta;
|
|
@@ -44,7 +105,7 @@ export function generateOpenAPI(info, routes, options) {
|
|
|
44
105
|
const operation = {};
|
|
45
106
|
const parameters = [];
|
|
46
107
|
if (meta.paramsSchema) {
|
|
47
|
-
const schema =
|
|
108
|
+
const schema = convert(meta.paramsSchema);
|
|
48
109
|
if ('properties' in schema) {
|
|
49
110
|
const props = schema['properties'];
|
|
50
111
|
for (const [name, propSchema] of Object.entries(props)) {
|
|
@@ -58,7 +119,7 @@ export function generateOpenAPI(info, routes, options) {
|
|
|
58
119
|
}
|
|
59
120
|
}
|
|
60
121
|
if (meta.querySchema) {
|
|
61
|
-
const schema =
|
|
122
|
+
const schema = convert(meta.querySchema);
|
|
62
123
|
if ('properties' in schema) {
|
|
63
124
|
const props = schema['properties'];
|
|
64
125
|
const requiredFields = Array.isArray(schema['required']) ? schema['required'] : [];
|
|
@@ -81,7 +142,7 @@ export function generateOpenAPI(info, routes, options) {
|
|
|
81
142
|
required: !meta.inputSchema.safeParse(undefined).success,
|
|
82
143
|
content: {
|
|
83
144
|
'application/json': {
|
|
84
|
-
schema:
|
|
145
|
+
schema: convert(meta.inputSchema),
|
|
85
146
|
},
|
|
86
147
|
},
|
|
87
148
|
};
|
|
@@ -116,7 +177,7 @@ export function generateOpenAPI(info, routes, options) {
|
|
|
116
177
|
description: status === 200 ? 'Successful response' : `${status} response`,
|
|
117
178
|
content: {
|
|
118
179
|
'application/json': {
|
|
119
|
-
schema:
|
|
180
|
+
schema: convert(outputSchema),
|
|
120
181
|
},
|
|
121
182
|
},
|
|
122
183
|
};
|
|
@@ -139,7 +200,7 @@ export function generateOpenAPI(info, routes, options) {
|
|
|
139
200
|
'application/json': {
|
|
140
201
|
schema: declared === undefined
|
|
141
202
|
? VALIDATION_ERROR_SCHEMA
|
|
142
|
-
: { oneOf: [
|
|
203
|
+
: { oneOf: [convert(declared), VALIDATION_ERROR_SCHEMA] },
|
|
143
204
|
},
|
|
144
205
|
},
|
|
145
206
|
};
|
|
@@ -152,7 +213,7 @@ export function generateOpenAPI(info, routes, options) {
|
|
|
152
213
|
'application/json': {
|
|
153
214
|
schema: declared === undefined
|
|
154
215
|
? TOO_MANY_REQUESTS_SCHEMA
|
|
155
|
-
: { oneOf: [
|
|
216
|
+
: { oneOf: [convert(declared), TOO_MANY_REQUESTS_SCHEMA] },
|
|
156
217
|
},
|
|
157
218
|
},
|
|
158
219
|
};
|
|
@@ -166,6 +227,8 @@ export function generateOpenAPI(info, routes, options) {
|
|
|
166
227
|
operation['responses'] = responses;
|
|
167
228
|
pathItem[method.toLowerCase()] = operation;
|
|
168
229
|
}
|
|
169
|
-
return
|
|
230
|
+
return Object.keys(components).length === 0
|
|
231
|
+
? { openapi: '3.0.3', info, paths }
|
|
232
|
+
: { openapi: '3.0.3', info, paths, components: { schemas: components } };
|
|
170
233
|
}
|
|
171
234
|
//# sourceMappingURL=generate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/request/openapi/generate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/request/openapi/generate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -7,6 +7,11 @@ export function toJsonSchema(schema: z.ZodType): JsonSchema {
|
|
|
7
7
|
return collapseSingleRefAllOf(z.toJSONSchema(schema, { target: 'draft-07' })) as JsonSchema
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/** OpenAPI 3.0 form: `nullable: true`, no `$schema`; recursion still arrives as a `definitions` block. */
|
|
11
|
+
export function toOpenApiSchema(schema: z.ZodType): JsonSchema {
|
|
12
|
+
return collapseSingleRefAllOf(z.toJSONSchema(schema, { target: 'openapi-3.0' })) as JsonSchema
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
// zod emits a lazy reference as `{ allOf: [{ $ref }] }`; fast-json-stringify recurses
|
|
11
16
|
// forever on that form when the target is recursive, and accepts the bare `$ref`.
|
|
12
17
|
function collapseSingleRefAllOf(node: unknown): unknown {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RouteRegistration } from '../http/types.js'
|
|
2
|
-
import {
|
|
2
|
+
import { toOpenApiSchema } from '../../data/schema/json-schema.js'
|
|
3
3
|
|
|
4
4
|
export interface OpenAPIInfo {
|
|
5
5
|
title: string
|
|
@@ -11,6 +11,7 @@ export interface OpenAPIDocument {
|
|
|
11
11
|
openapi: '3.0.3'
|
|
12
12
|
info: OpenAPIInfo
|
|
13
13
|
paths: Record<string, Record<string, unknown>>
|
|
14
|
+
components?: { schemas: Record<string, unknown> }
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/** The body of a validation 422: `{ error: { code, message, fields? } }`. */
|
|
@@ -46,12 +47,71 @@ export const TOO_MANY_REQUESTS_SCHEMA = {
|
|
|
46
47
|
required: ['error'],
|
|
47
48
|
} as const
|
|
48
49
|
|
|
50
|
+
const LOCAL_REF = '#/definitions/'
|
|
51
|
+
|
|
52
|
+
function rewriteRefs(node: unknown, names: ReadonlyMap<string, string>): unknown {
|
|
53
|
+
if (Array.isArray(node)) return node.map((n) => rewriteRefs(n, names))
|
|
54
|
+
if (node === null || typeof node !== 'object') return node
|
|
55
|
+
const out: Record<string, unknown> = {}
|
|
56
|
+
for (const [key, value] of Object.entries(node)) {
|
|
57
|
+
if (key === '$ref' && typeof value === 'string' && value.startsWith(LOCAL_REF)) {
|
|
58
|
+
const local = value.slice(LOCAL_REF.length)
|
|
59
|
+
out[key] = `#/components/schemas/${names.get(local) ?? local}`
|
|
60
|
+
} else {
|
|
61
|
+
out[key] = rewriteRefs(value, names)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Converts schemas for one document, hoisting zod's per-schema `definitions` into shared components. */
|
|
68
|
+
function createConverter(components: Record<string, unknown>): (schema: Parameters<typeof toOpenApiSchema>[0]) => Record<string, unknown> {
|
|
69
|
+
const seen = new Map<string, ReadonlyMap<string, string>>()
|
|
70
|
+
let counter = 0
|
|
71
|
+
|
|
72
|
+
const nameFor = (local: string, taken: Set<string>): string => {
|
|
73
|
+
if (/^__schema\d+$/.test(local)) {
|
|
74
|
+
let name: string
|
|
75
|
+
do name = `Schema${++counter}`; while (name in components || taken.has(name))
|
|
76
|
+
return name
|
|
77
|
+
}
|
|
78
|
+
let name = local
|
|
79
|
+
for (let n = 2; name in components || taken.has(name); n++) name = `${local}_${n}`
|
|
80
|
+
return name
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (schema) => {
|
|
84
|
+
const { definitions, ...rest } = toOpenApiSchema(schema) as Record<string, unknown>
|
|
85
|
+
delete rest['$schema']
|
|
86
|
+
if (definitions === null || typeof definitions !== 'object') return rest
|
|
87
|
+
const key = JSON.stringify(definitions)
|
|
88
|
+
let names = seen.get(key)
|
|
89
|
+
if (names === undefined) {
|
|
90
|
+
const map = new Map<string, string>()
|
|
91
|
+
const taken = new Set<string>()
|
|
92
|
+
for (const local of Object.keys(definitions)) {
|
|
93
|
+
const name = nameFor(local, taken)
|
|
94
|
+
taken.add(name)
|
|
95
|
+
map.set(local, name)
|
|
96
|
+
}
|
|
97
|
+
for (const [local, def] of Object.entries(definitions)) {
|
|
98
|
+
components[map.get(local)!] = rewriteRefs(def, map)
|
|
99
|
+
}
|
|
100
|
+
seen.set(key, map)
|
|
101
|
+
names = map
|
|
102
|
+
}
|
|
103
|
+
return rewriteRefs(rest, names) as Record<string, unknown>
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
49
107
|
export function generateOpenAPI(
|
|
50
108
|
info: OpenAPIInfo,
|
|
51
109
|
routes: readonly RouteRegistration[],
|
|
52
110
|
options?: { rateLimited?: boolean },
|
|
53
111
|
): OpenAPIDocument {
|
|
54
112
|
const paths: Record<string, Record<string, unknown>> = {}
|
|
113
|
+
const components: Record<string, unknown> = {}
|
|
114
|
+
const convert = createConverter(components)
|
|
55
115
|
|
|
56
116
|
for (const route of routes) {
|
|
57
117
|
const { method, path, handler } = route
|
|
@@ -70,7 +130,7 @@ export function generateOpenAPI(
|
|
|
70
130
|
const parameters: Record<string, unknown>[] = []
|
|
71
131
|
|
|
72
132
|
if (meta.paramsSchema) {
|
|
73
|
-
const schema =
|
|
133
|
+
const schema = convert(meta.paramsSchema)
|
|
74
134
|
if ('properties' in schema) {
|
|
75
135
|
const props = schema['properties'] as Record<string, unknown>
|
|
76
136
|
for (const [name, propSchema] of Object.entries(props)) {
|
|
@@ -85,7 +145,7 @@ export function generateOpenAPI(
|
|
|
85
145
|
}
|
|
86
146
|
|
|
87
147
|
if (meta.querySchema) {
|
|
88
|
-
const schema =
|
|
148
|
+
const schema = convert(meta.querySchema)
|
|
89
149
|
if ('properties' in schema) {
|
|
90
150
|
const props = schema['properties'] as Record<string, unknown>
|
|
91
151
|
const requiredFields = Array.isArray(schema['required']) ? schema['required'] as string[] : []
|
|
@@ -110,7 +170,7 @@ export function generateOpenAPI(
|
|
|
110
170
|
required: !meta.inputSchema.safeParse(undefined).success,
|
|
111
171
|
content: {
|
|
112
172
|
'application/json': {
|
|
113
|
-
schema:
|
|
173
|
+
schema: convert(meta.inputSchema),
|
|
114
174
|
},
|
|
115
175
|
},
|
|
116
176
|
}
|
|
@@ -145,7 +205,7 @@ export function generateOpenAPI(
|
|
|
145
205
|
description: status === 200 ? 'Successful response' : `${status} response`,
|
|
146
206
|
content: {
|
|
147
207
|
'application/json': {
|
|
148
|
-
schema:
|
|
208
|
+
schema: convert(outputSchema),
|
|
149
209
|
},
|
|
150
210
|
},
|
|
151
211
|
}
|
|
@@ -169,7 +229,7 @@ export function generateOpenAPI(
|
|
|
169
229
|
'application/json': {
|
|
170
230
|
schema: declared === undefined
|
|
171
231
|
? VALIDATION_ERROR_SCHEMA
|
|
172
|
-
: { oneOf: [
|
|
232
|
+
: { oneOf: [convert(declared), VALIDATION_ERROR_SCHEMA] },
|
|
173
233
|
},
|
|
174
234
|
},
|
|
175
235
|
}
|
|
@@ -183,7 +243,7 @@ export function generateOpenAPI(
|
|
|
183
243
|
'application/json': {
|
|
184
244
|
schema: declared === undefined
|
|
185
245
|
? TOO_MANY_REQUESTS_SCHEMA
|
|
186
|
-
: { oneOf: [
|
|
246
|
+
: { oneOf: [convert(declared), TOO_MANY_REQUESTS_SCHEMA] },
|
|
187
247
|
},
|
|
188
248
|
},
|
|
189
249
|
}
|
|
@@ -201,5 +261,7 @@ export function generateOpenAPI(
|
|
|
201
261
|
pathItem[method.toLowerCase()] = operation
|
|
202
262
|
}
|
|
203
263
|
|
|
204
|
-
return
|
|
264
|
+
return Object.keys(components).length === 0
|
|
265
|
+
? { openapi: '3.0.3', info, paths }
|
|
266
|
+
: { openapi: '3.0.3', info, paths, components: { schemas: components } }
|
|
205
267
|
}
|