Skip to content

Commit 058ce14

Browse files
committed
Disable validation of nested structures
1 parent 602ca07 commit 058ce14

5 files changed

+114
-39
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ engine
6565

6666
```
6767

68-
## Validation
68+
## Schema and rules validation on construction
6969

70+
In order to prevent most common errors, `Engine` does initial validation on the schema, during construction.
71+
If no `schema` is provided to the constructor
7072

7173

7274
## Conditional logic

src/Engine.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Engine {
99
let conditions = rules.map(rule => rule.conditions);
1010
if (schema !== undefined && schema !== null) {
1111
if (isObject(schema)) {
12-
validatePredicates(conditions);
12+
validatePredicates(conditions, schema);
1313
validateConditionFields(conditions, schema)
1414
} else {
1515
toError(`Expected valid schema object, but got - ${schema}`)

src/validation.js

+31-15
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,70 @@ import predicate from "predicate";
22
import { flatMap, isObject, toError } from "./utils";
33
import { OR, AND, NOT } from './constants';
44

5-
export function predicatesFromRule(rule) {
5+
export function predicatesFromRule(rule, schema) {
66
if (isObject(rule)) {
77
return flatMap(Object.keys(rule), p => {
88
let comparable = rule[p];
99
if (isObject(comparable) || p === NOT) {
1010
if (p === OR || p === AND) {
1111
if (Array.isArray(comparable)) {
12-
return flatMap(comparable, condition => predicatesFromRule(condition));
12+
return flatMap(comparable, condition => predicatesFromRule(condition, schema));
1313
} else {
1414
toError(`"${p}" must be an array`);
1515
return [];
1616
}
1717
} else {
18-
let predicates = predicatesFromRule(comparable);
18+
let predicates = predicatesFromRule(comparable, schema);
1919
predicates.push(p);
2020
return predicates;
2121
}
2222
} else {
23-
return predicatesFromRule(p);
23+
return predicatesFromRule(p, schema);
2424
}
2525
});
2626
} else {
2727
return [rule];
2828
}
2929
}
3030

31-
export function predicatesFromCondition(condition) {
31+
export function predicatesFromCondition(condition, schema) {
3232
return flatMap(Object.keys(condition), ref => {
3333
if (ref === OR || ref === AND) {
34-
return flatMap(condition[ref], w => predicatesFromRule(w));
34+
if (Array.isArray(condition[ref])) {
35+
return flatMap(condition[ref], w => predicatesFromRule(w, schema));
36+
} else {
37+
toError(`${p} in ${JSON.stringify(condition)} must be an Array`);
38+
return [];
39+
}
3540
} else if (ref === NOT) {
36-
return predicatesFromCondition(condition[ref]);
41+
return predicatesFromCondition(condition[ref], schema);
3742
} else {
38-
return predicatesFromRule(condition[ref]);
43+
// TODO disable validation of nested structures
44+
let isField = schema.properties[ref] !== undefined;
45+
let isArray = isField && schema.properties[ref].type === "array";
46+
if (isField && !isArray) {
47+
return predicatesFromRule(condition[ref], schema);
48+
} else {
49+
return [];
50+
}
3951
}
4052
});
4153
}
4254

43-
export function listAllPredicates(conditions) {
55+
export function listAllPredicates(conditions, schema) {
4456
let allPredicates = flatMap(conditions, condition =>
45-
predicatesFromCondition(condition)
57+
predicatesFromCondition(condition, schema)
4658
);
4759
return allPredicates.filter((v, i, a) => allPredicates.indexOf(v) === i);
4860
}
4961

50-
export function listInvalidPredicates(conditions) {
51-
let refPredicates = listAllPredicates(conditions);
62+
export function listInvalidPredicates(conditions, schema) {
63+
let refPredicates = listAllPredicates(conditions, schema);
5264
return refPredicates.filter((p) => predicate[p] === undefined);
5365
}
5466

55-
export function validatePredicates(conditions) {
56-
let invalidPredicates = listInvalidPredicates(conditions);
67+
export function validatePredicates(conditions, schema) {
68+
let invalidPredicates = listInvalidPredicates(conditions, schema);
5769
if (invalidPredicates.length !== 0) {
5870
toError(`Rule contains invalid predicates ${invalidPredicates}`);
5971
}
@@ -65,8 +77,12 @@ export function fieldsFromCondition(condition) {
6577
return flatMap(Object.keys(condition), ref => {
6678
if (ref === OR || ref === AND) {
6779
return flatMap(condition[ref], w => fieldsFromCondition(w));
68-
} else {
80+
} else if (ref === NOT) {
81+
return fieldsFromCondition(condition[ref]);
82+
} else if(ref.indexOf(".") === -1) {
6983
return [ref];
84+
} else {
85+
return [];
7086
}
7187
});
7288
}

test/validation.nestedFields.test.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Engine from '../src/Engine';
2+
3+
let rules = [{
4+
conditions: {
5+
medications: {
6+
type: { equal: "D" },
7+
},
8+
"primaryMedication.type": { equal: "C" }
9+
},
10+
event: {
11+
type: 'remove'
12+
}
13+
}];
14+
15+
let schema = {
16+
definitions: {
17+
medications: {
18+
type: "object",
19+
properties: {
20+
type: { type: "string" },
21+
isLiquid: { type: "boolean" },
22+
},
23+
}
24+
},
25+
type: "object",
26+
required: [
27+
"medications",
28+
"firstName",
29+
"lastName",
30+
],
31+
properties: {
32+
firstName: {
33+
type: "string",
34+
},
35+
lastName: {
36+
type: "string",
37+
},
38+
medications: {
39+
type: "array",
40+
items: { "$ref": "#/definitions/hobby" }
41+
},
42+
primaryMedication: {
43+
"$ref": "#/definitions/hobby"
44+
}
45+
}
46+
};
47+
48+
49+
test("valid rules", () => {
50+
expect(new Engine(rules, schema)).not.toBeUndefined();
51+
52+
let engine = new Engine(rules, schema);
53+
return engine.
54+
run({ primaryMedication: { type: "C" }, medications: [ { type: "D" } ]}).
55+
then(event => expect(event.length).toEqual(1));
56+
57+
});
58+

test/validation.test.js

+21-22
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,21 @@ function conditionsFrom(rules) {
1414
return rules.map(({ conditions }) => conditions);
1515
}
1616

17+
let defSchema = { properties: {
18+
firstName: { type: "string" },
19+
password: { type: "string" },
20+
age: { type: "integer" }
21+
}};
22+
1723
test("Check predicates", () => {
1824
const conditions = conditionsFrom([
1925
{ conditions: { firstName: "epty" } },
2026
{ conditions: { age: { greater: 10 } } },
2127
{ conditions: { age: { less: 20 } } },
2228
]);
2329

24-
let predicates = listInvalidPredicates(conditions);
25-
expect(predicates).toEqual(["epty"]);
30+
expect(listAllPredicates(conditions, defSchema)).toEqual(["epty", "greater", "less"]);
31+
expect(listInvalidPredicates(conditions, defSchema)).toEqual(["epty"]);
2632
});
2733

2834
test("Two field rule ", () => {
@@ -41,7 +47,7 @@ test("Two field rule ", () => {
4147
},
4248
]);
4349

44-
let predicates = listAllPredicates(conditions);
50+
let predicates = listAllPredicates(conditions, defSchema);
4551
expect(predicates).toEqual(["empty", "greater", "less"]);
4652

4753
let fields = listAllFields(conditions);
@@ -65,7 +71,7 @@ test("3 field rule ", () => {
6571
},
6672
]);
6773

68-
let predicates = listAllPredicates(conditions);
74+
let predicates = listAllPredicates(conditions, defSchema);
6975
expect(predicates).toEqual(["empty", "greater", "less"]);
7076

7177
let fields = listAllFields(conditions);
@@ -87,19 +93,12 @@ test("invalidate predicates", () => {
8793
},
8894
]);
8995

90-
expect(listAllPredicates(invalidConditions)).toEqual(["greater", "less", "wtf"]);
91-
expect(listInvalidPredicates(invalidConditions)).toEqual(["wtf"]);
92-
expect(() => validatePredicates(invalidConditions)).toThrow();
93-
expect(() => testInProd(validatePredicates(invalidConditions))).not.toBeUndefined();
96+
expect(listAllPredicates(invalidConditions, defSchema)).toEqual(["greater", "less", "wtf"]);
97+
expect(listInvalidPredicates(invalidConditions, defSchema)).toEqual(["wtf"]);
98+
expect(() => validatePredicates(invalidConditions, defSchema)).toThrow();
99+
expect(() => testInProd(validatePredicates(invalidConditions, defSchema))).not.toBeUndefined();
94100
});
95101

96-
let schema = {
97-
properties: {
98-
firstName: { type: "string" },
99-
password: { type: "string" },
100-
},
101-
};
102-
103102
test("invalid field", () => {
104103
let invalidFieldConditions = conditionsFrom([
105104
{
@@ -116,8 +115,8 @@ test("invalid field", () => {
116115
]);
117116

118117
expect(listAllFields(invalidFieldConditions)).toEqual(["lastName", "firstName"]);
119-
expect(listInvalidFields(invalidFieldConditions, schema)).toEqual([ "lastName",]);
120-
expect(() => validateConditionFields(invalidFieldConditions, schema)).toThrow();
118+
expect(listInvalidFields(invalidFieldConditions, defSchema)).toEqual([ "lastName",]);
119+
expect(() => validateConditionFields(invalidFieldConditions, defSchema)).toThrow();
121120
});
122121

123122
test("invalid OR", () => {
@@ -130,7 +129,7 @@ test("invalid OR", () => {
130129
},
131130
]);
132131

133-
expect(() => validate(invalidOrConditions, schema)).toThrow();
132+
expect(() => validatePredicates(invalidOrConditions, defSchema)).toThrow();
134133
});
135134

136135
test("invalid field or", () => {
@@ -150,7 +149,7 @@ test("invalid field or", () => {
150149

151150
expect(() => predicatesFromRule(invalidFieldOr[0].firstName)).toThrow();
152151
expect(testInProd(() => predicatesFromRule(invalidFieldOr[0].firstName))).toEqual([]);
153-
expect(() => validatePredicates(invalidFieldOr, schema)).toThrow();
152+
expect(() => validatePredicates(invalidFieldOr, defSchema)).toThrow();
154153
});
155154

156155
test("invalid field NOT or", () => {
@@ -165,7 +164,7 @@ test("invalid field NOT or", () => {
165164
},
166165
]);
167166

168-
expect(() => validatePredicates(invalidFieldNotWithOr, schema)).toThrow();
167+
expect(() => validatePredicates(invalidFieldNotWithOr, defSchema)).toThrow();
169168
});
170169

171170
test("valid field or", () => {
@@ -182,8 +181,8 @@ test("valid field or", () => {
182181
},
183182
]);
184183

185-
expect(predicatesFromCondition(validFieldOr[0])).toEqual(["is", "is"]);
186-
expect(validateConditionFields(validFieldOr, schema)).toBeUndefined();
184+
expect(predicatesFromCondition(validFieldOr[0], defSchema)).toEqual(["is", "is"]);
185+
expect(validateConditionFields(validFieldOr, defSchema)).toBeUndefined();
187186
});
188187

189188
test("extract predicates from when with or & and", () => {

0 commit comments

Comments
 (0)