Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove additionalProperties from intersected objects. #65

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 39 additions & 1 deletion src/parsers/intersection.ts
@@ -1,11 +1,18 @@
import { ZodIntersectionDef } from "zod";
import { JsonSchema7Type, parseDef } from "../parseDef";
import { Refs } from "../Refs";
import { JsonSchema7StringType } from "./string";

export type JsonSchema7AllOfType = {
allOf: JsonSchema7Type[];
unevaluatedProperties?: boolean;
};

const isJsonSchema7AllOfType = (type: JsonSchema7Type | JsonSchema7StringType): type is JsonSchema7AllOfType => {
if ("type" in type && type.type === "string") return false;
return 'allOf' in type;
}

export function parseIntersectionDef(
def: ZodIntersectionDef,
refs: Refs
Expand All @@ -21,5 +28,36 @@ export function parseIntersectionDef(
}),
].filter((x): x is JsonSchema7Type => !!x);

return allOf.length ? { allOf } : undefined;

let unevaluatedProperties: { unevaluatedProperties: boolean } | undefined = {unevaluatedProperties: false};
const mergedAllOf: JsonSchema7Type[] = []
// If either of the schemas is an allOf, merge them into a single allOf
allOf.forEach((schema) => {
if (isJsonSchema7AllOfType(schema)) {

mergedAllOf.push(...schema.allOf);
if (schema.unevaluatedProperties === undefined) {
// If one of the schemas has no unevaluatedProperties set,
// the merged schema should also have no unevaluatedProperties set
unevaluatedProperties = undefined;
}

} else {

let nestedSchema: JsonSchema7Type = schema
if ('additionalProperties' in schema && schema.additionalProperties === false) {
const {additionalProperties, ...rest} = schema;
nestedSchema = rest;
} else {
// As soon as one of the schemas has additionalProperties set not to false, we allow unevaluatedProperties
unevaluatedProperties = undefined;
}
mergedAllOf.push(nestedSchema);

}
});
return mergedAllOf.length ? {
allOf: mergedAllOf,
...unevaluatedProperties
Andy2003 marked this conversation as resolved.
Show resolved Hide resolved
} : undefined;
}
164 changes: 164 additions & 0 deletions test/parsers/intersection.test.ts
Expand Up @@ -38,4 +38,168 @@ describe("intersections", () => {
],
});
});

it("should intersect complex objects correctly", () => {
const schema1 = z.object({
foo: z.string()
});
const schema2 = z.object({
bar: z.string()
});
const intersection = z.intersection(schema1, schema2);
const jsonSchema = parseIntersectionDef(intersection._def, getRefs());

expect(jsonSchema).toStrictEqual({
allOf: [
{
properties: {
foo: {
type: "string",
}
},
required: ["foo"],
type: "object",
},
{
properties: {
bar: {
type: "string",
}
},
required: ["bar"],
type: "object",
}
],
unevaluatedProperties: false,
});
});

it("should return `unevaluatedProperties` only if all sub-schemas has additionalProperties set to false", () => {
const schema1 = z.object({
foo: z.string()
});
const schema2 = z.object({
bar: z.string()
}).passthrough();
const intersection = z.intersection(schema1, schema2);
const jsonSchema = parseIntersectionDef(intersection._def, getRefs());

expect(jsonSchema).toStrictEqual({
allOf: [
{
properties: {
foo: {
type: "string",
}
},
required: ["foo"],
type: "object",
},
{
properties: {
bar: {
type: "string",
}
},
required: ["bar"],
type: "object",
additionalProperties: true,
}
],
});
});

it("should intersect multiple complex objects correctly", () => {
const schema1 = z.object({
foo: z.string()
});
const schema2 = z.object({
bar: z.string()
});
const schema3 = z.object({
baz: z.string()
});
const intersection = schema1.and(schema2).and(schema3);
const jsonSchema = parseIntersectionDef(intersection._def, getRefs());

expect(jsonSchema).toStrictEqual({
allOf: [
{
properties: {
foo: {
type: "string",
}
},
required: ["foo"],
type: "object",
},
{
properties: {
bar: {
type: "string",
}
},
required: ["bar"],
type: "object",
},
{
properties: {
baz: {
type: "string",
}
},
required: ["baz"],
type: "object",
},
],
unevaluatedProperties: false,
});
});

it("should return `unevaluatedProperties` only if all of the multiple sub-schemas has additionalProperties set to false", () => {
const schema1 = z.object({
foo: z.string()
});
const schema2 = z.object({
bar: z.string()
});
const schema3 = z.object({
baz: z.string()
}).passthrough();
const intersection = schema1.and(schema2).and(schema3);
const jsonSchema = parseIntersectionDef(intersection._def, getRefs());

expect(jsonSchema).toStrictEqual({
allOf: [
{
properties: {
foo: {
type: "string",
}
},
required: ["foo"],
type: "object",
},
{
properties: {
bar: {
type: "string",
}
},
required: ["bar"],
type: "object",
},
{
additionalProperties: true,
properties: {
baz: {
type: "string",
}
},
required: ["baz"],
type: "object",
},
]
});
});
});