@kubb/plugin-zod 0.0.0-canary-20241104172400
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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/chunk-37OP7TSO.cjs +354 -0
- package/dist/chunk-37OP7TSO.cjs.map +1 -0
- package/dist/chunk-OOAUU32I.js +235 -0
- package/dist/chunk-OOAUU32I.js.map +1 -0
- package/dist/chunk-QEYWJ6VH.cjs +244 -0
- package/dist/chunk-QEYWJ6VH.cjs.map +1 -0
- package/dist/chunk-SSOO3TXR.js +347 -0
- package/dist/chunk-SSOO3TXR.js.map +1 -0
- package/dist/components.cjs +16 -0
- package/dist/components.cjs.map +1 -0
- package/dist/components.d.cts +29 -0
- package/dist/components.d.ts +29 -0
- package/dist/components.js +3 -0
- package/dist/components.js.map +1 -0
- package/dist/generators.cjs +17 -0
- package/dist/generators.cjs.map +1 -0
- package/dist/generators.d.cts +10 -0
- package/dist/generators.d.ts +10 -0
- package/dist/generators.js +4 -0
- package/dist/generators.js.map +1 -0
- package/dist/index.cjs +17 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types-L1fXCZAs.d.cts +100 -0
- package/dist/types-L1fXCZAs.d.ts +100 -0
- package/package.json +95 -0
- package/src/components/Operations.tsx +49 -0
- package/src/components/Zod.tsx +68 -0
- package/src/components/index.ts +2 -0
- package/src/generators/__snapshots__/anyof.ts +3 -0
- package/src/generators/__snapshots__/coercion.ts +3 -0
- package/src/generators/__snapshots__/coercionDates.ts +3 -0
- package/src/generators/__snapshots__/coercionNumbers.ts +3 -0
- package/src/generators/__snapshots__/coercionStrings.ts +3 -0
- package/src/generators/__snapshots__/createPet.ts +15 -0
- package/src/generators/__snapshots__/createPetWithUnknownTypeAny.ts +13 -0
- package/src/generators/__snapshots__/createPetWithUnknownTypeUnknown.ts +15 -0
- package/src/generators/__snapshots__/deletePet.ts +3 -0
- package/src/generators/__snapshots__/discriminator.ts +3 -0
- package/src/generators/__snapshots__/enumBooleanLiteral.ts +3 -0
- package/src/generators/__snapshots__/enumNamesType.ts +3 -0
- package/src/generators/__snapshots__/enumNullable.ts +3 -0
- package/src/generators/__snapshots__/enumSingleLiteral.ts +3 -0
- package/src/generators/__snapshots__/enumVarNamesType.ts +3 -0
- package/src/generators/__snapshots__/example.ts +3 -0
- package/src/generators/__snapshots__/getPets.ts +15 -0
- package/src/generators/__snapshots__/mixedValueTypeConst.ts +6 -0
- package/src/generators/__snapshots__/nullableString.ts +3 -0
- package/src/generators/__snapshots__/nullableStringUuid.ts +3 -0
- package/src/generators/__snapshots__/nullableStringWithAnyOf.ts +3 -0
- package/src/generators/__snapshots__/numberValueConst.ts +6 -0
- package/src/generators/__snapshots__/oneof.ts +3 -0
- package/src/generators/__snapshots__/operations.ts +43 -0
- package/src/generators/__snapshots__/optionalPetInfer.ts +5 -0
- package/src/generators/__snapshots__/optionalPetTyped.ts +3 -0
- package/src/generators/__snapshots__/order.ts +3 -0
- package/src/generators/__snapshots__/orderDateTyeString.ts +10 -0
- package/src/generators/__snapshots__/orderDateTypeFalse.ts +3 -0
- package/src/generators/__snapshots__/orderDateTypeString.ts +3 -0
- package/src/generators/__snapshots__/pet.ts +3 -0
- package/src/generators/__snapshots__/petArray.ts +6 -0
- package/src/generators/__snapshots__/petCoercion.ts +3 -0
- package/src/generators/__snapshots__/petTupleObject.ts +6 -0
- package/src/generators/__snapshots__/petWithMapper.ts +3 -0
- package/src/generators/__snapshots__/pets.ts +3 -0
- package/src/generators/__snapshots__/recursive.ts +3 -0
- package/src/generators/__snapshots__/showPetById.ts +15 -0
- package/src/generators/__snapshots__/stringValueConst.ts +6 -0
- package/src/generators/__snapshots__/uuidSchema.ts +3 -0
- package/src/generators/index.ts +2 -0
- package/src/generators/operationsGenerator.tsx +39 -0
- package/src/generators/zodGenerator.tsx +121 -0
- package/src/index.ts +2 -0
- package/src/parser/index.ts +381 -0
- package/src/plugin.ts +139 -0
- package/src/types.ts +106 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const order = z.object({ "id": z.number().int().optional(), "petId": z.number().int().optional(), "quantity": z.number().int().optional(), "shipDate": z.string().optional(), "status": z.enum(["placed", "approved", "delivered"]).describe("Order Status").optional(), "complete": z.boolean().optional() });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const order = z.object({ "id": z.number().int().optional(), "petId": z.number().int().optional(), "quantity": z.number().int().optional(), "shipDate": z.string().datetime().optional(), "status": z.enum(["placed", "approved", "delivered"]).describe("Order Status").optional(), "complete": z.boolean().optional() });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const showPetByIdPathParams = z.object({ "petId": z.string().describe("The id of the pet to retrieve"), "testId": z.string().describe("The id of the pet to retrieve") });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @description Expected response to a valid request
|
|
7
|
+
*/
|
|
8
|
+
export const showPetById200 = z.lazy(() => pet);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @description unexpected error
|
|
12
|
+
*/
|
|
13
|
+
export const showPetByIdError = z.lazy(() => error);
|
|
14
|
+
|
|
15
|
+
export const showPetByIdQueryResponse = z.lazy(() => showPetById200);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createReactGenerator } from '@kubb/plugin-oas'
|
|
2
|
+
import { useOperationManager } from '@kubb/plugin-oas/hooks'
|
|
3
|
+
import { File, useApp } from '@kubb/react'
|
|
4
|
+
import { Operations } from '../components/Operations'
|
|
5
|
+
import { pluginZodName } from '../plugin.ts'
|
|
6
|
+
import type { PluginZod } from '../types'
|
|
7
|
+
|
|
8
|
+
export const operationsGenerator = createReactGenerator<PluginZod>({
|
|
9
|
+
name: 'operations',
|
|
10
|
+
Operations({ operations }) {
|
|
11
|
+
const {
|
|
12
|
+
pluginManager,
|
|
13
|
+
plugin: {
|
|
14
|
+
options: { output },
|
|
15
|
+
},
|
|
16
|
+
} = useApp<PluginZod>()
|
|
17
|
+
const { getFile, groupSchemasByName } = useOperationManager()
|
|
18
|
+
|
|
19
|
+
const name = 'operations'
|
|
20
|
+
const file = pluginManager.getFile({ name, extname: '.ts', pluginKey: [pluginZodName] })
|
|
21
|
+
|
|
22
|
+
const transformedOperations = operations.map((operation) => ({ operation, data: groupSchemasByName(operation, { type: 'function' }) }))
|
|
23
|
+
|
|
24
|
+
const imports = Object.entries(transformedOperations)
|
|
25
|
+
.map(([key, { data, operation }]) => {
|
|
26
|
+
const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean)
|
|
27
|
+
|
|
28
|
+
return <File.Import key={key} name={names} root={file.path} path={getFile(operation).path} />
|
|
29
|
+
})
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<File baseName={file.baseName} path={file.path} meta={file.meta} banner={output?.banner} footer={output?.footer}>
|
|
34
|
+
{imports}
|
|
35
|
+
<Operations name={name} operations={transformedOperations} />
|
|
36
|
+
</File>
|
|
37
|
+
)
|
|
38
|
+
},
|
|
39
|
+
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { type OperationSchema as OperationSchemaType, SchemaGenerator, createReactGenerator, schemaKeywords } from '@kubb/plugin-oas'
|
|
2
|
+
import { Oas } from '@kubb/plugin-oas/components'
|
|
3
|
+
import { useOas, useOperationManager, useSchemaManager } from '@kubb/plugin-oas/hooks'
|
|
4
|
+
import { pluginTsName } from '@kubb/plugin-ts'
|
|
5
|
+
import { File, useApp } from '@kubb/react'
|
|
6
|
+
import { Zod } from '../components'
|
|
7
|
+
import type { PluginZod } from '../types'
|
|
8
|
+
|
|
9
|
+
export const zodGenerator = createReactGenerator<PluginZod>({
|
|
10
|
+
name: 'zod',
|
|
11
|
+
Operation({ operation, options }) {
|
|
12
|
+
const { coercion, inferred, typed, mapper } = options
|
|
13
|
+
|
|
14
|
+
const { plugin, pluginManager, mode } = useApp<PluginZod>()
|
|
15
|
+
const oas = useOas()
|
|
16
|
+
const { getSchemas, getFile } = useOperationManager()
|
|
17
|
+
const schemaManager = useSchemaManager()
|
|
18
|
+
|
|
19
|
+
const file = getFile(operation)
|
|
20
|
+
const schemas = getSchemas(operation)
|
|
21
|
+
const schemaGenerator = new SchemaGenerator(options, {
|
|
22
|
+
oas,
|
|
23
|
+
plugin,
|
|
24
|
+
pluginManager,
|
|
25
|
+
mode,
|
|
26
|
+
override: options.override,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const operationSchemas = [schemas.pathParams, schemas.queryParams, schemas.headerParams, schemas.statusCodes, schemas.request, schemas.response]
|
|
30
|
+
.flat()
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
|
|
33
|
+
const mapOperationSchema = ({ name, schema, description, keysToOmit, ...options }: OperationSchemaType, i: number) => {
|
|
34
|
+
// hack so Params can be optional when needed
|
|
35
|
+
const required = Array.isArray(schema?.required) ? !!schema.required.length : !!schema?.required
|
|
36
|
+
const optional = !required && !!name.includes('Params')
|
|
37
|
+
const tree = [...schemaGenerator.parse({ schema, name }), optional ? { keyword: schemaKeywords.optional } : undefined].filter(Boolean)
|
|
38
|
+
const imports = schemaManager.getImports(tree)
|
|
39
|
+
|
|
40
|
+
const zod = {
|
|
41
|
+
name: schemaManager.getName(name, { type: 'function' }),
|
|
42
|
+
inferTypeName: schemaManager.getName(name, { type: 'type' }),
|
|
43
|
+
file: schemaManager.getFile(name),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const type = {
|
|
47
|
+
name: schemaManager.getName(name, { type: 'type', pluginKey: [pluginTsName] }),
|
|
48
|
+
file: schemaManager.getFile(options.operationName || name, { pluginKey: [pluginTsName], tag: options.operation?.getTags()[0]?.name }),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Oas.Schema key={i} name={name} value={schema} tree={tree}>
|
|
53
|
+
{typed && <File.Import isTypeOnly root={file.path} path={type.file.path} name={[type.name]} />}
|
|
54
|
+
{imports.map((imp, index) => (
|
|
55
|
+
<File.Import key={index} root={file.path} path={imp.path} name={imp.name} />
|
|
56
|
+
))}
|
|
57
|
+
<Zod
|
|
58
|
+
name={zod.name}
|
|
59
|
+
typeName={typed ? type.name : undefined}
|
|
60
|
+
inferTypeName={inferred ? zod.inferTypeName : undefined}
|
|
61
|
+
description={description}
|
|
62
|
+
tree={tree}
|
|
63
|
+
mapper={mapper}
|
|
64
|
+
coercion={coercion}
|
|
65
|
+
keysToOmit={keysToOmit}
|
|
66
|
+
/>
|
|
67
|
+
</Oas.Schema>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<File baseName={file.baseName} path={file.path} meta={file.meta} banner={plugin.options.output?.banner} footer={plugin.options.output?.footer}>
|
|
73
|
+
<File.Import name={['z']} path={plugin.options.importPath} />
|
|
74
|
+
{operationSchemas.map(mapOperationSchema)}
|
|
75
|
+
</File>
|
|
76
|
+
)
|
|
77
|
+
},
|
|
78
|
+
Schema({ schema, options }) {
|
|
79
|
+
const { coercion, inferred, typed, mapper, importPath } = options
|
|
80
|
+
|
|
81
|
+
const { getName, getFile, getImports } = useSchemaManager()
|
|
82
|
+
const {
|
|
83
|
+
plugin: {
|
|
84
|
+
options: { output },
|
|
85
|
+
},
|
|
86
|
+
} = useApp<PluginZod>()
|
|
87
|
+
|
|
88
|
+
const imports = getImports(schema.tree)
|
|
89
|
+
|
|
90
|
+
const zod = {
|
|
91
|
+
name: getName(schema.name, { type: 'function' }),
|
|
92
|
+
inferTypeName: getName(schema.name, { type: 'type' }),
|
|
93
|
+
file: getFile(schema.name),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const type = {
|
|
97
|
+
name: getName(schema.name, { type: 'type', pluginKey: [pluginTsName] }),
|
|
98
|
+
file: getFile(schema.name, { pluginKey: [pluginTsName] }),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<File baseName={zod.file.baseName} path={zod.file.path} meta={zod.file.meta} banner={output?.banner} footer={output?.footer}>
|
|
103
|
+
<File.Import name={['z']} path={importPath} />
|
|
104
|
+
{typed && <File.Import isTypeOnly root={zod.file.path} path={type.file.path} name={[type.name]} />}
|
|
105
|
+
{imports.map((imp, index) => (
|
|
106
|
+
<File.Import key={index} root={zod.file.path} path={imp.path} name={imp.name} />
|
|
107
|
+
))}
|
|
108
|
+
|
|
109
|
+
<Zod
|
|
110
|
+
name={zod.name}
|
|
111
|
+
typeName={typed ? type.name : undefined}
|
|
112
|
+
inferTypeName={inferred ? zod.inferTypeName : undefined}
|
|
113
|
+
description={schema.value.description}
|
|
114
|
+
tree={schema.tree}
|
|
115
|
+
mapper={mapper}
|
|
116
|
+
coercion={coercion}
|
|
117
|
+
/>
|
|
118
|
+
</File>
|
|
119
|
+
)
|
|
120
|
+
},
|
|
121
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import transformers, { createJSDocBlockText } from '@kubb/core/transformers'
|
|
2
|
+
import { type SchemaKeywordMapper, type SchemaTree, isKeyword, schemaKeywords } from '@kubb/plugin-oas'
|
|
3
|
+
|
|
4
|
+
import type { Schema, SchemaKeywordBase, SchemaMapper } from '@kubb/plugin-oas'
|
|
5
|
+
|
|
6
|
+
const zodKeywordMapper = {
|
|
7
|
+
any: () => 'z.any()',
|
|
8
|
+
unknown: () => 'z.unknown()',
|
|
9
|
+
number: (coercion?: boolean, min?: number, max?: number) => {
|
|
10
|
+
return [coercion ? 'z.coerce.number()' : 'z.number()', min !== undefined ? `.min(${min})` : undefined, max !== undefined ? `.max(${max})` : undefined]
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.join('')
|
|
13
|
+
},
|
|
14
|
+
integer: (coercion?: boolean, min?: number, max?: number) => {
|
|
15
|
+
return [
|
|
16
|
+
coercion ? 'z.coerce.number().int()' : 'z.number().int()',
|
|
17
|
+
min !== undefined ? `.min(${min})` : undefined,
|
|
18
|
+
max !== undefined ? `.max(${max})` : undefined,
|
|
19
|
+
]
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.join('')
|
|
22
|
+
},
|
|
23
|
+
object: (value?: string) => `z.object({${value}})`,
|
|
24
|
+
string: (coercion?: boolean, min?: number, max?: number) => {
|
|
25
|
+
return [coercion ? 'z.coerce.string()' : 'z.string()', min !== undefined ? `.min(${min})` : undefined, max !== undefined ? `.max(${max})` : undefined]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join('')
|
|
28
|
+
},
|
|
29
|
+
boolean: () => 'z.boolean()',
|
|
30
|
+
undefined: () => 'z.undefined()',
|
|
31
|
+
nullable: () => '.nullable()',
|
|
32
|
+
null: () => 'z.null()',
|
|
33
|
+
nullish: () => '.nullish()',
|
|
34
|
+
array: (items: string[] = [], min?: number, max?: number) => {
|
|
35
|
+
return [`z.array(${items?.join('')})`, min !== undefined ? `.min(${min})` : undefined, max !== undefined ? `.max(${max})` : undefined]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join('')
|
|
38
|
+
},
|
|
39
|
+
tuple: (items: string[] = []) => `z.tuple([${items?.join(', ')}])`,
|
|
40
|
+
enum: (items: string[] = []) => `z.enum([${items?.join(', ')}])`,
|
|
41
|
+
union: (items: string[] = []) => `z.union([${items?.join(', ')}])`,
|
|
42
|
+
const: (value?: string | number | boolean) => `z.literal(${value ?? ''})`,
|
|
43
|
+
/**
|
|
44
|
+
* ISO 8601
|
|
45
|
+
*/
|
|
46
|
+
datetime: (offset = false, local = false) => {
|
|
47
|
+
if (offset) {
|
|
48
|
+
return `z.string().datetime({ offset: ${offset} })`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (local) {
|
|
52
|
+
return `z.string().datetime({ local: ${local} })`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return 'z.string().datetime()'
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Type `'date'` Date
|
|
59
|
+
* Type `'string'` ISO date format (YYYY-MM-DD)
|
|
60
|
+
* @default ISO date format (YYYY-MM-DD)
|
|
61
|
+
*/
|
|
62
|
+
date: (type: 'date' | 'string' = 'string', coercion?: boolean) => {
|
|
63
|
+
if (type === 'string') {
|
|
64
|
+
return 'z.string().date()'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (coercion) {
|
|
68
|
+
return 'z.coerce.date()'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return 'z.date()'
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Type `'date'` Date
|
|
75
|
+
* Type `'string'` ISO time format (HH:mm:ss[.SSSSSS])
|
|
76
|
+
* @default ISO time format (HH:mm:ss[.SSSSSS])
|
|
77
|
+
*/
|
|
78
|
+
time: (type: 'date' | 'string' = 'string', coercion?: boolean) => {
|
|
79
|
+
if (type === 'string') {
|
|
80
|
+
return 'z.string().time()'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (coercion) {
|
|
84
|
+
return 'z.coerce.date()'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return 'z.date()'
|
|
88
|
+
},
|
|
89
|
+
uuid: () => '.uuid()',
|
|
90
|
+
url: () => '.url()',
|
|
91
|
+
strict: () => '.strict()',
|
|
92
|
+
default: (value?: string | number | true) => `.default(${value ?? ''})`,
|
|
93
|
+
and: (items: string[] = []) => items?.map((item) => `.and(${item})`).join(''),
|
|
94
|
+
describe: (value = '') => `.describe(${value})`,
|
|
95
|
+
min: (value?: number) => `.min(${value ?? ''})`,
|
|
96
|
+
max: (value?: number) => `.max(${value ?? ''})`,
|
|
97
|
+
optional: () => '.optional()',
|
|
98
|
+
matches: (value = '') => `.regex(${value})`,
|
|
99
|
+
email: () => '.email()',
|
|
100
|
+
firstName: undefined,
|
|
101
|
+
lastName: undefined,
|
|
102
|
+
password: undefined,
|
|
103
|
+
phone: undefined,
|
|
104
|
+
readOnly: undefined,
|
|
105
|
+
writeOnly: undefined,
|
|
106
|
+
ref: (value?: string) => (value ? `z.lazy(() => ${value})` : undefined),
|
|
107
|
+
blob: () => 'z.string()',
|
|
108
|
+
deprecated: undefined,
|
|
109
|
+
example: undefined,
|
|
110
|
+
schema: undefined,
|
|
111
|
+
catchall: (value?: string) => (value ? `.catchall(${value})` : undefined),
|
|
112
|
+
name: undefined,
|
|
113
|
+
} satisfies SchemaMapper<string | null | undefined>
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @link based on https://github.com/cellular/oazapfts/blob/7ba226ebb15374e8483cc53e7532f1663179a22c/src/codegen/generate.ts#L398
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
export function sort(items?: Schema[]): Schema[] {
|
|
120
|
+
const order: string[] = [
|
|
121
|
+
schemaKeywords.string,
|
|
122
|
+
schemaKeywords.datetime,
|
|
123
|
+
schemaKeywords.date,
|
|
124
|
+
schemaKeywords.time,
|
|
125
|
+
schemaKeywords.tuple,
|
|
126
|
+
schemaKeywords.number,
|
|
127
|
+
schemaKeywords.object,
|
|
128
|
+
schemaKeywords.enum,
|
|
129
|
+
schemaKeywords.url,
|
|
130
|
+
schemaKeywords.email,
|
|
131
|
+
schemaKeywords.firstName,
|
|
132
|
+
schemaKeywords.lastName,
|
|
133
|
+
schemaKeywords.password,
|
|
134
|
+
schemaKeywords.matches,
|
|
135
|
+
schemaKeywords.uuid,
|
|
136
|
+
schemaKeywords.min,
|
|
137
|
+
schemaKeywords.max,
|
|
138
|
+
schemaKeywords.default,
|
|
139
|
+
schemaKeywords.describe,
|
|
140
|
+
schemaKeywords.optional,
|
|
141
|
+
schemaKeywords.nullable,
|
|
142
|
+
schemaKeywords.nullish,
|
|
143
|
+
schemaKeywords.null,
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
if (!items) {
|
|
147
|
+
return []
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return transformers.orderBy(items, [(v) => order.indexOf(v.keyword)], ['asc'])
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const shouldCoerce = (coercion: ParserOptions['coercion'] | undefined, type: 'dates' | 'strings' | 'numbers'): boolean => {
|
|
154
|
+
if (coercion === undefined) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
if (typeof coercion === 'boolean') {
|
|
158
|
+
return coercion
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return !!coercion[type]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
type ParserOptions = {
|
|
165
|
+
name: string
|
|
166
|
+
typeName?: string
|
|
167
|
+
description?: string
|
|
168
|
+
keysToOmit?: string[]
|
|
169
|
+
mapper?: Record<string, string>
|
|
170
|
+
coercion?: boolean | { dates?: boolean; strings?: boolean; numbers?: boolean }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function parse({ parent, current, siblings }: SchemaTree, options: ParserOptions): string | undefined {
|
|
174
|
+
const value = zodKeywordMapper[current.keyword as keyof typeof zodKeywordMapper]
|
|
175
|
+
|
|
176
|
+
if (!value) {
|
|
177
|
+
return undefined
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (isKeyword(current, schemaKeywords.union)) {
|
|
181
|
+
// zod union type needs at least 2 items
|
|
182
|
+
if (Array.isArray(current.args) && current.args.length === 1) {
|
|
183
|
+
return parse({ parent, current: current.args[0] as Schema, siblings }, options)
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(current.args) && !current.args.length) {
|
|
186
|
+
return ''
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return zodKeywordMapper.union(
|
|
190
|
+
sort(current.args)
|
|
191
|
+
.map((schema, _index, siblings) => parse({ parent: current, current: schema, siblings }, options))
|
|
192
|
+
.filter(Boolean),
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (isKeyword(current, schemaKeywords.and)) {
|
|
197
|
+
const items = sort(current.args)
|
|
198
|
+
.filter((schema: Schema) => {
|
|
199
|
+
return ![schemaKeywords.optional, schemaKeywords.describe].includes(schema.keyword as typeof schemaKeywords.describe)
|
|
200
|
+
})
|
|
201
|
+
.map((schema: Schema, _index, siblings) => parse({ parent: current, current: schema, siblings }, options))
|
|
202
|
+
.filter(Boolean)
|
|
203
|
+
|
|
204
|
+
return `${items.slice(0, 1)}${zodKeywordMapper.and(items.slice(1))}`
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isKeyword(current, schemaKeywords.array)) {
|
|
208
|
+
return zodKeywordMapper.array(
|
|
209
|
+
sort(current.args.items)
|
|
210
|
+
.map((schemas, _index, siblings) => parse({ parent: current, current: schemas, siblings }, options))
|
|
211
|
+
.filter(Boolean),
|
|
212
|
+
current.args.min,
|
|
213
|
+
current.args.max,
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (isKeyword(current, schemaKeywords.enum)) {
|
|
218
|
+
if (current.args.asConst) {
|
|
219
|
+
if (current.args.items.length === 1) {
|
|
220
|
+
const child = {
|
|
221
|
+
keyword: schemaKeywords.const,
|
|
222
|
+
args: current.args.items[0],
|
|
223
|
+
}
|
|
224
|
+
return parse({ parent: current, current: child, siblings: [child] }, options)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return zodKeywordMapper.union(
|
|
228
|
+
current.args.items
|
|
229
|
+
.map((schema) => ({
|
|
230
|
+
keyword: schemaKeywords.const,
|
|
231
|
+
args: schema,
|
|
232
|
+
}))
|
|
233
|
+
.map((schema, _index, siblings) => {
|
|
234
|
+
return parse({ parent: current, current: schema, siblings }, options)
|
|
235
|
+
})
|
|
236
|
+
.filter(Boolean),
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return zodKeywordMapper.enum(
|
|
241
|
+
current.args.items.map((schema) => {
|
|
242
|
+
if (schema.format === 'boolean') {
|
|
243
|
+
return transformers.stringify(schema.value)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (schema.format === 'number') {
|
|
247
|
+
return transformers.stringify(schema.value)
|
|
248
|
+
}
|
|
249
|
+
return transformers.stringify(schema.value)
|
|
250
|
+
}),
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (isKeyword(current, schemaKeywords.ref)) {
|
|
255
|
+
return zodKeywordMapper.ref(current.args?.name)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (isKeyword(current, schemaKeywords.object)) {
|
|
259
|
+
const properties = Object.entries(current.args?.properties || {})
|
|
260
|
+
.filter((item) => {
|
|
261
|
+
const schema = item[1]
|
|
262
|
+
return schema && typeof schema.map === 'function'
|
|
263
|
+
})
|
|
264
|
+
.map(([name, schemas]) => {
|
|
265
|
+
const nameSchema = schemas.find((schema) => schema.keyword === schemaKeywords.name) as SchemaKeywordMapper['name']
|
|
266
|
+
const mappedName = nameSchema?.args || name
|
|
267
|
+
|
|
268
|
+
// custom mapper(pluginOptions)
|
|
269
|
+
if (options.mapper?.[mappedName]) {
|
|
270
|
+
return `"${name}": ${options.mapper?.[mappedName]}`
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return `"${name}": ${sort(schemas)
|
|
274
|
+
.map((schema, array, siblings) => {
|
|
275
|
+
return parse({ parent: current, current: schema, siblings }, options)
|
|
276
|
+
})
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
.join('')}`
|
|
279
|
+
})
|
|
280
|
+
.join(',')
|
|
281
|
+
|
|
282
|
+
const additionalProperties = current.args?.additionalProperties?.length
|
|
283
|
+
? current.args.additionalProperties
|
|
284
|
+
.map((schema, _index, siblings) => parse({ parent: current, current: schema, siblings }, options))
|
|
285
|
+
.filter(Boolean)
|
|
286
|
+
.join('')
|
|
287
|
+
: undefined
|
|
288
|
+
|
|
289
|
+
const text = [
|
|
290
|
+
zodKeywordMapper.object(properties),
|
|
291
|
+
current.args?.strict ? zodKeywordMapper.strict() : undefined,
|
|
292
|
+
additionalProperties ? zodKeywordMapper.catchall(additionalProperties) : undefined,
|
|
293
|
+
].filter(Boolean)
|
|
294
|
+
|
|
295
|
+
return text.join('')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (isKeyword(current, schemaKeywords.tuple)) {
|
|
299
|
+
return zodKeywordMapper.tuple(
|
|
300
|
+
current.args.items.map((schema, _index, siblings) => parse({ parent: current, current: schema, siblings }, options)).filter(Boolean),
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (isKeyword(current, schemaKeywords.const)) {
|
|
305
|
+
if (current.args.format === 'number' && current.args.value !== undefined) {
|
|
306
|
+
return zodKeywordMapper.const(Number.parseInt(current.args.value?.toString()))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (current.args.format === 'boolean' && current.args.value !== undefined) {
|
|
310
|
+
return zodKeywordMapper.const(current.args.value)
|
|
311
|
+
}
|
|
312
|
+
return zodKeywordMapper.const(transformers.stringify(current.args.value))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (isKeyword(current, schemaKeywords.matches)) {
|
|
316
|
+
if (current.args) {
|
|
317
|
+
return zodKeywordMapper.matches(transformers.toRegExpString(current.args))
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (isKeyword(current, schemaKeywords.default)) {
|
|
322
|
+
if (current.args) {
|
|
323
|
+
return zodKeywordMapper.default(current.args)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (isKeyword(current, schemaKeywords.describe)) {
|
|
328
|
+
if (current.args) {
|
|
329
|
+
return zodKeywordMapper.describe(transformers.stringify(current.args.toString()))
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (isKeyword(current, schemaKeywords.string)) {
|
|
334
|
+
return zodKeywordMapper.string(shouldCoerce(options.coercion, 'strings'))
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (isKeyword(current, schemaKeywords.number)) {
|
|
338
|
+
return zodKeywordMapper.number(shouldCoerce(options.coercion, 'numbers'))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (isKeyword(current, schemaKeywords.integer)) {
|
|
342
|
+
return zodKeywordMapper.integer(shouldCoerce(options.coercion, 'numbers'))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (isKeyword(current, schemaKeywords.min)) {
|
|
346
|
+
return zodKeywordMapper.min(current.args)
|
|
347
|
+
}
|
|
348
|
+
if (isKeyword(current, schemaKeywords.max)) {
|
|
349
|
+
return zodKeywordMapper.max(current.args)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (isKeyword(current, schemaKeywords.datetime)) {
|
|
353
|
+
return zodKeywordMapper.datetime(current.args.offset, current.args.local)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (isKeyword(current, schemaKeywords.date)) {
|
|
357
|
+
return zodKeywordMapper.date(current.args.type, shouldCoerce(options.coercion, 'dates'))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (isKeyword(current, schemaKeywords.time)) {
|
|
361
|
+
return zodKeywordMapper.time(current.args.type, shouldCoerce(options.coercion, 'dates'))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (current.keyword in zodKeywordMapper && 'args' in current) {
|
|
365
|
+
const value = zodKeywordMapper[current.keyword as keyof typeof zodKeywordMapper] as (typeof zodKeywordMapper)['const']
|
|
366
|
+
|
|
367
|
+
return value((current as SchemaKeywordBase<unknown>).args as any)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (isKeyword(current, schemaKeywords.optional)) {
|
|
371
|
+
if (siblings.some((schema) => isKeyword(schema, schemaKeywords.default))) return ''
|
|
372
|
+
|
|
373
|
+
return value()
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (current.keyword in zodKeywordMapper) {
|
|
377
|
+
return value()
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return undefined
|
|
381
|
+
}
|