@bram-dc/fastify-type-provider-zod 5.0.3 → 7.0.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/README.md +34 -4
- package/dist/cjs/core.cjs +33 -45
- package/dist/cjs/core.cjs.map +1 -1
- package/dist/cjs/core.d.cts +11 -12
- package/dist/cjs/errors.cjs +1 -1
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/registry.cjs +43 -0
- package/dist/cjs/registry.cjs.map +1 -0
- package/dist/cjs/registry.d.cts +9 -0
- package/dist/cjs/utils.cjs +21 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +12 -0
- package/dist/cjs/zod-to-json.cjs +36 -41
- package/dist/cjs/zod-to-json.cjs.map +1 -1
- package/dist/cjs/zod-to-json.d.cts +7 -7
- package/dist/esm/core.d.ts +11 -12
- package/dist/esm/core.js +34 -46
- package/dist/esm/core.js.map +1 -1
- package/dist/esm/errors.js +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/registry.d.ts +9 -0
- package/dist/esm/registry.js +43 -0
- package/dist/esm/registry.js.map +1 -0
- package/dist/esm/utils.d.ts +12 -0
- package/dist/esm/utils.js +21 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/zod-to-json.d.ts +7 -7
- package/dist/esm/zod-to-json.js +36 -41
- package/dist/esm/zod-to-json.js.map +1 -1
- package/package.json +13 -11
- package/src/core.ts +44 -60
- package/src/registry.ts +64 -0
- package/src/utils.ts +33 -0
- package/src/zod-to-json.ts +62 -52
- package/dist/cjs/json-to-oas.cjs +0 -85
- package/dist/cjs/json-to-oas.cjs.map +0 -1
- package/dist/cjs/json-to-oas.d.cts +0 -9
- package/dist/esm/json-to-oas.d.ts +0 -9
- package/dist/esm/json-to-oas.js +0 -85
- package/dist/esm/json-to-oas.js.map +0 -1
- package/src/json-to-oas.ts +0 -109
package/src/core.ts
CHANGED
|
@@ -5,16 +5,17 @@ import type {
|
|
|
5
5
|
FastifyPluginOptions,
|
|
6
6
|
FastifySchema,
|
|
7
7
|
FastifySchemaCompiler,
|
|
8
|
+
FastifySerializerCompiler,
|
|
8
9
|
FastifyTypeProvider,
|
|
9
10
|
RawServerBase,
|
|
10
11
|
RawServerDefault,
|
|
11
12
|
} from 'fastify'
|
|
12
|
-
import type {
|
|
13
|
-
import
|
|
14
|
-
import { $ZodType, globalRegistry, safeParse } from 'zod/v4/core'
|
|
13
|
+
import type { $ZodRegistry, output } from 'zod/v4/core'
|
|
14
|
+
import { $ZodType, globalRegistry, safeDecode, safeEncode } from 'zod/v4/core'
|
|
15
15
|
import { createValidationError, InvalidSchemaError, ResponseSerializationError } from './errors'
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { generateIORegistries, type SchemaRegistryMeta } from './registry'
|
|
17
|
+
import { assertIsOpenAPIObject, getJSONSchemaTarget } from './utils'
|
|
18
|
+
import { type ZodToJsonConfig, zodRegistryToJson, zodSchemaToJson } from './zod-to-json'
|
|
18
19
|
|
|
19
20
|
type FreeformRecord = Record<string, any>
|
|
20
21
|
|
|
@@ -30,7 +31,7 @@ const defaultSkipList = [
|
|
|
30
31
|
|
|
31
32
|
export interface ZodTypeProvider extends FastifyTypeProvider {
|
|
32
33
|
validator: this['schema'] extends $ZodType ? output<this['schema']> : unknown
|
|
33
|
-
serializer: this['schema'] extends $ZodType ?
|
|
34
|
+
serializer: this['schema'] extends $ZodType ? output<this['schema']> : unknown
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
interface Schema extends FastifySchema {
|
|
@@ -39,19 +40,19 @@ interface Schema extends FastifySchema {
|
|
|
39
40
|
|
|
40
41
|
type CreateJsonSchemaTransformOptions = {
|
|
41
42
|
skipList?: readonly string[]
|
|
42
|
-
schemaRegistry?: $ZodRegistry<
|
|
43
|
+
schemaRegistry?: $ZodRegistry<SchemaRegistryMeta>
|
|
44
|
+
zodToJsonConfig?: ZodToJsonConfig
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export const createJsonSchemaTransform = ({
|
|
46
48
|
skipList = defaultSkipList,
|
|
47
49
|
schemaRegistry = globalRegistry,
|
|
50
|
+
zodToJsonConfig = {},
|
|
48
51
|
}: CreateJsonSchemaTransformOptions): SwaggerTransform<Schema> => {
|
|
49
|
-
return (
|
|
50
|
-
|
|
51
|
-
throw new Error('OpenAPI 2.0 is not supported')
|
|
52
|
-
}
|
|
52
|
+
return (document) => {
|
|
53
|
+
assertIsOpenAPIObject(document)
|
|
53
54
|
|
|
54
|
-
const { schema, url } =
|
|
55
|
+
const { schema, url } = document
|
|
55
56
|
|
|
56
57
|
if (!schema) {
|
|
57
58
|
return {
|
|
@@ -60,6 +61,14 @@ export const createJsonSchemaTransform = ({
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
const target = getJSONSchemaTarget(document.openapiObject.openapi)
|
|
65
|
+
const config = {
|
|
66
|
+
target,
|
|
67
|
+
...zodToJsonConfig,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { inputRegistry, outputRegistry } = generateIORegistries(schemaRegistry)
|
|
71
|
+
|
|
63
72
|
const { response, headers, querystring, body, params, hide, ...rest } = schema
|
|
64
73
|
|
|
65
74
|
const transformed: FreeformRecord = {}
|
|
@@ -71,15 +80,10 @@ export const createJsonSchemaTransform = ({
|
|
|
71
80
|
|
|
72
81
|
const zodSchemas: FreeformRecord = { headers, querystring, body, params }
|
|
73
82
|
|
|
74
|
-
const oasVersion = getOASVersion(input)
|
|
75
|
-
|
|
76
83
|
for (const prop in zodSchemas) {
|
|
77
84
|
const zodSchema = zodSchemas[prop]
|
|
78
85
|
if (zodSchema) {
|
|
79
|
-
|
|
80
|
-
const oasSchema = jsonSchemaToOAS(jsonSchema, oasVersion)
|
|
81
|
-
|
|
82
|
-
transformed[prop] = oasSchema
|
|
86
|
+
transformed[prop] = zodSchemaToJson(zodSchema, inputRegistry, 'input', config)
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -88,17 +92,8 @@ export const createJsonSchemaTransform = ({
|
|
|
88
92
|
|
|
89
93
|
for (const prop in response as any) {
|
|
90
94
|
const zodSchema = resolveSchema((response as any)[prop])
|
|
91
|
-
const jsonSchema = zodSchemaToJson(zodSchema, schemaRegistry, 'output')
|
|
92
|
-
|
|
93
|
-
// Check is the JSON schema is null then return as it is since fastify-swagger will handle it
|
|
94
|
-
if (jsonSchema.type === 'null') {
|
|
95
|
-
transformed.response[prop] = jsonSchema
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
98
95
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
transformed.response[prop] = oasSchema
|
|
96
|
+
transformed.response[prop] = zodSchemaToJson(zodSchema, outputRegistry, 'output', config)
|
|
102
97
|
}
|
|
103
98
|
}
|
|
104
99
|
|
|
@@ -116,47 +111,36 @@ export const createJsonSchemaTransform = ({
|
|
|
116
111
|
export const jsonSchemaTransform: SwaggerTransform<Schema> = createJsonSchemaTransform({})
|
|
117
112
|
|
|
118
113
|
type CreateJsonSchemaTransformObjectOptions = {
|
|
119
|
-
schemaRegistry?: $ZodRegistry<
|
|
114
|
+
schemaRegistry?: $ZodRegistry<SchemaRegistryMeta>
|
|
115
|
+
zodToJsonConfig?: ZodToJsonConfig
|
|
120
116
|
}
|
|
121
117
|
|
|
122
118
|
export const createJsonSchemaTransformObject =
|
|
123
119
|
({
|
|
124
120
|
schemaRegistry = globalRegistry,
|
|
121
|
+
zodToJsonConfig = {},
|
|
125
122
|
}: CreateJsonSchemaTransformObjectOptions): SwaggerTransformObject =>
|
|
126
|
-
(
|
|
127
|
-
|
|
128
|
-
throw new Error('OpenAPI 2.0 is not supported')
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const oasVersion = getOASVersion(input)
|
|
132
|
-
|
|
133
|
-
const inputSchemas = zodRegistryToJson(schemaRegistry, 'input')
|
|
134
|
-
const outputSchemas = zodRegistryToJson(schemaRegistry, 'output')
|
|
135
|
-
|
|
136
|
-
for (const key in outputSchemas) {
|
|
137
|
-
if (inputSchemas[key]) {
|
|
138
|
-
throw new Error(
|
|
139
|
-
`Collision detected for schema "${key}". The is already an input schema with the same name.`,
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
123
|
+
(document) => {
|
|
124
|
+
assertIsOpenAPIObject(document)
|
|
143
125
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
126
|
+
const target = getJSONSchemaTarget(document.openapiObject.openapi)
|
|
127
|
+
const config = {
|
|
128
|
+
target,
|
|
129
|
+
...zodToJsonConfig,
|
|
147
130
|
}
|
|
148
131
|
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
)
|
|
132
|
+
const { inputRegistry, outputRegistry } = generateIORegistries(schemaRegistry)
|
|
133
|
+
const inputSchemas = zodRegistryToJson(inputRegistry, 'input', config)
|
|
134
|
+
const outputSchemas = zodRegistryToJson(outputRegistry, 'output', config)
|
|
152
135
|
|
|
153
136
|
return {
|
|
154
|
-
...
|
|
137
|
+
...document.openapiObject,
|
|
155
138
|
components: {
|
|
156
|
-
...
|
|
139
|
+
...document.openapiObject.components,
|
|
157
140
|
schemas: {
|
|
158
|
-
...
|
|
159
|
-
...
|
|
141
|
+
...document.openapiObject.components?.schemas,
|
|
142
|
+
...inputSchemas,
|
|
143
|
+
...outputSchemas,
|
|
160
144
|
},
|
|
161
145
|
},
|
|
162
146
|
} as ReturnType<SwaggerTransformObject>
|
|
@@ -167,7 +151,7 @@ export const jsonSchemaTransformObject: SwaggerTransformObject = createJsonSchem
|
|
|
167
151
|
export const validatorCompiler: FastifySchemaCompiler<$ZodType> =
|
|
168
152
|
({ schema }) =>
|
|
169
153
|
(data) => {
|
|
170
|
-
const result =
|
|
154
|
+
const result = safeDecode(schema, data)
|
|
171
155
|
if (result.error) {
|
|
172
156
|
return { error: createValidationError(result.error) as unknown as Error }
|
|
173
157
|
}
|
|
@@ -177,10 +161,10 @@ export const validatorCompiler: FastifySchemaCompiler<$ZodType> =
|
|
|
177
161
|
|
|
178
162
|
function resolveSchema(maybeSchema: $ZodType | { properties: $ZodType }): $ZodType {
|
|
179
163
|
if (maybeSchema instanceof $ZodType) {
|
|
180
|
-
return maybeSchema
|
|
164
|
+
return maybeSchema as $ZodType
|
|
181
165
|
}
|
|
182
166
|
if ('properties' in maybeSchema && maybeSchema.properties instanceof $ZodType) {
|
|
183
|
-
return maybeSchema.properties
|
|
167
|
+
return maybeSchema.properties as $ZodType
|
|
184
168
|
}
|
|
185
169
|
throw new InvalidSchemaError(JSON.stringify(maybeSchema))
|
|
186
170
|
}
|
|
@@ -199,7 +183,7 @@ export const createSerializerCompiler =
|
|
|
199
183
|
(data) => {
|
|
200
184
|
const schema = resolveSchema(maybeSchema)
|
|
201
185
|
|
|
202
|
-
const result =
|
|
186
|
+
const result = safeEncode(schema, data)
|
|
203
187
|
if (result.error) {
|
|
204
188
|
throw new ResponseSerializationError(method, url, { cause: result.error })
|
|
205
189
|
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { $ZodRegistry, type $ZodType } from 'zod/v4/core'
|
|
2
|
+
|
|
3
|
+
export type SchemaRegistryMeta = {
|
|
4
|
+
id?: string | undefined
|
|
5
|
+
[key: string]: unknown
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const getSchemaId = (id: string, io: 'input' | 'output'): string => {
|
|
9
|
+
return io === 'input' ? `${id}Input` : id
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A WeakMap that falls back to another WeakMap when a key is not found, this is to ensure nested metadata is properly resolved
|
|
13
|
+
class WeakMapWithFallback extends WeakMap<$ZodType, SchemaRegistryMeta> {
|
|
14
|
+
constructor(private fallback: WeakMap<$ZodType, SchemaRegistryMeta>) {
|
|
15
|
+
super()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(key: $ZodType): SchemaRegistryMeta | undefined {
|
|
19
|
+
return super.get(key) ?? this.fallback.get(key)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
has(key: $ZodType): boolean {
|
|
23
|
+
return super.has(key) || this.fallback.has(key)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const copyRegistry = (
|
|
28
|
+
inputRegistry: $ZodRegistry<SchemaRegistryMeta>,
|
|
29
|
+
idReplaceFn: (id: string) => string,
|
|
30
|
+
): $ZodRegistry<SchemaRegistryMeta> => {
|
|
31
|
+
const outputRegistry = new $ZodRegistry<SchemaRegistryMeta>()
|
|
32
|
+
|
|
33
|
+
outputRegistry._map = new WeakMapWithFallback(inputRegistry._map)
|
|
34
|
+
|
|
35
|
+
inputRegistry._idmap.forEach((schema, id) => {
|
|
36
|
+
outputRegistry.add(schema, {
|
|
37
|
+
...inputRegistry._map.get(schema),
|
|
38
|
+
id: idReplaceFn(id),
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return outputRegistry
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const generateIORegistries = (
|
|
46
|
+
baseRegistry: $ZodRegistry<SchemaRegistryMeta>,
|
|
47
|
+
): {
|
|
48
|
+
inputRegistry: $ZodRegistry<SchemaRegistryMeta>
|
|
49
|
+
outputRegistry: $ZodRegistry<SchemaRegistryMeta>
|
|
50
|
+
} => {
|
|
51
|
+
const inputRegistry = copyRegistry(baseRegistry, (id) => getSchemaId(id, 'input'))
|
|
52
|
+
const outputRegistry = copyRegistry(baseRegistry, (id) => getSchemaId(id, 'output'))
|
|
53
|
+
|
|
54
|
+
// Detect colliding schemas
|
|
55
|
+
inputRegistry._idmap.forEach((_, id) => {
|
|
56
|
+
if (outputRegistry._idmap.has(id)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Collision detected for schema "${id}". There is already an input schema with the same name.`,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return { inputRegistry, outputRegistry }
|
|
64
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
|
|
2
|
+
|
|
3
|
+
type SwaggerObject = {
|
|
4
|
+
swaggerObject: Partial<OpenAPIV2.Document>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type OpenAPIObject = {
|
|
8
|
+
openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const assertIsOpenAPIObject: (
|
|
12
|
+
obj: SwaggerObject | OpenAPIObject,
|
|
13
|
+
) => asserts obj is OpenAPIObject = (obj) => {
|
|
14
|
+
if ('swaggerObject' in obj) {
|
|
15
|
+
throw new Error('This package currently does not support component references for Swagger 2.0')
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type JSONSchemaTarget = 'draft-2020-12' | 'openapi-3.0'
|
|
20
|
+
|
|
21
|
+
export const getReferenceUri = (input: string): string => {
|
|
22
|
+
const id = input.replace(/^#\/(?:\$defs|definitions|components\/schemas)\//, '')
|
|
23
|
+
|
|
24
|
+
return `#/components/schemas/${id}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const getJSONSchemaTarget = (version = '3.0.0'): JSONSchemaTarget => {
|
|
28
|
+
if (version.startsWith('3.0')) {
|
|
29
|
+
return 'openapi-3.0'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return 'draft-2020-12'
|
|
33
|
+
}
|
package/src/zod-to-json.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
$ZodDate,
|
|
3
|
+
$ZodUndefined,
|
|
4
|
+
$ZodUnion,
|
|
5
|
+
JSONSchema,
|
|
6
|
+
RegistryToJSONSchemaParams,
|
|
7
|
+
} from 'zod/v4/core'
|
|
2
8
|
import { $ZodRegistry, $ZodType, toJSONSchema } from 'zod/v4/core'
|
|
9
|
+
import type { SchemaRegistryMeta } from './registry'
|
|
10
|
+
import { getReferenceUri } from './utils'
|
|
3
11
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const getReferenceUri = (id: string, io: 'input' | 'output') => {
|
|
9
|
-
return `#/components/schemas/${getSchemaId(id, io)}`
|
|
10
|
-
}
|
|
12
|
+
const SCHEMA_REGISTRY_ID_PLACEHOLDER = '__SCHEMA__ID__PLACEHOLDER__'
|
|
13
|
+
const SCHEMA_URI_PLACEHOLDER = '__SCHEMA__PLACEHOLDER__'
|
|
11
14
|
|
|
12
15
|
function isZodDate(entity: unknown): entity is $ZodDate {
|
|
13
16
|
return entity instanceof $ZodType && entity._zod.def.type === 'date'
|
|
@@ -51,23 +54,40 @@ const getOverride = (
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
export type ZodToJsonConfig = {} & Omit<
|
|
58
|
+
RegistryToJSONSchemaParams,
|
|
59
|
+
'io' | 'metadata' | 'cycles' | 'reused' | 'uri'
|
|
60
|
+
>
|
|
61
|
+
|
|
62
|
+
const deleteInvalidProperties: (
|
|
63
|
+
schema: JSONSchema.BaseSchema,
|
|
64
|
+
) => Omit<JSONSchema.BaseSchema, 'id' | '$schema'> = (schema) => {
|
|
65
|
+
const object = { ...schema }
|
|
66
|
+
|
|
67
|
+
delete object.id
|
|
68
|
+
delete object.$schema
|
|
69
|
+
|
|
70
|
+
// ToDo added in newer zod
|
|
71
|
+
delete object.$id
|
|
72
|
+
|
|
73
|
+
return object
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
export const zodSchemaToJson: (
|
|
55
77
|
zodSchema: $ZodType,
|
|
56
|
-
registry: $ZodRegistry<
|
|
78
|
+
registry: $ZodRegistry<SchemaRegistryMeta>,
|
|
57
79
|
io: 'input' | 'output',
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
80
|
+
config: ZodToJsonConfig,
|
|
81
|
+
) => ReturnType<typeof deleteInvalidProperties> = (zodSchema, registry, io, config) => {
|
|
61
82
|
/**
|
|
62
83
|
* Checks whether the provided schema is registered in the given registry.
|
|
63
84
|
* If it is present and has an `id`, it can be referenced as component.
|
|
64
85
|
*
|
|
65
86
|
* @see https://github.com/turkerdev/fastify-type-provider-zod/issues/173
|
|
66
87
|
*/
|
|
88
|
+
const schemaRegistryEntry = registry.get(zodSchema)
|
|
67
89
|
if (schemaRegistryEntry?.id) {
|
|
68
|
-
return {
|
|
69
|
-
$ref: getReferenceUri(schemaRegistryEntry.id, io),
|
|
70
|
-
}
|
|
90
|
+
return { $ref: getReferenceUri(schemaRegistryEntry.id) }
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
/**
|
|
@@ -78,72 +98,62 @@ export const zodSchemaToJson: (
|
|
|
78
98
|
*
|
|
79
99
|
* @see https://github.com/colinhacks/zod/issues/4281
|
|
80
100
|
*/
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
tempRegistry.add(zodSchema, { id: tempID })
|
|
101
|
+
const tempRegistry = new $ZodRegistry<SchemaRegistryMeta>()
|
|
102
|
+
tempRegistry.add(zodSchema, { id: SCHEMA_REGISTRY_ID_PLACEHOLDER })
|
|
84
103
|
|
|
85
104
|
const {
|
|
86
|
-
schemas: { [
|
|
105
|
+
schemas: { [SCHEMA_REGISTRY_ID_PLACEHOLDER]: result },
|
|
87
106
|
} = toJSONSchema(tempRegistry, {
|
|
88
|
-
|
|
89
|
-
metadata: registry,
|
|
107
|
+
...config,
|
|
90
108
|
io,
|
|
91
|
-
|
|
109
|
+
target: config.target,
|
|
110
|
+
metadata: registry,
|
|
111
|
+
unrepresentable: config.unrepresentable ?? 'any',
|
|
92
112
|
cycles: 'ref',
|
|
93
113
|
reused: 'inline',
|
|
94
|
-
|
|
95
114
|
/**
|
|
96
115
|
* The uri option only allows customizing the base path of the `$ref`, and it automatically appends a path to it.
|
|
97
|
-
* As a workaround, we set a placeholder that looks something like this
|
|
98
|
-
*
|
|
99
|
-
* | marker | always added by zod | meta.id |
|
|
100
|
-
* |__SCHEMA__PLACEHOLDER__| #/$defs/ | User |
|
|
101
|
-
*
|
|
102
|
-
* @example `__SCHEMA__PLACEHOLDER__#/$defs/User"`
|
|
103
|
-
* @example `__SCHEMA__PLACEHOLDER__#/$defs/Group"`
|
|
104
|
-
*
|
|
116
|
+
* As a workaround, we set a placeholder that looks something like this.
|
|
105
117
|
* @see jsonSchemaReplaceRef
|
|
106
118
|
* @see https://github.com/colinhacks/zod/issues/4750
|
|
107
119
|
*/
|
|
108
|
-
uri: () =>
|
|
109
|
-
override: (ctx) => getOverride(ctx, io),
|
|
120
|
+
uri: () => SCHEMA_URI_PLACEHOLDER,
|
|
121
|
+
override: config.override ?? ((ctx) => getOverride(ctx, io)),
|
|
110
122
|
})
|
|
111
123
|
|
|
112
|
-
const jsonSchema =
|
|
113
|
-
delete jsonSchema.id
|
|
124
|
+
const jsonSchema = deleteInvalidProperties(result)
|
|
114
125
|
|
|
115
126
|
/**
|
|
116
127
|
* Replace the previous generated placeholders with the final `$ref` value
|
|
117
128
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
return JSON.parse(JSON.stringify(jsonSchema), (__key, value) => {
|
|
130
|
+
if (typeof value === 'string' && value.startsWith(SCHEMA_URI_PLACEHOLDER)) {
|
|
131
|
+
return getReferenceUri(value.slice(SCHEMA_URI_PLACEHOLDER.length))
|
|
132
|
+
}
|
|
133
|
+
return value
|
|
134
|
+
}) as typeof result
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
export const zodRegistryToJson: (
|
|
127
|
-
registry: $ZodRegistry<
|
|
138
|
+
registry: $ZodRegistry<SchemaRegistryMeta>,
|
|
128
139
|
io: 'input' | 'output',
|
|
129
|
-
|
|
140
|
+
config: ZodToJsonConfig,
|
|
141
|
+
) => Record<string, JSONSchema.BaseSchema> = (registry, io, config) => {
|
|
130
142
|
const result = toJSONSchema(registry, {
|
|
131
|
-
|
|
143
|
+
...config,
|
|
132
144
|
io,
|
|
133
|
-
|
|
145
|
+
target: config.target,
|
|
146
|
+
metadata: registry,
|
|
147
|
+
unrepresentable: config.unrepresentable ?? 'any',
|
|
134
148
|
cycles: 'ref',
|
|
135
149
|
reused: 'inline',
|
|
136
|
-
uri: (id) => getReferenceUri(id
|
|
137
|
-
override: (ctx) => getOverride(ctx, io),
|
|
150
|
+
uri: (id) => getReferenceUri(id),
|
|
151
|
+
override: config.override ?? ((ctx) => getOverride(ctx, io)),
|
|
138
152
|
}).schemas
|
|
139
153
|
|
|
140
154
|
const jsonSchemas: Record<string, JSONSchema.BaseSchema> = {}
|
|
141
155
|
for (const id in result) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
delete jsonSchema.id
|
|
145
|
-
|
|
146
|
-
jsonSchemas[getSchemaId(id, io)] = jsonSchema
|
|
156
|
+
jsonSchemas[id] = deleteInvalidProperties(result[id])
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
return jsonSchemas
|
package/dist/cjs/json-to-oas.cjs
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const getOASVersion = (documentObject) => {
|
|
4
|
-
const openapiVersion = documentObject.openapiObject.openapi || "3.0.3";
|
|
5
|
-
if (openapiVersion.startsWith("3.1")) {
|
|
6
|
-
return "3.1";
|
|
7
|
-
}
|
|
8
|
-
if (openapiVersion.startsWith("3.0")) {
|
|
9
|
-
return "3.0";
|
|
10
|
-
}
|
|
11
|
-
throw new Error("Unsupported OpenAPI document object");
|
|
12
|
-
};
|
|
13
|
-
const jsonSchemaToOAS_3_0 = (jsonSchema) => {
|
|
14
|
-
const clone = { ...jsonSchema };
|
|
15
|
-
if (clone.type === "null") {
|
|
16
|
-
clone.nullable = true;
|
|
17
|
-
delete clone.type;
|
|
18
|
-
clone.enum = [null];
|
|
19
|
-
}
|
|
20
|
-
if (Array.isArray(clone.prefixItems)) {
|
|
21
|
-
const tuple = clone.prefixItems;
|
|
22
|
-
clone.minItems ??= tuple.length;
|
|
23
|
-
clone.maxItems ??= tuple.length;
|
|
24
|
-
clone.items = {
|
|
25
|
-
oneOf: tuple.map(jsonSchemaToOAS_3_0)
|
|
26
|
-
};
|
|
27
|
-
delete clone.prefixItems;
|
|
28
|
-
}
|
|
29
|
-
if ("const" in clone && clone.const !== void 0) {
|
|
30
|
-
clone.enum = [clone.const];
|
|
31
|
-
delete clone.const;
|
|
32
|
-
}
|
|
33
|
-
if (typeof clone.exclusiveMinimum === "number") {
|
|
34
|
-
clone.minimum = clone.exclusiveMinimum;
|
|
35
|
-
clone.exclusiveMinimum = true;
|
|
36
|
-
}
|
|
37
|
-
if (typeof clone.exclusiveMaximum === "number") {
|
|
38
|
-
clone.maximum = clone.exclusiveMaximum;
|
|
39
|
-
clone.exclusiveMaximum = true;
|
|
40
|
-
}
|
|
41
|
-
for (const key of [
|
|
42
|
-
"$schema",
|
|
43
|
-
"$id",
|
|
44
|
-
"unevaluatedProperties",
|
|
45
|
-
"dependentSchemas",
|
|
46
|
-
"patternProperties",
|
|
47
|
-
"propertyNames",
|
|
48
|
-
"contentEncoding",
|
|
49
|
-
"contentMediaType"
|
|
50
|
-
]) {
|
|
51
|
-
delete clone[key];
|
|
52
|
-
}
|
|
53
|
-
const recursive = (v) => Array.isArray(v) ? v.map(jsonSchemaToOAS_3_0) : jsonSchemaToOAS_3_0(v);
|
|
54
|
-
if (clone.properties) {
|
|
55
|
-
for (const [k, v] of Object.entries(clone.properties)) {
|
|
56
|
-
clone.properties[k] = jsonSchemaToOAS_3_0(v);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (clone.items && !Array.isArray(clone.items)) {
|
|
60
|
-
clone.items = recursive(clone.items);
|
|
61
|
-
}
|
|
62
|
-
for (const key of ["allOf", "anyOf", "oneOf", "not", "then", "else", "if", "contains"]) {
|
|
63
|
-
if (clone[key]) {
|
|
64
|
-
clone[key] = recursive(clone[key]);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return clone;
|
|
68
|
-
};
|
|
69
|
-
const jsonSchemaToOAS_3_1 = (jsonSchema) => {
|
|
70
|
-
return jsonSchema;
|
|
71
|
-
};
|
|
72
|
-
const jsonSchemaToOAS = (jsonSchema, oasVersion) => {
|
|
73
|
-
switch (oasVersion) {
|
|
74
|
-
case "3.0":
|
|
75
|
-
return jsonSchemaToOAS_3_0(jsonSchema);
|
|
76
|
-
case "3.1":
|
|
77
|
-
return jsonSchemaToOAS_3_1(jsonSchema);
|
|
78
|
-
default:
|
|
79
|
-
throw new Error(`Unsupported OpenAPI version: ${oasVersion}`);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
exports.getOASVersion = getOASVersion;
|
|
83
|
-
exports.jsonSchemaToOAS = jsonSchemaToOAS;
|
|
84
|
-
exports.jsonSchemaToOAS_3_0 = jsonSchemaToOAS_3_0;
|
|
85
|
-
//# sourceMappingURL=json-to-oas.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"json-to-oas.cjs","sources":["../../src/json-to-oas.ts"],"sourcesContent":["import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'\nimport type { JSONSchema } from 'zod/v4/core'\n\ntype OASVersion = '3.0' | '3.1'\n\nexport const getOASVersion = (documentObject: {\n openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>\n}): OASVersion => {\n const openapiVersion = documentObject.openapiObject.openapi || '3.0.3'\n\n if (openapiVersion.startsWith('3.1')) {\n return '3.1'\n }\n\n if (openapiVersion.startsWith('3.0')) {\n return '3.0'\n }\n\n throw new Error('Unsupported OpenAPI document object')\n}\n\nexport const jsonSchemaToOAS_3_0 = (jsonSchema: JSONSchema.BaseSchema): OpenAPIV3.SchemaObject => {\n const clone: any = { ...jsonSchema }\n\n if (clone.type === 'null') {\n clone.nullable = true\n delete clone.type\n clone.enum = [null]\n }\n\n if (Array.isArray(clone.prefixItems)) {\n const tuple = clone.prefixItems as JSONSchema.BaseSchema[]\n\n clone.minItems ??= tuple.length\n clone.maxItems ??= tuple.length\n\n clone.items = {\n oneOf: tuple.map(jsonSchemaToOAS_3_0),\n }\n\n delete clone.prefixItems\n }\n\n if ('const' in clone && clone.const !== undefined) {\n clone.enum = [clone.const]\n delete clone.const\n }\n\n if (typeof clone.exclusiveMinimum === 'number') {\n clone.minimum = clone.exclusiveMinimum\n clone.exclusiveMinimum = true\n }\n if (typeof clone.exclusiveMaximum === 'number') {\n clone.maximum = clone.exclusiveMaximum\n clone.exclusiveMaximum = true\n }\n\n for (const key of [\n '$schema',\n '$id',\n 'unevaluatedProperties',\n 'dependentSchemas',\n 'patternProperties',\n 'propertyNames',\n 'contentEncoding',\n 'contentMediaType',\n ]) {\n delete clone[key]\n }\n\n const recursive = (v: any): any =>\n Array.isArray(v) ? v.map(jsonSchemaToOAS_3_0) : jsonSchemaToOAS_3_0(v)\n\n if (clone.properties) {\n for (const [k, v] of Object.entries(clone.properties)) {\n clone.properties![k] = jsonSchemaToOAS_3_0(v as any)\n }\n }\n\n if (clone.items && !Array.isArray(clone.items)) {\n clone.items = recursive(clone.items)\n }\n\n for (const key of ['allOf', 'anyOf', 'oneOf', 'not', 'then', 'else', 'if', 'contains']) {\n if (clone[key]) {\n clone[key] = recursive(clone[key])\n }\n }\n\n return clone as OpenAPIV3.SchemaObject\n}\n\nconst jsonSchemaToOAS_3_1 = (jsonSchema: JSONSchema.BaseSchema): OpenAPIV3_1.SchemaObject => {\n return jsonSchema as OpenAPIV3_1.SchemaObject\n}\n\nexport const jsonSchemaToOAS = (\n jsonSchema: JSONSchema.BaseSchema,\n oasVersion: OASVersion,\n): OpenAPIV3.SchemaObject | OpenAPIV3_1.SchemaObject => {\n switch (oasVersion) {\n case '3.0':\n return jsonSchemaToOAS_3_0(jsonSchema)\n case '3.1':\n return jsonSchemaToOAS_3_1(jsonSchema)\n default:\n throw new Error(`Unsupported OpenAPI version: ${oasVersion}`)\n }\n}\n"],"names":[],"mappings":";;AAKO,MAAM,gBAAgB,CAAC,mBAEZ;AAChB,QAAM,iBAAiB,eAAe,cAAc,WAAW;AAE/D,MAAI,eAAe,WAAW,KAAK,GAAG;AACpC,WAAO;AAAA,EACT;AAEA,MAAI,eAAe,WAAW,KAAK,GAAG;AACpC,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,MAAM,qCAAqC;AACvD;AAEO,MAAM,sBAAsB,CAAC,eAA8D;AAChG,QAAM,QAAa,EAAE,GAAG,WAAA;AAExB,MAAI,MAAM,SAAS,QAAQ;AACzB,UAAM,WAAW;AACjB,WAAO,MAAM;AACb,UAAM,OAAO,CAAC,IAAI;AAAA,EACpB;AAEA,MAAI,MAAM,QAAQ,MAAM,WAAW,GAAG;AACpC,UAAM,QAAQ,MAAM;AAEpB,UAAM,aAAa,MAAM;AACzB,UAAM,aAAa,MAAM;AAEzB,UAAM,QAAQ;AAAA,MACZ,OAAO,MAAM,IAAI,mBAAmB;AAAA,IAAA;AAGtC,WAAO,MAAM;AAAA,EACf;AAEA,MAAI,WAAW,SAAS,MAAM,UAAU,QAAW;AACjD,UAAM,OAAO,CAAC,MAAM,KAAK;AACzB,WAAO,MAAM;AAAA,EACf;AAEA,MAAI,OAAO,MAAM,qBAAqB,UAAU;AAC9C,UAAM,UAAU,MAAM;AACtB,UAAM,mBAAmB;AAAA,EAC3B;AACA,MAAI,OAAO,MAAM,qBAAqB,UAAU;AAC9C,UAAM,UAAU,MAAM;AACtB,UAAM,mBAAmB;AAAA,EAC3B;AAEA,aAAW,OAAO;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,GACC;AACD,WAAO,MAAM,GAAG;AAAA,EAClB;AAEA,QAAM,YAAY,CAAC,MACjB,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,mBAAmB,IAAI,oBAAoB,CAAC;AAEvE,MAAI,MAAM,YAAY;AACpB,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,UAAU,GAAG;AACrD,YAAM,WAAY,CAAC,IAAI,oBAAoB,CAAQ;AAAA,IACrD;AAAA,EACF;AAEA,MAAI,MAAM,SAAS,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG;AAC9C,UAAM,QAAQ,UAAU,MAAM,KAAK;AAAA,EACrC;AAEA,aAAW,OAAO,CAAC,SAAS,SAAS,SAAS,OAAO,QAAQ,QAAQ,MAAM,UAAU,GAAG;AACtF,QAAI,MAAM,GAAG,GAAG;AACd,YAAM,GAAG,IAAI,UAAU,MAAM,GAAG,CAAC;AAAA,IACnC;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,sBAAsB,CAAC,eAAgE;AAC3F,SAAO;AACT;AAEO,MAAM,kBAAkB,CAC7B,YACA,eACsD;AACtD,UAAQ,YAAA;AAAA,IACN,KAAK;AACH,aAAO,oBAAoB,UAAU;AAAA,IACvC,KAAK;AACH,aAAO,oBAAoB,UAAU;AAAA,IACvC;AACE,YAAM,IAAI,MAAM,gCAAgC,UAAU,EAAE;AAAA,EAAA;AAElE;;;;"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { OpenAPIV3, OpenAPIV3_1 } from "openapi-types";
|
|
2
|
-
import type { JSONSchema } from "zod/v4/core";
|
|
3
|
-
type OASVersion = "3.0" | "3.1";
|
|
4
|
-
export declare const getOASVersion: (documentObject: {
|
|
5
|
-
openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>;
|
|
6
|
-
}) => OASVersion;
|
|
7
|
-
export declare const jsonSchemaToOAS_3_0: (jsonSchema: JSONSchema.BaseSchema) => OpenAPIV3.SchemaObject;
|
|
8
|
-
export declare const jsonSchemaToOAS: (jsonSchema: JSONSchema.BaseSchema, oasVersion: OASVersion) => OpenAPIV3.SchemaObject | OpenAPIV3_1.SchemaObject;
|
|
9
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { OpenAPIV3, OpenAPIV3_1 } from "openapi-types";
|
|
2
|
-
import type { JSONSchema } from "zod/v4/core";
|
|
3
|
-
type OASVersion = "3.0" | "3.1";
|
|
4
|
-
export declare const getOASVersion: (documentObject: {
|
|
5
|
-
openapiObject: Partial<OpenAPIV3.Document | OpenAPIV3_1.Document>;
|
|
6
|
-
}) => OASVersion;
|
|
7
|
-
export declare const jsonSchemaToOAS_3_0: (jsonSchema: JSONSchema.BaseSchema) => OpenAPIV3.SchemaObject;
|
|
8
|
-
export declare const jsonSchemaToOAS: (jsonSchema: JSONSchema.BaseSchema, oasVersion: OASVersion) => OpenAPIV3.SchemaObject | OpenAPIV3_1.SchemaObject;
|
|
9
|
-
export {};
|
package/dist/esm/json-to-oas.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
const getOASVersion = (documentObject) => {
|
|
2
|
-
const openapiVersion = documentObject.openapiObject.openapi || "3.0.3";
|
|
3
|
-
if (openapiVersion.startsWith("3.1")) {
|
|
4
|
-
return "3.1";
|
|
5
|
-
}
|
|
6
|
-
if (openapiVersion.startsWith("3.0")) {
|
|
7
|
-
return "3.0";
|
|
8
|
-
}
|
|
9
|
-
throw new Error("Unsupported OpenAPI document object");
|
|
10
|
-
};
|
|
11
|
-
const jsonSchemaToOAS_3_0 = (jsonSchema) => {
|
|
12
|
-
const clone = { ...jsonSchema };
|
|
13
|
-
if (clone.type === "null") {
|
|
14
|
-
clone.nullable = true;
|
|
15
|
-
delete clone.type;
|
|
16
|
-
clone.enum = [null];
|
|
17
|
-
}
|
|
18
|
-
if (Array.isArray(clone.prefixItems)) {
|
|
19
|
-
const tuple = clone.prefixItems;
|
|
20
|
-
clone.minItems ??= tuple.length;
|
|
21
|
-
clone.maxItems ??= tuple.length;
|
|
22
|
-
clone.items = {
|
|
23
|
-
oneOf: tuple.map(jsonSchemaToOAS_3_0)
|
|
24
|
-
};
|
|
25
|
-
delete clone.prefixItems;
|
|
26
|
-
}
|
|
27
|
-
if ("const" in clone && clone.const !== void 0) {
|
|
28
|
-
clone.enum = [clone.const];
|
|
29
|
-
delete clone.const;
|
|
30
|
-
}
|
|
31
|
-
if (typeof clone.exclusiveMinimum === "number") {
|
|
32
|
-
clone.minimum = clone.exclusiveMinimum;
|
|
33
|
-
clone.exclusiveMinimum = true;
|
|
34
|
-
}
|
|
35
|
-
if (typeof clone.exclusiveMaximum === "number") {
|
|
36
|
-
clone.maximum = clone.exclusiveMaximum;
|
|
37
|
-
clone.exclusiveMaximum = true;
|
|
38
|
-
}
|
|
39
|
-
for (const key of [
|
|
40
|
-
"$schema",
|
|
41
|
-
"$id",
|
|
42
|
-
"unevaluatedProperties",
|
|
43
|
-
"dependentSchemas",
|
|
44
|
-
"patternProperties",
|
|
45
|
-
"propertyNames",
|
|
46
|
-
"contentEncoding",
|
|
47
|
-
"contentMediaType"
|
|
48
|
-
]) {
|
|
49
|
-
delete clone[key];
|
|
50
|
-
}
|
|
51
|
-
const recursive = (v) => Array.isArray(v) ? v.map(jsonSchemaToOAS_3_0) : jsonSchemaToOAS_3_0(v);
|
|
52
|
-
if (clone.properties) {
|
|
53
|
-
for (const [k, v] of Object.entries(clone.properties)) {
|
|
54
|
-
clone.properties[k] = jsonSchemaToOAS_3_0(v);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (clone.items && !Array.isArray(clone.items)) {
|
|
58
|
-
clone.items = recursive(clone.items);
|
|
59
|
-
}
|
|
60
|
-
for (const key of ["allOf", "anyOf", "oneOf", "not", "then", "else", "if", "contains"]) {
|
|
61
|
-
if (clone[key]) {
|
|
62
|
-
clone[key] = recursive(clone[key]);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return clone;
|
|
66
|
-
};
|
|
67
|
-
const jsonSchemaToOAS_3_1 = (jsonSchema) => {
|
|
68
|
-
return jsonSchema;
|
|
69
|
-
};
|
|
70
|
-
const jsonSchemaToOAS = (jsonSchema, oasVersion) => {
|
|
71
|
-
switch (oasVersion) {
|
|
72
|
-
case "3.0":
|
|
73
|
-
return jsonSchemaToOAS_3_0(jsonSchema);
|
|
74
|
-
case "3.1":
|
|
75
|
-
return jsonSchemaToOAS_3_1(jsonSchema);
|
|
76
|
-
default:
|
|
77
|
-
throw new Error(`Unsupported OpenAPI version: ${oasVersion}`);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
export {
|
|
81
|
-
getOASVersion,
|
|
82
|
-
jsonSchemaToOAS,
|
|
83
|
-
jsonSchemaToOAS_3_0
|
|
84
|
-
};
|
|
85
|
-
//# sourceMappingURL=json-to-oas.js.map
|