Skip to content

Commit 13fb66e

Browse files
committed
change: dependencies to compile to dependentRequired and dependentSchemas
1 parent e084b4c commit 13fb66e

File tree

7 files changed

+121
-105
lines changed

7 files changed

+121
-105
lines changed

src/SchemaNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export interface SchemaNode extends SchemaNodeMethodsType {
9999
allOf?: SchemaNode[];
100100
anyOf?: SchemaNode[];
101101
contains?: SchemaNode;
102-
dependencies?: Record<string, SchemaNode | boolean | string[]>;
102+
dependentRequired?: Record<string, string[]>;
103103
dependentSchemas?: Record<string, SchemaNode | boolean>;
104104
else?: SchemaNode;
105105
if?: SchemaNode;

src/draft2019-09/methods/getData.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,17 @@ const TYPE: Record<string, (node: SchemaNode, data: unknown, opts: TemplateOptio
197197
});
198198
}
199199

200-
if (isObject(node.dependencies)) {
201-
Object.keys(node.dependencies).forEach((propertyName) => {
202-
const propertyValue = node.dependencies[propertyName];
200+
if (isObject(node.dependentRequired)) {
201+
Object.keys(node.dependentRequired).forEach((propertyName) => {
202+
const propertyValue = node.dependentRequired[propertyName];
203203
const hasValue = getValue(d, propertyName) !== undefined;
204-
if (hasValue && Array.isArray(propertyValue)) {
204+
if (hasValue) {
205205
propertyValue.forEach((addProperty) => {
206206
const { node: propertyNode } = node.getNodeChild(addProperty, d);
207207
if (propertyNode) {
208208
d[addProperty] = propertyNode.getData(getValue(d, addProperty), opts);
209209
}
210210
});
211-
} else if (d[propertyName] !== undefined && isSchemaNode(propertyValue)) {
212-
const dependencyData = propertyValue.getData(data ?? d, opts);
213-
Object.assign(d, dependencyData);
214211
}
215212
// if false and removeInvalidData => remove from data
216213
});

src/keywords/dependencies.ts

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { isSchemaNode, SchemaNode } from "../types";
22
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
3-
import { getValue } from "../utils/getValue";
43
import { isObject } from "../utils/isObject";
5-
import { validateNode } from "../validateNode";
64
import { mergeNode } from "../mergeNode";
5+
import { hasProperty } from "../utils/hasProperty";
6+
import { validateDependentRequired } from "./dependentRequired";
7+
import { validateDependentSchemas } from "./dependentSchemas";
78

89
export const dependenciesKeyword: Keyword = {
910
id: "dependencies",
1011
keyword: "dependencies",
1112
parse: parseDependencies,
12-
addReduce: (node) => node.dependencies != null,
13+
order: -9,
14+
addReduce: (node) => node.schema.dependencies != null,
1315
reduce: reduceDependencies,
14-
addValidate: (node) => node.dependencies != null,
16+
addValidate: (node) => node.schema.dependencies != null,
1517
validate: validateDependencies
1618
};
1719

@@ -20,44 +22,72 @@ export function parseDependencies(node: SchemaNode) {
2022
if (!isObject(dependencies)) {
2123
return;
2224
}
23-
2425
const schemas = Object.keys(dependencies);
25-
if (schemas.length === 0) {
26-
return;
27-
}
28-
node.dependencies = {};
2926
schemas.forEach((property) => {
30-
const schema = dependencies[property];
27+
const schema = dependencies[property] as string[];
3128
if (isObject(schema) || typeof schema === "boolean") {
32-
node.dependencies[property] = node.compileSchema(
33-
// @ts-expect-error boolean schema
29+
node.dependentSchemas = node.dependentSchemas ?? {};
30+
node.dependentSchemas[property] = node.compileSchema(
3431
schema,
3532
`${node.spointer}/dependencies/${property}`,
3633
`${node.schemaId}/dependencies/${property}`
3734
);
38-
} else if (Array.isArray(schema)) {
39-
node.dependencies[property] = schema;
35+
} else {
36+
node.dependentRequired = node.dependentRequired ?? {};
37+
node.dependentRequired[property] = schema;
4038
}
4139
});
4240
}
4341

4442
export function reduceDependencies({ node, data, key, path }: JsonSchemaReducerParams) {
45-
if (!isObject(data) || node.dependencies == null) {
43+
if (!isObject(data)) {
4644
// @todo remove dependentSchemas
4745
return node;
4846
}
4947

48+
if (node.dependentRequired == null && node.dependentSchemas == null) {
49+
return node;
50+
}
51+
5052
let workingNode = node.compileSchema(node.schema, node.spointer, node.schemaId);
51-
const { dependencies } = node;
5253
let required = workingNode.schema.required ?? [];
53-
Object.keys(dependencies).forEach((propertyName) => {
54-
if (isSchemaNode(dependencies[propertyName])) {
55-
const reducedDependency = dependencies[propertyName].reduceNode(data, { key, path }).node;
54+
55+
if (node.dependentRequired) {
56+
Object.keys(node.dependentRequired).forEach((propertyName) => {
57+
if (!hasProperty(data, propertyName) && !required.includes(propertyName)) {
58+
console.log(propertyName, "abort", required);
59+
return;
60+
}
61+
if (node.dependentRequired[propertyName] == null) {
62+
return;
63+
}
64+
required.push(...node.dependentRequired[propertyName]);
65+
});
66+
}
67+
68+
if (node.dependentSchemas) {
69+
Object.keys(node.dependentSchemas).forEach((propertyName) => {
70+
if (!hasProperty(data, propertyName) && !required.includes(propertyName)) {
71+
return true;
72+
}
73+
const dependency = node.dependentSchemas[propertyName];
74+
if (!isSchemaNode(dependency)) {
75+
return true;
76+
}
77+
78+
// @note pass on updated required-list to resolve nested dependencies. This is currently supported,
79+
// but probably not how json-schema spec defines this behaviour (resolve only within sub-schema)
80+
const reducedDependency = { ...dependency, schema: { ...dependency.schema, required } }.reduceNode(data, {
81+
key,
82+
path
83+
}).node;
84+
5685
workingNode = mergeNode(workingNode, reducedDependency);
57-
} else if (Array.isArray(dependencies[propertyName]) && data[propertyName] !== undefined) {
58-
required.push(...dependencies[propertyName]);
59-
}
60-
});
86+
if (workingNode.schema.required) {
87+
required.push(...workingNode.schema.required);
88+
}
89+
});
90+
}
6191

6292
if (workingNode === node) {
6393
return node;
@@ -72,48 +102,25 @@ export function reduceDependencies({ node, data, key, path }: JsonSchemaReducerP
72102
}
73103

74104
required = workingNode.schema.required ? workingNode.schema.required.concat(...required) : required;
105+
required = required.filter((r: string, index: number, list: string[]) => list.indexOf(r) === index);
106+
workingNode = mergeNode(workingNode, workingNode, "dependencies");
75107
return workingNode.compileSchema({ ...workingNode.schema, required }, workingNode.spointer, workingNode.schemaId);
76108
}
77109

78110
function validateDependencies({ node, data, pointer, path }: JsonSchemaValidatorParams) {
79111
if (!isObject(data)) {
80112
return undefined;
81113
}
82-
83-
const errors: ValidationResult[] = [];
84-
const dependencies = node.dependencies;
85-
Object.keys(data).forEach((property) => {
86-
const propertyValue = dependencies[property];
87-
if (propertyValue === undefined) {
88-
return;
89-
}
90-
// @draft >= 6 boolean schema
91-
if (propertyValue === true) {
92-
return;
93-
}
94-
if (propertyValue === false) {
95-
errors.push(node.createError("missing-dependency-error", { pointer, schema: node.schema, value: data }));
96-
return;
97-
}
98-
99-
if (Array.isArray(propertyValue)) {
100-
propertyValue
101-
.filter((dependency: any) => getValue(data, dependency) === undefined)
102-
.forEach((missingProperty: any) =>
103-
errors.push(
104-
node.createError("missing-dependency-error", {
105-
missingProperty,
106-
pointer: `${pointer}/${missingProperty}`,
107-
schema: node.schema,
108-
value: data
109-
})
110-
)
111-
);
112-
} else if (isSchemaNode(propertyValue)) {
113-
errors.push(...validateNode(propertyValue, data, pointer, path));
114-
} else {
115-
throw new Error(`Invalid dependency definition for ${pointer}/${property}. Must be string[] or schema`);
114+
let errors: ValidationResult[];
115+
if (node.dependentRequired) {
116+
errors = validateDependentRequired({ node, data, pointer, path }) ?? [];
117+
}
118+
if (node.dependentSchemas) {
119+
const schemaErrors = validateDependentSchemas({ node, data, pointer, path });
120+
if (schemaErrors) {
121+
errors = errors ?? [];
122+
errors.push(...schemaErrors);
116123
}
117-
});
124+
}
118125
return errors;
119126
}

src/keywords/dependentRequired.ts

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,62 @@
1-
import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2-
import { JsonError } from "../types";
1+
import { Keyword, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
2+
import { JsonError, SchemaNode } from "../types";
33
import { isObject } from "../utils/isObject";
44

55
export const dependentRequiredKeyword: Keyword = {
66
id: "dependentRequired",
77
keyword: "dependentRequired",
8+
parse: parseDependentRequired,
89
addValidate: (node) => isObject(node.schema.dependentRequired),
910
validate: validateDependentRequired
1011
};
1112

12-
export function validateDependentRequired({ node, data, pointer = "#" }: JsonSchemaValidatorParams) {
13+
export function parseDependentRequired(node: SchemaNode) {
14+
if (!isObject(node.schema.dependentRequired)) {
15+
return;
16+
}
17+
node.dependentRequired = (node.schema.dependentRequired as Record<string, string[]>) ?? {};
18+
}
19+
20+
export function validateDependentRequired({
21+
node,
22+
data,
23+
pointer = "#"
24+
}: JsonSchemaValidatorParams): ValidationResult[] {
1325
if (!isObject(data)) {
1426
return undefined;
1527
}
16-
const { schema } = node;
17-
const dependentRequired = schema.dependentRequired;
28+
const { dependentRequired } = node;
1829
const errors: JsonError[] = [];
19-
Object.keys(data).forEach((property) => {
20-
const dependencies = dependentRequired[property];
21-
// @draft >= 6 boolean schema
22-
if (dependencies === true) {
23-
return;
24-
}
25-
if (dependencies === false) {
26-
errors.push(node.createError("missing-dependency-error", { pointer, schema, value: data }));
27-
return;
28-
}
29-
if (!Array.isArray(dependencies)) {
30-
return;
31-
}
32-
for (let i = 0, l = dependencies.length; i < l; i += 1) {
33-
if (data[dependencies[i]] === undefined) {
34-
errors.push(
35-
node.createError("missing-dependency-error", {
36-
missingProperty: dependencies[i],
37-
pointer,
38-
schema,
39-
value: data
40-
})
41-
);
30+
if (dependentRequired) {
31+
Object.keys(data).forEach((property) => {
32+
const dependencies = dependentRequired[property];
33+
// @draft >= 6 boolean schema
34+
// @ts-expect-error boolean schema
35+
if (dependencies === true) {
36+
return;
37+
}
38+
// @ts-expect-error boolean schema
39+
if (dependencies === false) {
40+
// @ts-expect-error boolean schema
41+
errors.push(node.createError("missing-dependency-error", { pointer, schema, value: data }));
42+
return;
4243
}
43-
}
44-
});
44+
if (!Array.isArray(dependencies)) {
45+
return;
46+
}
47+
for (let i = 0, l = dependencies.length; i < l; i += 1) {
48+
if (data[dependencies[i]] === undefined) {
49+
errors.push(
50+
node.createError("missing-dependency-error", {
51+
missingProperty: dependencies[i],
52+
pointer,
53+
schema: node.schema,
54+
value: data
55+
})
56+
);
57+
}
58+
}
59+
});
60+
}
4561
return errors;
4662
}

src/keywords/dependentSchemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function reduceDependentSchemas({ node, data }: JsonSchemaReducerParams)
7474

7575
export function validateDependentSchemas({ node, data, pointer, path }: JsonSchemaValidatorParams) {
7676
const { schema, dependentSchemas } = node;
77-
if (!isObject(data)) {
77+
if (!isObject(data) || dependentSchemas == null) {
7878
return undefined;
7979
}
8080
const errors: ValidationResult[] = [];

src/methods/getData.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,20 +188,17 @@ const TYPE: Record<string, (node: SchemaNode, data: unknown, opts: TemplateOptio
188188
});
189189
}
190190

191-
if (isObject(node.dependencies)) {
192-
Object.keys(node.dependencies).forEach((propertyName) => {
193-
const propertyValue = node.dependencies[propertyName];
191+
if (isObject(node.dependentRequired)) {
192+
Object.keys(node.dependentRequired).forEach((propertyName) => {
193+
const propertyValue = node.dependentRequired[propertyName];
194194
const hasValue = getValue(d, propertyName) !== undefined;
195-
if (hasValue && Array.isArray(propertyValue)) {
195+
if (hasValue) {
196196
propertyValue.forEach((addProperty) => {
197197
const { node: propertyNode } = node.getNodeChild(addProperty, d);
198198
if (propertyNode) {
199199
d[addProperty] = propertyNode.getData(getValue(d, addProperty), opts);
200200
}
201201
});
202-
} else if (d[propertyName] !== undefined && isSchemaNode(propertyValue)) {
203-
const dependencyData = propertyValue.getData(data ?? d, opts);
204-
Object.assign(d, dependencyData);
205202
}
206203
// if false and removeInvalidData => remove from data
207204
});

src/methods/toSchemaNodes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export function toSchemaNodes(node: SchemaNode | unknown, nodeList: SchemaNode[]
2525
eachItem(nodeList, node.allOf);
2626
eachItem(nodeList, node.anyOf);
2727
node.contains && toSchemaNodes(node.contains, nodeList);
28-
eachProperty(nodeList, node.dependencies);
2928
eachProperty(nodeList, node.dependentSchemas);
3029
node.if && toSchemaNodes(node.if, nodeList);
3130
node.else && toSchemaNodes(node.else, nodeList);

0 commit comments

Comments
 (0)