From acd8f712ee8d5bad6999fd5208398b9bc2e141be Mon Sep 17 00:00:00 2001 From: Stijn Van Hulle Date: Sun, 18 Feb 2024 15:46:07 +0100 Subject: [PATCH 1/2] fix: use of strict for anyOf --- .changeset/silver-suits-wait.md | 5 + packages/swagger-faker/src/FakerGenerator.ts | 6 +- .../OperationGenerator.test.tsx.snap | 18 +- .../__snapshots__/Mutation.test.tsx.snap | 2 +- .../__snapshots__/Query.test.tsx.snap | 4 +- .../swagger-faker/src/fakerParser.test.ts | 10 +- packages/swagger-faker/src/fakerParser.ts | 154 +++++--------- packages/swagger-zod/mocks/anyof.yaml | 24 +++ packages/swagger-zod/src/ZodGenerator.test.ts | 26 +++ packages/swagger-zod/src/ZodGenerator.ts | 15 +- .../__snapshots__/ZodGenerator.test.ts.snap | 6 + packages/swagger-zod/src/zodParser.test.ts | 6 +- packages/swagger-zod/src/zodParser.ts | 197 +++++++----------- 13 files changed, 233 insertions(+), 240 deletions(-) create mode 100644 .changeset/silver-suits-wait.md create mode 100644 packages/swagger-zod/mocks/anyof.yaml diff --git a/.changeset/silver-suits-wait.md b/.changeset/silver-suits-wait.md new file mode 100644 index 000000000..ea07d8718 --- /dev/null +++ b/.changeset/silver-suits-wait.md @@ -0,0 +1,5 @@ +--- +"@kubb/swagger-zod": patch +--- + +anyOf with strict when using z.object diff --git a/packages/swagger-faker/src/FakerGenerator.ts b/packages/swagger-faker/src/FakerGenerator.ts index 8de2c627a..005f58c2a 100644 --- a/packages/swagger-faker/src/FakerGenerator.ts +++ b/packages/swagger-faker/src/FakerGenerator.ts @@ -128,7 +128,7 @@ export class FakerGenerator extends Generator [GET] should generate 1`] = ` */ export function ListPetsError(override?: NonNullable>): NonNullable { - return Error(override); + return undefined(override); } /** * @description A paged array of pets */ export function ListPetsQueryResponse(override?: NonNullable>): NonNullable { - return Pets(override); + return undefined(override); } ", }, @@ -204,14 +204,14 @@ exports[`OperationGenerator > [GET] should generate 2`] = ` */ export function ShowPetByIdError(override?: NonNullable>): NonNullable { - return Error(override); + return undefined(override); } /** * @description Expected response to a valid request */ export function ShowPetByIdQueryResponse(override?: NonNullable>): NonNullable { - return Pet(override); + return undefined(override); } ", }, @@ -295,7 +295,7 @@ exports[`OperationGenerator > [GET] should generate with seed \`[222]\` 1`] = ` export function ListPetsError(override?: NonNullable>): NonNullable { faker.seed([222]); - return Error(override); + return undefined(override); } /** * @description A paged array of pets @@ -303,7 +303,7 @@ exports[`OperationGenerator > [GET] should generate with seed \`[222]\` 1`] = ` export function ListPetsQueryResponse(override?: NonNullable>): NonNullable { faker.seed([222]); - return Pets(override); + return undefined(override); } ", }, @@ -387,7 +387,7 @@ exports[`OperationGenerator > [GET] should generate with seed \`[222]\` 2`] = ` export function ShowPetByIdError(override?: NonNullable>): NonNullable { faker.seed([222]); - return Error(override); + return undefined(override); } /** * @description Expected response to a valid request @@ -395,7 +395,7 @@ exports[`OperationGenerator > [GET] should generate with seed \`[222]\` 2`] = ` export function ShowPetByIdQueryResponse(override?: NonNullable>): NonNullable { faker.seed([222]); - return Pet(override); + return undefined(override); } ", }, @@ -489,7 +489,7 @@ exports[`OperationGenerator > [POST] should generate 1`] = ` */ export function CreatePetsError(override?: NonNullable>): NonNullable { - return Error(override); + return undefined(override); } ", }, diff --git a/packages/swagger-faker/src/components/__snapshots__/Mutation.test.tsx.snap b/packages/swagger-faker/src/components/__snapshots__/Mutation.test.tsx.snap index 9fb42112f..76a85860d 100644 --- a/packages/swagger-faker/src/components/__snapshots__/Mutation.test.tsx.snap +++ b/packages/swagger-faker/src/components/__snapshots__/Mutation.test.tsx.snap @@ -25,7 +25,7 @@ export function createCreatePetsMutationResponse(override?: NonNullable>): NonNullable { - return createError(override) + return undefined(override) } " `; diff --git a/packages/swagger-faker/src/components/__snapshots__/Query.test.tsx.snap b/packages/swagger-faker/src/components/__snapshots__/Query.test.tsx.snap index 081303710..8b96a268e 100644 --- a/packages/swagger-faker/src/components/__snapshots__/Query.test.tsx.snap +++ b/packages/swagger-faker/src/components/__snapshots__/Query.test.tsx.snap @@ -13,7 +13,7 @@ exports[` > showPetById 1`] = ` */ export function createShowPetByIdError(override?: NonNullable>): NonNullable { - return createError(override) + return undefined(override) } /** @@ -21,7 +21,7 @@ export function createShowPetByIdError(override?: NonNullable>): NonNullable { - return createPet(override) + return undefined(override) } " `; diff --git a/packages/swagger-faker/src/fakerParser.test.ts b/packages/swagger-faker/src/fakerParser.test.ts index 502889cd6..a3606a4fd 100644 --- a/packages/swagger-faker/src/fakerParser.test.ts +++ b/packages/swagger-faker/src/fakerParser.test.ts @@ -48,7 +48,7 @@ const input = [ { input: parseFakerMeta({ keyword: 'ref', - args: 'createPet', + args: { name: 'createPet' }, }), expected: 'createPet()', }, @@ -85,7 +85,7 @@ const input = [ { input: parseFakerMeta({ keyword: 'array', - args: [{ keyword: 'ref', args: 'createPet' }], + args: [{ keyword: 'ref', args: { name: 'createPet' } }], }), expected: 'faker.helpers.arrayElements([createPet()]) as any', }, @@ -132,8 +132,10 @@ const input = [ input: parseFakerMeta({ keyword: 'object', args: { - firstName: [{ keyword: 'string', args: { min: 2 } }], - address: [{ keyword: 'string' }, { keyword: 'null' }], + entries: { + firstName: [{ keyword: 'string', args: { min: 2 } }], + address: [{ keyword: 'string' }, { keyword: 'null' }], + }, }, }), expected: '{"firstName": faker.string.alpha({"min":2}),"address": faker.helpers.arrayElement([faker.string.alpha(),null])}', diff --git a/packages/swagger-faker/src/fakerParser.ts b/packages/swagger-faker/src/fakerParser.ts index 743dfbdfc..72f5474db 100644 --- a/packages/swagger-faker/src/fakerParser.ts +++ b/packages/swagger-faker/src/fakerParser.ts @@ -1,3 +1,31 @@ +export type FakerMetaMapper = { + object: { keyword: 'object'; args: { entries: { [x: string]: FakerMeta[] }; strict?: boolean } } + url: { keyword: 'url' } + uuid: { keyword: 'uuid' } + email: { keyword: 'email' } + firstName: { keyword: 'firstName' } + lastName: { keyword: 'lastName' } + phone: { keyword: 'phone' } + password: { keyword: 'password' } + datetime: { keyword: 'datetime' } + tuple: { keyword: 'tuple'; args?: FakerMeta[] } + array: { keyword: 'array'; args?: FakerMeta[] } + enum: { keyword: 'enum'; args?: Array } + and: { keyword: 'and'; args?: FakerMeta[] } + union: { keyword: 'union'; args?: FakerMeta[] } + ref: { keyword: 'ref'; args?: { name: string } } + catchall: { keyword: 'catchall'; args?: FakerMeta[] } + matches: { keyword: 'matches'; args?: string } + boolean: { keyword: 'boolean' } + string: { keyword: 'string'; args?: { min?: number; max?: number } } + integer: { keyword: 'integer'; args?: { min?: number; max?: number } } + number: { keyword: 'number'; args?: { min?: number; max?: number } } + undefined: { keyword: 'undefined' } + null: { keyword: 'null' } + any: { keyword: 'any' } + unknown: { keyword: 'unknown' } +} + export const fakerKeywords = { any: 'any', unknown: 'unknown', @@ -27,7 +55,7 @@ export const fakerKeywords = { lastName: 'lastName', password: 'password', phone: 'phone', -} as const +} satisfies { [K in keyof FakerMetaMapper]: FakerMetaMapper[K]['keyword'] } export type FakerKeyword = keyof typeof fakerKeywords @@ -60,86 +88,21 @@ export const fakerKeywordMapper = { lastName: 'faker.person.lastName', password: 'faker.internet.password', phone: 'faker.phone.number', -} as const satisfies Record +} satisfies { [K in keyof FakerMetaMapper]: string } type FakerMetaBase = { keyword: FakerKeyword args: T } -type FakerMetaUnknown = { keyword: typeof fakerKeywords.unknown } - -type FakerMetaAny = { keyword: typeof fakerKeywords.any } -type FakerMetaNull = { keyword: typeof fakerKeywords.null } -type FakerMetaUndefined = { keyword: typeof fakerKeywords.undefined } - -type FakerMetaNumber = { keyword: typeof fakerKeywords.number; args?: { min?: number; max?: number } } -type FakerMetaInteger = { keyword: typeof fakerKeywords.integer; args?: { min?: number; max?: number } } - -type FakerMetaString = { keyword: typeof fakerKeywords.string; args?: { min?: number; max?: number } } - -type FakerMetaBoolean = { keyword: typeof fakerKeywords.boolean } - -type FakerMetaMatches = { keyword: typeof fakerKeywords.matches; args?: string } - -type FakerMetaObject = { keyword: typeof fakerKeywords.object; args?: { [x: string]: FakerMeta[] } } - -type FakerMetaCatchall = { keyword: typeof fakerKeywords.catchall; args?: FakerMeta[] } - -type FakerMetaRef = { keyword: typeof fakerKeywords.ref; args?: string } - -type FakerMetaUnion = { keyword: typeof fakerKeywords.union; args?: FakerMeta[] } - -type FakerMetaAnd = { keyword: typeof fakerKeywords.and; args?: FakerMeta[] } - -type FakerMetaEnum = { keyword: typeof fakerKeywords.enum; args?: Array } - -type FakerMetaArray = { keyword: typeof fakerKeywords.array; args?: FakerMeta[] } - -type FakerMetaTuple = { keyword: typeof fakerKeywords.tuple; args?: FakerMeta[] } -type FakerMetaEmail = { keyword: typeof fakerKeywords.email } - -type FakerMetaFirstName = { keyword: typeof fakerKeywords.firstName } - -type FakerMetaLastName = { keyword: typeof fakerKeywords.lastName } -type FakerMetaPassword = { keyword: typeof fakerKeywords.password } - -type FakerMetaPhone = { keyword: typeof fakerKeywords.phone } - -type FakerMetaDatetime = { keyword: typeof fakerKeywords.datetime } - -type FakerMetaUuid = { keyword: typeof fakerKeywords.uuid } - -type FakerMetaUrl = { keyword: typeof fakerKeywords.url } - export type FakerMeta = | { keyword: string } - | FakerMetaUnknown - | FakerMetaAny - | FakerMetaNull - | FakerMetaUndefined - | FakerMetaNumber - | FakerMetaInteger - | FakerMetaString - | FakerMetaBoolean - | FakerMetaMatches - | FakerMetaObject - | FakerMetaCatchall - | FakerMetaRef - | FakerMetaUnion - | FakerMetaAnd - | FakerMetaEnum - | FakerMetaArray - | FakerMetaTuple - | FakerMetaEmail - | FakerMetaFirstName - | FakerMetaLastName - | FakerMetaPassword - | FakerMetaPhone - | FakerMetaDatetime - | FakerMetaUuid - | FakerMetaUrl -// use example + | FakerMetaMapper[keyof FakerMetaMapper] + +export function isKeyword(meta: T, keyword: K): meta is Extract { + return meta.keyword === keyword +} + /** * @link based on https://github.com/cellular/oazapfts/blob/7ba226ebb15374e8483cc53e7532f1663179a22c/src/codegen/generate.ts#L398 */ @@ -164,45 +127,42 @@ function joinItems(items: string[]): string { } export function parseFakerMeta( - item: FakerMeta, + item: FakerMeta = {} as FakerMeta, { mapper = fakerKeywordMapper, withOverride }: { mapper?: Record; withOverride?: boolean } = {}, ): string { - // eslint-disable-next-line prefer-const - let { keyword, args } = (item || {}) as FakerMetaBase - const value = mapper[keyword] + const value = mapper[item.keyword as keyof typeof mapper] - if (keyword === fakerKeywords.tuple || keyword === fakerKeywords.array || keyword === fakerKeywords.union) { + if (isKeyword(item, fakerKeywords.tuple) || isKeyword(item, fakerKeywords.array) || isKeyword(item, fakerKeywords.union)) { return `${value}(${ - Array.isArray(args) ? `[${args.map((item) => parseFakerMeta(item as FakerMeta, { mapper })).join(',')}]` : parseFakerMeta(args as FakerMeta) + Array.isArray(item.args) + ? `[${item.args.map((orItem) => parseFakerMeta(orItem, { mapper })).join(',')}]` + : parseFakerMeta(item.args) }) as any` } - if (keyword === fakerKeywords.and) { + if (isKeyword(item, fakerKeywords.and)) { return `${value}({},${ - Array.isArray(args) ? `${args.map((item) => parseFakerMeta(item as FakerMeta, { mapper })).join(',')}` : parseFakerMeta(args as FakerMeta) + Array.isArray(item.args) ? `${item.args.map((andItem) => parseFakerMeta(andItem, { mapper })).join(',')}` : parseFakerMeta(item.args) })` } - if (keyword === fakerKeywords.enum) { - return `${value}(${Array.isArray(args) ? `${args.join(',')}` : parseFakerMeta(args as FakerMeta)})` + if (isKeyword(item, fakerKeywords.enum)) { + return `${value}(${Array.isArray(item.args) ? `${item.args.join(',')}` : parseFakerMeta(item.args)})` } - if (keyword === fakerKeywords.catchall) { + if (isKeyword(item, fakerKeywords.catchall)) { throw new Error('catchall is not implemented') } - if (keyword === fakerKeywords.object) { - if (!args) { - args = '{}' - } - const argsObject = Object.entries(args as FakerMeta) + if (isKeyword(item, fakerKeywords.object)) { + const argsObject = Object.entries(item.args?.entries || '{}') .filter((item) => { - const schema = item[1] as FakerMeta[] + const schema = item[1] return schema && typeof schema.map === 'function' }) .map((item) => { const name = item[0] - const schema = item[1] as FakerMeta[] + const schema = item[1] return `"${name}": ${ joinItems( schema @@ -217,19 +177,19 @@ export function parseFakerMeta( } // custom type - if (keyword === fakerKeywords.ref) { + if (isKeyword(item, fakerKeywords.ref)) { if (withOverride) { - return `${args as string}(override)` + return `${item.args?.name as string}(override)` } - return `${args as string}()` + return `${item.args?.name as string}()` } - if (keyword === fakerKeywords.null || keyword === fakerKeywords.undefined || keyword === fakerKeywords.any) { + if (isKeyword(item, fakerKeywords.null) || isKeyword(item, fakerKeywords.undefined) || isKeyword(item, fakerKeywords.any)) { return value } - if (keyword in mapper) { - const options = JSON.stringify(args) + if (item.keyword in mapper) { + const options = JSON.stringify((item as FakerMetaBase).args) return `${value}(${options ?? ''})` } diff --git a/packages/swagger-zod/mocks/anyof.yaml b/packages/swagger-zod/mocks/anyof.yaml new file mode 100644 index 000000000..5b06db486 --- /dev/null +++ b/packages/swagger-zod/mocks/anyof.yaml @@ -0,0 +1,24 @@ +info: + title: anyof test cases + version: 1.0.0 +openapi: 3.1.0 +paths: {} +components: + schemas: + test: + anyOf: + - type: object + properties: + propertyA: + type: string + required: + - propertyA + - type: object + properties: + propertyA: + type: string + propertyB: + type: string + required: + - propertyA + - propertyB diff --git a/packages/swagger-zod/src/ZodGenerator.test.ts b/packages/swagger-zod/src/ZodGenerator.test.ts index eb0bc5720..b8850fcc3 100644 --- a/packages/swagger-zod/src/ZodGenerator.test.ts +++ b/packages/swagger-zod/src/ZodGenerator.test.ts @@ -267,3 +267,29 @@ describe('ZodGenerator recursive', async () => { expect(node).toMatchSnapshot() }) }) + +describe('ZodGenerator anyof', async () => { + const discriminatorPath = path.resolve(__dirname, '../mocks/anyof.yaml') + const oas = await new OasManager().parse(discriminatorPath) + const generator = new ZodGenerator({ + exclude: undefined, + include: undefined, + override: undefined, + transformers: {}, + typed: false, + dateType: 'string', + unknownType: 'any', + }, { + oas, + pluginManager: mockedPluginManager, + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain + const schemas = oas.getDefinition().components?.schemas! + + test('anyof with 2 objects', async () => { + const schema = schemas['test'] as OasTypes.SchemaObject + const node = generator.build({ schema, baseName: 'test' }) + + expect(node).toMatchSnapshot() + }) +}) diff --git a/packages/swagger-zod/src/ZodGenerator.ts b/packages/swagger-zod/src/ZodGenerator.ts index 27cf127a5..141905ec7 100644 --- a/packages/swagger-zod/src/ZodGenerator.ts +++ b/packages/swagger-zod/src/ZodGenerator.ts @@ -5,7 +5,7 @@ import { getSchemaFactory, isReference } from '@kubb/swagger/utils' import { pluginKey as swaggerTypeScriptPluginKey } from '@kubb/swagger-ts' import { pluginKey } from './plugin.ts' -import { zodKeywords, zodParser } from './zodParser.ts' +import { isKeyword, zodKeywords, zodParser } from './zodParser.ts' import type { PluginManager } from '@kubb/core' import type { ts } from '@kubb/parser' @@ -140,7 +140,7 @@ export class ZodGenerator extends Generator { return item && item.keyword !== this.#unknownReturn + }).map(item => { + if (isKeyword(item, zodKeywords.object)) { + return { + ...item, + args: { + ...item.args, + strict: true, + }, + } + } + return item }), } if (schemaWithoutAnyOf.properties) { diff --git a/packages/swagger-zod/src/__snapshots__/ZodGenerator.test.ts.snap b/packages/swagger-zod/src/__snapshots__/ZodGenerator.test.ts.snap index b8fc7bef4..85d66724c 100644 --- a/packages/swagger-zod/src/__snapshots__/ZodGenerator.test.ts.snap +++ b/packages/swagger-zod/src/__snapshots__/ZodGenerator.test.ts.snap @@ -30,6 +30,12 @@ exports[`ZodGenerator PetStore > generate schema for Pets 1`] = ` ] `; +exports[`ZodGenerator anyof > anyof with 2 objects 1`] = ` +[ + "export const test = z.union([z.object({"propertyA": z.string()}).strict(),z.object({"propertyA": z.string(),"propertyB": z.string()}).strict()]);", +] +`; + exports[`ZodGenerator constCases > MixedValueTypeConst generates zod literal value correctly, overriding the type constraint 1`] = ` [ "export const MixedValueTypeConst = z.object({"foobar": z.literal("foobar")}).describe(\`This probably should fail miserably\`);", diff --git a/packages/swagger-zod/src/zodParser.test.ts b/packages/swagger-zod/src/zodParser.test.ts index 7f3d7ed22..7604c3711 100644 --- a/packages/swagger-zod/src/zodParser.test.ts +++ b/packages/swagger-zod/src/zodParser.test.ts @@ -153,8 +153,10 @@ const input = [ input: parseZodMeta({ keyword: 'object', args: { - firstName: [{ keyword: 'string' }, { keyword: 'min', args: 2 }], - address: [{ keyword: 'string' }, { keyword: 'nullable' }, { keyword: 'describe', args: '"Your address"' }], + entries: { + firstName: [{ keyword: 'string' }, { keyword: 'min', args: 2 }], + address: [{ keyword: 'string' }, { keyword: 'nullable' }, { keyword: 'describe', args: '"Your address"' }], + }, }, }), expected: 'z.object({"firstName": z.string().min(2),"address": z.string().nullable().describe("Your address")})', diff --git a/packages/swagger-zod/src/zodParser.ts b/packages/swagger-zod/src/zodParser.ts index ea16ced48..03fbdc463 100644 --- a/packages/swagger-zod/src/zodParser.ts +++ b/packages/swagger-zod/src/zodParser.ts @@ -1,3 +1,39 @@ +export type ZodMetaMapper = { + object: { keyword: 'object'; args: { entries: { [x: string]: ZodMeta[] }; strict?: boolean } } + strict: { keyword: 'strict' } + readOnly: { keyword: 'readOnly' } + url: { keyword: 'url' } + uuid: { keyword: 'uuid' } + email: { keyword: 'email' } + date: { keyword: 'date' } + datetime: { keyword: 'datetime' } + default: { keyword: 'default'; args?: string | number | boolean } + lazy: { keyword: 'lazy' } + tuple: { keyword: 'tuple'; args?: ZodMeta[] } + array: { keyword: 'array'; args?: ZodMeta[] } + enum: { keyword: 'enum'; args?: Array } + and: { keyword: 'and'; args?: ZodMeta[] } + literal: { keyword: 'literal'; args: string | number } + union: { keyword: 'union'; args?: ZodMeta[] } + ref: { keyword: 'ref'; args?: { name: string } } + catchall: { keyword: 'catchall'; args?: ZodMeta[] } + optional: { keyword: 'optional' } + matches: { keyword: 'matches'; args?: string } + max: { keyword: 'max'; args?: number } + min: { keyword: 'min'; args?: number } + describe: { keyword: 'describe'; args?: string } + boolean: { keyword: 'boolean' } + string: { keyword: 'string' } + integer: { keyword: 'integer' } + number: { keyword: 'number' } + undefined: { keyword: 'undefined' } + nullish: { keyword: 'nullish' } + nullable: { keyword: 'nullable' } + null: { keyword: 'null' } + any: { keyword: 'any' } + unknown: { keyword: 'unknown' } +} + export const zodKeywords = { any: 'any', unknown: 'unknown', @@ -21,6 +57,7 @@ export const zodKeywords = { email: 'email', uuid: 'uuid', url: 'url', + strict: 'strict', /* intersection */ default: 'default', and: 'and', @@ -34,7 +71,7 @@ export const zodKeywords = { // custom ones ref: 'ref', matches: 'matches', -} as const +} satisfies { [K in keyof ZodMetaMapper]: ZodMetaMapper[K]['keyword'] } export type ZodKeyword = keyof typeof zodKeywords @@ -61,6 +98,7 @@ export const zodKeywordMapper = { email: '.email', uuid: '.uuid', url: '.url', + strict: '.strict', /* intersection */ default: '.default', and: '.and', @@ -74,98 +112,20 @@ export const zodKeywordMapper = { // custom ones ref: 'ref', matches: '.regex', -} as const satisfies Record +} satisfies { [K in keyof ZodMetaMapper]: string } type ZodMetaBase = { keyword: ZodKeyword args: T } -type ZodMetaUnknown = { keyword: typeof zodKeywords.unknown } - -type ZodMetaAny = { keyword: typeof zodKeywords.any } -type ZodMetaNull = { keyword: typeof zodKeywords.null } - -type ZodMetaNullish = { keyword: typeof zodKeywords.nullish } -type ZodMetaUndefined = { keyword: typeof zodKeywords.undefined } - -type ZodMetaNumber = { keyword: typeof zodKeywords.number } -type ZodMetaInteger = { keyword: typeof zodKeywords.integer } - -type ZodMetaString = { keyword: typeof zodKeywords.string } - -type ZodMetaBoolean = { keyword: typeof zodKeywords.boolean } - -type ZodMetaDescribe = { keyword: typeof zodKeywords.describe; args?: string } -type ZodMetaMin = { keyword: typeof zodKeywords.min; args?: number } - -type ZodMetaMax = { keyword: typeof zodKeywords.max; args?: number } -type ZodMetaMatches = { keyword: typeof zodKeywords.matches; args?: string } -type ZodMetaOptional = { keyword: typeof zodKeywords.optional } - -type ZodMetaObject = { keyword: typeof zodKeywords.object; args?: { [x: string]: ZodMeta[] } } - -type ZodMetaCatchall = { keyword: typeof zodKeywords.catchall; args?: ZodMeta[] } - -type ZodMetaRef = { keyword: typeof zodKeywords.ref; args?: { name: string } } - -type ZodMetaUnion = { keyword: typeof zodKeywords.union; args?: ZodMeta[] } -type ZodMetaLiteral = { keyword: typeof zodKeywords.literal; args: string | number } - -type ZodMetaAnd = { keyword: typeof zodKeywords.and; args?: ZodMeta[] } - -type ZodMetaEnum = { keyword: typeof zodKeywords.enum; args?: Array } - -type ZodMetaArray = { keyword: typeof zodKeywords.array; args?: ZodMeta[] } - -type ZodMetaTuple = { keyword: typeof zodKeywords.tuple; args?: ZodMeta[] } -type ZodMetaLazy = { keyword: typeof zodKeywords.lazy } -type ZodMetaDefault = { keyword: typeof zodKeywords.default; args?: string | number | boolean } - -type ZodMetaDatetime = { keyword: typeof zodKeywords.datetime } - -type ZodMetaDate = { keyword: typeof zodKeywords.date } - -type ZodMetaEmail = { keyword: typeof zodKeywords.email } - -type ZodMetaUuid = { keyword: typeof zodKeywords.uuid } - -type ZodMetaUrl = { keyword: typeof zodKeywords.url } -type ZodMetaReadOnly = { keyword: typeof zodKeywords.readOnly } +export function isKeyword(meta: T, keyword: K): meta is Extract { + return meta.keyword === keyword +} export type ZodMeta = | { keyword: string } - | ZodMetaUnknown - | ZodMetaAny - | ZodMetaNull - | ZodMetaNullish - | ZodMetaUndefined - | ZodMetaInteger - | ZodMetaNumber - | ZodMetaString - | ZodMetaBoolean - | ZodMetaLazy - | ZodMetaDescribe - | ZodMetaMin - | ZodMetaMax - | ZodMetaMatches - | ZodMetaOptional - | ZodMetaObject - | ZodMetaCatchall - | ZodMetaRef - | ZodMetaUnion - | ZodMetaAnd - | ZodMetaEnum - | ZodMetaArray - | ZodMetaTuple - | ZodMetaDefault - | ZodMetaDatetime - | ZodMetaDate - | ZodMetaEmail - | ZodMetaUuid - | ZodMetaLiteral - | ZodMetaUrl - | ZodMetaReadOnly + | ZodMetaMapper[keyof ZodMetaMapper] /** * @link based on https://github.com/cellular/oazapfts/blob/7ba226ebb15374e8483cc53e7532f1663179a22c/src/codegen/generate.ts#L398 @@ -179,42 +139,40 @@ function zodKeywordSorter(a: ZodMeta, b: ZodMeta): 1 | -1 | 0 { return 0 } -export function parseZodMeta(item: ZodMeta, mapper: Record = zodKeywordMapper): string { - // eslint-disable-next-line prefer-const - let { keyword, args = '' } = (item || {}) as ZodMetaBase - const value = mapper[keyword] +export function parseZodMeta(item: ZodMeta = {} as ZodMeta, mapper: Record = zodKeywordMapper): string { + const value = mapper[item.keyword as keyof typeof mapper] - if (keyword === zodKeywords.tuple) { - return `${value}(${Array.isArray(args) ? `[${args.map((item) => parseZodMeta(item as ZodMeta, mapper)).join(',')}]` : parseZodMeta(args as ZodMeta)})` + if (isKeyword(item, zodKeywords.tuple)) { + return `${value}(${Array.isArray(item.args) ? `[${item.args.map((tupleItem) => parseZodMeta(tupleItem, mapper)).join(',')}]` : parseZodMeta(item.args)})` } - if (keyword === zodKeywords.enum) { - return `${value}(${Array.isArray(args) ? `[${args.join(',')}]` : parseZodMeta(args as ZodMeta)})` + if (isKeyword(item, zodKeywords.enum)) { + return `${value}(${Array.isArray(item.args) ? `[${item.args.join(',')}]` : parseZodMeta(item.args)})` } - if (keyword === zodKeywords.array) { - return `${value}(${Array.isArray(args) ? `${args.map((item) => parseZodMeta(item as ZodMeta, mapper)).join('')}` : parseZodMeta(args as ZodMeta)})` + if (isKeyword(item, zodKeywords.array)) { + return `${value}(${Array.isArray(item.args) ? `${item.args.map((arrayItem) => parseZodMeta(arrayItem, mapper)).join('')}` : parseZodMeta(item.args)})` } - if (keyword === zodKeywords.union) { + if (isKeyword(item, zodKeywords.union)) { // zod union type needs at least 2 items - if (Array.isArray(args) && args.length === 1) { - return parseZodMeta(args[0] as ZodMeta) + if (Array.isArray(item.args) && item.args.length === 1) { + return parseZodMeta(item.args[0] as ZodMeta) } - if (Array.isArray(args) && !args.length) { + if (Array.isArray(item.args) && !item.args.length) { return '' } - return `${Array.isArray(args) ? `${value}([${args.map((item) => parseZodMeta(item as ZodMeta, mapper)).join(',')}])` : parseZodMeta(args as ZodMeta)}` + return `${Array.isArray(item.args) ? `${value}([${item.args.map((unionItem) => parseZodMeta(unionItem, mapper)).join(',')}])` : parseZodMeta(item.args)}` } - if (keyword === zodKeywords.catchall) { - return `${value}(${Array.isArray(args) ? `${args.map((item) => parseZodMeta(item as ZodMeta, mapper)).join('')}` : parseZodMeta(args as ZodMeta)})` + if (isKeyword(item, zodKeywords.catchall)) { + return `${value}(${Array.isArray(item.args) ? `${item.args.map((catchAllItem) => parseZodMeta(catchAllItem, mapper)).join('')}` : parseZodMeta(item.args)})` } - if (keyword === zodKeywords.and && Array.isArray(args)) { + if (isKeyword(item, zodKeywords.and)) { return `${ - args - .filter((item: ZodMeta) => { + item.args + ?.filter((item: ZodMeta) => { return ![zodKeywords.optional, zodKeywords.describe].includes(item.keyword as typeof zodKeywords.optional | typeof zodKeywords.describe) }) .map((item: ZodMeta) => parseZodMeta(item, mapper)) @@ -224,18 +182,15 @@ export function parseZodMeta(item: ZodMeta, mapper: Record = }` } - if (keyword === zodKeywords.object) { - if (!args) { - args = '{}' - } - const argsObject = Object.entries(args as ZodMeta) + if (isKeyword(item, zodKeywords.object)) { + const argsObject = Object.entries(item.args?.entries || '{}') .filter((item) => { - const schema = item[1] as ZodMeta[] + const schema = item[1] return schema && typeof schema.map === 'function' }) .map((item) => { const name = item[0] - const schema = item[1] as ZodMeta[] + const schema = item[1] return `"${name}": ${ schema .sort(zodKeywordSorter) @@ -245,22 +200,24 @@ export function parseZodMeta(item: ZodMeta, mapper: Record = }) .join(',') - args = `{${argsObject}}` + if (item.args?.strict) { + return `${value}({${argsObject}}).strict()` + } + + return `${value}({${argsObject}})` } // custom type - if (keyword === zodKeywords.ref) { - const refArgs = args as ZodMetaRef['args'] - - return `${mapper.lazy}(() => ${refArgs?.name})` + if (isKeyword(item, zodKeywords.ref)) { + return `${mapper.lazy}(() => ${item.args?.name})` } - if (keyword === zodKeywords.default && args === undefined) { - return '' + if (item.keyword in mapper && 'args' in item) { + return `${value}(${(item as ZodMetaBase).args as string})` } - if (keyword in mapper) { - return `${value}(${args as string})` + if (item.keyword in mapper) { + return `${value}()` } return '""' From c98b0f861e906e9e8d238dcddb82a0ea8b680dc9 Mon Sep 17 00:00:00 2001 From: Stijn Van Hulle Date: Sun, 18 Feb 2024 16:01:18 +0100 Subject: [PATCH 2/2] chore: fix tests --- packages/swagger-faker/src/FakerGenerator.ts | 2 +- packages/swagger-faker/src/fakerParser.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/swagger-faker/src/FakerGenerator.ts b/packages/swagger-faker/src/FakerGenerator.ts index 005f58c2a..9f1bdc199 100644 --- a/packages/swagger-faker/src/FakerGenerator.ts +++ b/packages/swagger-faker/src/FakerGenerator.ts @@ -159,7 +159,7 @@ export class FakerGenerator extends Generator