Skip to content

Commit

Permalink
Add handling for multiple intersected schemas and set unevaluatedProp…
Browse files Browse the repository at this point in the history
…erties only if all allOf schemas have additionalProperties set to false
  • Loading branch information
Andy2003 committed May 10, 2023
1 parent b8eef1e commit 08a1eea
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 17 deletions.
46 changes: 37 additions & 9 deletions src/parsers/intersection.ts
@@ -1,12 +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;
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 @@ -20,16 +26,38 @@ export function parseIntersectionDef(
...refs,
currentPath: [...refs.currentPath, "allOf", "1"],
}),
].filter((x): x is JsonSchema7Type => !!x)
.map((schema) => {
].filter((x): x is JsonSchema7Type => !!x);


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 on 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;
return {
...rest
};
nestedSchema = rest;
} else {
// As soon as one of the schemas has additionalProperties set not to false, we allow unevaluatedProperties
unevaluatedProperties = undefined;
}
return schema
});
mergedAllOf.push(nestedSchema);

return allOf.length ? {allOf, unevaluatedProperties: false} : undefined;
}
});
return mergedAllOf.length ? {
allOf: mergedAllOf,
...unevaluatedProperties
} : undefined;
}
2 changes: 0 additions & 2 deletions test/allParsers.test.ts
Expand Up @@ -66,7 +66,6 @@ describe("All Parsers tests", () => {
maxLength: 4,
},
],
unevaluatedProperties: false,
},
literal: {
type: "string",
Expand Down Expand Up @@ -391,7 +390,6 @@ describe("All Parsers tests", () => {
maxLength: 4,
},
],
unevaluatedProperties: false,
},
literal: {
type: "string",
Expand Down
139 changes: 133 additions & 6 deletions test/parsers/intersection.test.ts
Expand Up @@ -19,7 +19,6 @@ describe("intersections", () => {
maxLength: 3,
},
],
unevaluatedProperties: false,
});
});

Expand All @@ -37,7 +36,6 @@ describe("intersections", () => {
$ref: "#/allOf/0",
},
],
unevaluatedProperties: false,
});
});

Expand All @@ -56,23 +54,152 @@ describe("intersections", () => {
{
properties: {
foo: {
type: "string"
type: "string",
}
},
required: ["foo"],
type: "object"
type: "object",
},
{
properties: {
bar: {
type: "string"
type: "string",
}
},
required: ["bar"],
type: "object"
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",
},
]
});
});
});

0 comments on commit 08a1eea

Please sign in to comment.