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
  • Loading branch information
marrowleaves authored and astahmer committed Dec 21, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
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.