Skip to content

Commit aea2d12

Browse files
fix(zod): warn on optional field usage (#1469)
1 parent b5a5ee4 commit aea2d12

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

src/_vendor/zod-to-json-schema/parsers/object.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,20 @@ export function parseObjectDef(def: ZodObjectDef, refs: Refs) {
3939
[propName, propDef],
4040
) => {
4141
if (propDef === undefined || propDef._def === undefined) return acc;
42+
const propertyPath = [...refs.currentPath, 'properties', propName];
4243
const parsedDef = parseDef(propDef._def, {
4344
...refs,
44-
currentPath: [...refs.currentPath, 'properties', propName],
45-
propertyPath: [...refs.currentPath, 'properties', propName],
45+
currentPath: propertyPath,
46+
propertyPath,
4647
});
4748
if (parsedDef === undefined) return acc;
49+
if (refs.openaiStrictMode && propDef.isOptional() && !propDef.isNullable()) {
50+
console.warn(
51+
`Zod field at \`${propertyPath.join(
52+
'/',
53+
)}\` uses \`.optional()\` without \`.nullable()\` which is not supported by the API. See: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#all-fields-must-be-required\nThis will become an error in a future version of the SDK.`,
54+
);
55+
}
4856
return {
4957
properties: {
5058
...acc.properties,

tests/helpers/zod.test.ts

+52
Original file line numberDiff line numberDiff line change
@@ -278,4 +278,56 @@ describe('zodResponseFormat', () => {
278278
}
279279
`);
280280
});
281+
282+
it('warns on optional fields', () => {
283+
const consoleSpy = jest.spyOn(console, 'warn');
284+
consoleSpy.mockClear();
285+
286+
zodResponseFormat(
287+
z.object({
288+
required: z.string(),
289+
optional: z.string().optional(),
290+
optional_and_nullable: z.string().optional().nullable(),
291+
}),
292+
'schema',
293+
);
294+
295+
expect(consoleSpy).toHaveBeenCalledWith(
296+
'Zod field at `#/definitions/schema/properties/optional` uses `.optional()` without `.nullable()` which is not supported by the API. See: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#all-fields-must-be-required\nThis will become an error in a future version of the SDK.',
297+
);
298+
expect(consoleSpy).toHaveBeenCalledTimes(1);
299+
});
300+
301+
it('warns on nested optional fields', () => {
302+
const consoleSpy = jest.spyOn(console, 'warn');
303+
consoleSpy.mockClear();
304+
305+
zodResponseFormat(
306+
z.object({
307+
foo: z.object({ bar: z.array(z.object({ can_be_missing: z.boolean().optional() })) }),
308+
}),
309+
'schema',
310+
);
311+
312+
expect(consoleSpy).toHaveBeenCalledWith(
313+
expect.stringContaining(
314+
'Zod field at `#/definitions/schema/properties/foo/properties/bar/items/properties/can_be_missing` uses `.optional()`',
315+
),
316+
);
317+
expect(consoleSpy).toHaveBeenCalledTimes(1);
318+
});
319+
320+
it('does not warn on union nullable fields', () => {
321+
const consoleSpy = jest.spyOn(console, 'warn');
322+
consoleSpy.mockClear();
323+
324+
zodResponseFormat(
325+
z.object({
326+
union: z.union([z.string(), z.null()]).optional(),
327+
}),
328+
'schema',
329+
);
330+
331+
expect(consoleSpy).toHaveBeenCalledTimes(0);
332+
});
281333
});

0 commit comments

Comments
 (0)