From e4a247a2a87b4d3bde55891b31e07413d3a9f00d Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Thu, 8 Aug 2024 14:45:47 +0100 Subject: [PATCH] fix(helpers/zod): correct logic for adding root schema to definitions --- .../zod-to-json-schema/zodToJsonSchema.ts | 6 +- tests/lib/parser.test.ts | 179 ++++++++++++++++++ 2 files changed, 181 insertions(+), 4 deletions(-) diff --git a/src/_vendor/zod-to-json-schema/zodToJsonSchema.ts b/src/_vendor/zod-to-json-schema/zodToJsonSchema.ts index 5547c4c37..1d95a98ba 100644 --- a/src/_vendor/zod-to-json-schema/zodToJsonSchema.ts +++ b/src/_vendor/zod-to-json-schema/zodToJsonSchema.ts @@ -61,8 +61,6 @@ const zodToJsonSchema = ( main.title = title; } - const rootRefPath = name ? [...refs.basePath, refs.definitionPath, name].join('/') : null; - const combined: ReturnType> = name === undefined ? definitions ? @@ -74,13 +72,13 @@ const zodToJsonSchema = ( : refs.nameStrategy === 'duplicate-ref' ? { ...main, - ...(definitions || refs.seenRefs.has(rootRefPath!) ? + ...(definitions || refs.seenRefs.size ? { [refs.definitionPath]: { ...definitions, // only actually duplicate the schema definition if it was ever referenced // otherwise the duplication is completely pointless - ...(refs.seenRefs.has(rootRefPath!) ? { [name]: main } : undefined), + ...(refs.seenRefs.size ? { [name]: main } : undefined), }, } : undefined), diff --git a/tests/lib/parser.test.ts b/tests/lib/parser.test.ts index 118954492..296787450 100644 --- a/tests/lib/parser.test.ts +++ b/tests/lib/parser.test.ts @@ -267,5 +267,184 @@ describe('.parse()', () => { } `); }); + + test('merged schemas', async () => { + const personSchema = z.object({ + name: z.string(), + phone_number: z.string().nullable(), + }); + + const contactPersonSchema = z.object({ + person1: personSchema.merge( + z.object({ + roles: z + .array(z.enum(['parent', 'child', 'sibling', 'spouse', 'friend', 'other'])) + .describe('Any roles for which the contact is important, use other for custom roles'), + description: z + .string() + .nullable() + .describe('Open text for any other relevant information about what the contact does.'), + }), + ), + person2: personSchema.merge( + z.object({ + differentField: z.string(), + }), + ), + }); + + expect(zodResponseFormat(contactPersonSchema, 'contactPerson').json_schema.schema) + .toMatchInlineSnapshot(` + { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "definitions": { + "contactPerson": { + "additionalProperties": false, + "properties": { + "person1": { + "additionalProperties": false, + "properties": { + "description": { + "description": "Open text for any other relevant information about what the contact does.", + "type": [ + "string", + "null", + ], + }, + "name": { + "type": "string", + }, + "phone_number": { + "type": [ + "string", + "null", + ], + }, + "roles": { + "description": "Any roles for which the contact is important, use other for custom roles", + "items": { + "enum": [ + "parent", + "child", + "sibling", + "spouse", + "friend", + "other", + ], + "type": "string", + }, + "type": "array", + }, + }, + "required": [ + "name", + "phone_number", + "roles", + "description", + ], + "type": "object", + }, + "person2": { + "additionalProperties": false, + "properties": { + "differentField": { + "type": "string", + }, + "name": { + "$ref": "#/definitions/contactPerson/properties/person1/properties/name", + }, + "phone_number": { + "$ref": "#/definitions/contactPerson/properties/person1/properties/phone_number", + }, + }, + "required": [ + "name", + "phone_number", + "differentField", + ], + "type": "object", + }, + }, + "required": [ + "person1", + "person2", + ], + "type": "object", + }, + }, + "properties": { + "person1": { + "additionalProperties": false, + "properties": { + "description": { + "description": "Open text for any other relevant information about what the contact does.", + "type": [ + "string", + "null", + ], + }, + "name": { + "type": "string", + }, + "phone_number": { + "type": [ + "string", + "null", + ], + }, + "roles": { + "description": "Any roles for which the contact is important, use other for custom roles", + "items": { + "enum": [ + "parent", + "child", + "sibling", + "spouse", + "friend", + "other", + ], + "type": "string", + }, + "type": "array", + }, + }, + "required": [ + "name", + "phone_number", + "roles", + "description", + ], + "type": "object", + }, + "person2": { + "additionalProperties": false, + "properties": { + "differentField": { + "type": "string", + }, + "name": { + "$ref": "#/definitions/contactPerson/properties/person1/properties/name", + }, + "phone_number": { + "$ref": "#/definitions/contactPerson/properties/person1/properties/phone_number", + }, + }, + "required": [ + "name", + "phone_number", + "differentField", + ], + "type": "object", + }, + }, + "required": [ + "person1", + "person2", + ], + "type": "object", + } + `); + }); }); });