Skip to content

Commit

Permalink
fix #257 to infer as object when only required array set
Browse files Browse the repository at this point in the history
marrowleaves authored and astahmer committed Dec 21, 2023

Verified

This commit was signed with the committer’s verified signature.
malcolmstill Malcolm Still
1 parent 70fedec commit 8c80b43
Showing 5 changed files with 70 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/selfish-pandas-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-zod-client": minor
---

Fix #257 by inferring as object when only required array defined
27 changes: 25 additions & 2 deletions lib/src/openApiToZod.ts
Original file line number Diff line number Diff line change
@@ -200,7 +200,12 @@ export function getZodSchema({ schema, ctx, meta: inheritedMeta, options }: Conv
return code.assign(`z.array(z.any())${readonly}`);
}

if (schemaType === "object" || schema.properties || schema.additionalProperties) {
if (
schemaType === "object" ||
schema.properties ||
schema.additionalProperties ||
(schema.required && Array.isArray(schema.required))
) {
// additional properties default to true if additionalPropertiesDefaultValue not provided
const additionalPropsDefaultValue = options?.additionalPropertiesDefaultValue !== undefined ? options?.additionalPropertiesDefaultValue : true;
const additionalProps = schema.additionalProperties === null || schema.additionalProperties === undefined ? additionalPropsDefaultValue : schema.additionalProperties;
@@ -259,7 +264,25 @@ export function getZodSchema({ schema, ctx, meta: inheritedMeta, options }: Conv

const partial = isPartial ? ".partial()" : "";

return code.assign(`z.object(${properties})${partial}${additionalPropsSchema}${readonly}`);
const schemaRequired = schema.required;
const schemaProperties = schema.properties;
// properties in required array but not in properties should be validated seprately, when additional props are allowed
const extraPropertiesFromRequiredArray =
additionalProps !== false && schemaRequired
? schemaProperties
? schemaRequired.filter((p) => !(p in schemaProperties))
: schemaRequired
: [];
const extraProperties =
extraPropertiesFromRequiredArray.length > 0
? "z.object({ " + extraPropertiesFromRequiredArray.map((p) => `${p}: z.unknown()`).join(", ") + " })"
: "";

return code.assign(
`z.object(${properties})${partial}${
extraProperties && ".and(" + extraProperties + ")"
}${additionalPropsSchema}${readonly}`
);
}

if (!schemaType) return code.assign("z.unknown()");
10 changes: 8 additions & 2 deletions lib/tests/array-oneOf-discriminated-union.test.ts
Original file line number Diff line number Diff line change
@@ -61,8 +61,14 @@ test("array-oneOf-discriminated-union", async () => {
const ArrayRequest = z.array(
z.discriminatedUnion("type", [
z.object({ type: z.literal("a") }).passthrough(),
z.object({ type: z.literal("b") }).passthrough(),
z
.object({ type: z.literal("a") })
.and(z.object({ a: z.unknown() }))
.passthrough(),
z
.object({ type: z.literal("b") })
.and(z.object({ b: z.unknown() }))
.passthrough(),
])
);
12 changes: 12 additions & 0 deletions lib/tests/infer-as-object-when-only-required-set.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getZodSchema } from "../src/openApiToZod";
import { test, expect } from "vitest";

test("infer-as-object-when-only-required-set", () => {
expect(
getZodSchema({
schema: {
required: ['name', 'email'],
},
})
).toMatchInlineSnapshot('"z.object({}).and(z.object({ name: z.unknown(), email: z.unknown() })).passthrough()"');
});
20 changes: 20 additions & 0 deletions lib/tests/required-additional-props-not-in-properties.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getZodSchema } from "../src/openApiToZod";
import { test, expect } from "vitest";

test("required-additional-props-not-in-properties", () => {
expect(
getZodSchema({
schema: {
properties: {
name: {
type: "string"
},
email: {
type: "string"
},
},
required: ['name', 'email', 'phone'],
},
})
).toMatchInlineSnapshot('"z.object({ name: z.string(), email: z.string() }).and(z.object({ phone: z.unknown() })).passthrough()"');
});

1 comment on commit 8c80b43

@vercel
Copy link

@vercel vercel bot commented on 8c80b43 Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.