Skip to content

Commit c8b0fed

Browse files
author
Sascha Goldhofer
committed
fix: issue#44 merging unresolved allOf statements
1 parent 040bf01 commit c8b0fed

File tree

6 files changed

+137
-3
lines changed

6 files changed

+137
-3
lines changed

lib/features/allOf.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export function resolveAllOf(
3838
}
3939

4040
/**
41+
* @attention: subschemas have to be resolved upfront (e.g. if-else that do not apply)
4142
* Merge all allOf sub schema into a single schema. Returns undefined for
4243
* missing allOf definition.
4344
*

lib/features/if.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ export function resolveIfSchema(
1616
schema: JsonSchema,
1717
data: unknown
1818
): JsonSchema | undefined {
19+
if (schema.if == null) {
20+
return undefined;
21+
}
1922
if (schema.if === false) {
2023
return schema.else;
2124
}
25+
2226
if (schema.if && (schema.then || schema.else)) {
2327
const ifErrors = draft.validate(data, draft.resolveRef(schema.if));
2428
if (ifErrors.length === 0 && schema.then) {

lib/resolveDynamicSchema.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,25 @@ export function resolveDynamicSchema(
5454
}
5555

5656
// @feature allOf
57-
const allOfSchema = mergeAllOfSchema(draft, schema);
58-
if (allOfSchema) {
59-
resolvedSchema = mergeSchema(resolvedSchema ?? {}, allOfSchema);
57+
if (Array.isArray(schema.allOf)) {
58+
const allOf = schema.allOf.map((s) => {
59+
// before merging allOf schema we need to resolve all subschemas
60+
// if not, we would wrongly merge oneOf, if-then statements, etc
61+
if (isDynamicSchema(s)) {
62+
// copy of reduceSchema
63+
let result = resolveDynamicSchema(draft, s, data, pointer);
64+
if (result) {
65+
result = mergeSchema(s, result);
66+
return omit(result, ...toOmit);
67+
}
68+
return undefined;
69+
}
70+
return s;
71+
});
72+
if (allOf.length > 0) {
73+
const allOfSchema = mergeAllOfSchema(draft, { allOf });
74+
resolvedSchema = mergeSchema(resolvedSchema ?? {}, allOfSchema);
75+
}
6076
}
6177

6278
// @feature anyOf
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect } from "chai";
2+
import { Draft07 as Draft } from "../../../lib/draft07";
3+
4+
describe("issue#44 - chained negative logic", () => {
5+
let draft: Draft;
6+
beforeEach(() => {
7+
draft = new Draft({
8+
type: "object",
9+
properties: {
10+
animal_species: {
11+
enum: ["cat", "eagle"]
12+
},
13+
diet_type: {
14+
enum: ["carnivore", "omnivore"]
15+
},
16+
habitat_type: {
17+
enum: ["forest", "mountain"]
18+
}
19+
},
20+
allOf: [
21+
{
22+
if: {
23+
not: {
24+
properties: {
25+
animal_species: { const: "cat" }
26+
}
27+
}
28+
},
29+
then: {
30+
properties: {
31+
diet_type: { const: "carnivore" }
32+
}
33+
}
34+
},
35+
{
36+
if: {
37+
not: {
38+
properties: {
39+
diet_type: { const: "carnivore" },
40+
animal_species: { const: "cat" }
41+
}
42+
}
43+
},
44+
then: {
45+
properties: {
46+
habitat_type: { const: "mountain" }
47+
}
48+
}
49+
}
50+
]
51+
});
52+
});
53+
54+
it("should validate input data", () => {
55+
const errors = draft.validate({
56+
animal_species: "cat",
57+
diet_type: "omnivore",
58+
habitat_type: "mountain"
59+
});
60+
61+
expect(errors).to.have.length(0);
62+
});
63+
});

test/unit/reduceSchema.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,54 @@ describe("reduceSchema", () => {
66
let draft: Draft;
77
beforeEach(() => (draft = new Draft()));
88

9+
describe("allOf", () => {
10+
it("should iteratively resolve allOf before merging (issue#44)", () => {
11+
const staticSchema = reduceSchema(
12+
draft,
13+
{
14+
type: "object",
15+
properties: {
16+
trigger: { type: "boolean" }
17+
},
18+
allOf: [
19+
{
20+
if: {
21+
not: {
22+
properties: {
23+
trigger: { type: "boolean", const: true }
24+
}
25+
}
26+
},
27+
then: {
28+
properties: {
29+
trigger: { type: "boolean", const: false }
30+
}
31+
}
32+
},
33+
{
34+
if: {
35+
not: {
36+
properties: {
37+
trigger: { type: "boolean", const: false }
38+
}
39+
}
40+
}
41+
}
42+
]
43+
},
44+
{ trigger: true },
45+
"#"
46+
);
47+
48+
assert.deepEqual(staticSchema, {
49+
type: "object",
50+
properties: {
51+
trigger: { type: "boolean" }
52+
}
53+
});
54+
});
55+
});
56+
957
describe("oneOf", () => {
1058
it("should return oneOf source schema for resolved oneOf object", () => {
1159
const staticSchema = reduceSchema(

test/unit/resolveAllOf.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ describe("resolveAllOf", () => {
165165
}
166166
});
167167
});
168+
168169
it("should return 'then'-schema when 'if' does match", () => {
169170
const schema = resolveAllOf(
170171
draft,
@@ -200,6 +201,7 @@ describe("resolveAllOf", () => {
200201
}
201202
});
202203
});
204+
203205
it("should merge multiple 'then'-schema", () => {
204206
const schema = resolveAllOf(
205207
draft,

0 commit comments

Comments
 (0)