Skip to content
This repository was archived by the owner on Jun 10, 2024. It is now read-only.

Commit ba57ffd

Browse files
committed
New: Default validation strategies
1 parent d3afa94 commit ba57ffd

File tree

6 files changed

+398
-8
lines changed

6 files changed

+398
-8
lines changed

‎README.md‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,48 @@ const result = {
100100

101101
## Tips and Tricks
102102

103+
### Named merge strategies
104+
105+
Instead of specifying a `merge()` method, you can specify one of the following strings to use a default merge strategy:
106+
107+
* `"assign"` - use `Object.assign()` to merge the two values into one object.
108+
* `"overwrite"` - the second value always replaces the first.
109+
* `"replace"` - the second value replaces the first if the second is not `undefined`.
110+
111+
For example:
112+
113+
```js
114+
const schema = new ObjectSchema({
115+
name: {
116+
merge: "replace",
117+
validate() {}
118+
}
119+
});
120+
```
121+
122+
### Named validation strategies
123+
124+
Instead of specifying a `validate()` method, you can specify one of the following strings to use a default validation strategy:
125+
126+
* `"array"` - value must be an array.
127+
* `"boolean"` - value must be a boolean.
128+
* `"number"` - value must be a number.
129+
* `"object"` - value must be an object.
130+
* `"object?"` - value must be an object or null.
131+
* `"string"` - value must be a string.
132+
* `"string!"` - value must be a non-empty string.
133+
134+
For example:
135+
136+
```js
137+
const schema = new ObjectSchema({
138+
name: {
139+
merge: "replace",
140+
validate: "string"
141+
}
142+
});
143+
```
144+
103145
### Remove Keys During Merge
104146

105147
If the merge strategy for a key returns `undefined`, then the key will not appear in the final object. For example:
@@ -161,4 +203,4 @@ In this example, even though `date` is an optional key, it is required to be pre
161203

162204
## License
163205

164-
BSD 3-Clause
206+
BSD 3-Clause

‎src/index.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
exports.ObjectSchema = require("./object-schema").ObjectSchema;
66
exports.MergeStrategy = require("./merge-strategy").MergeStrategy;
7+
exports.ValidationStrategy = require("./validation-strategy").ValidationStrategy;

‎src/object-schema.js‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//-----------------------------------------------------------------------------
1010

1111
const { MergeStrategy } = require("./merge-strategy");
12+
const { ValidationStrategy } = require("./validation-strategy");
1213

1314
//-----------------------------------------------------------------------------
1415
// Private
@@ -39,15 +40,15 @@ function validateDefinition(name, strategy) {
3940
if (!(strategy.merge in MergeStrategy)) {
4041
throw new TypeError(`Definition for key "${name}" missing valid merge strategy.`);
4142
}
42-
43-
return;
44-
}
45-
46-
if (typeof strategy.merge !== "function") {
43+
} else if (typeof strategy.merge !== "function") {
4744
throw new TypeError(`Definition for key "${name}" must have a merge property.`);
4845
}
4946

50-
if (typeof strategy.validate !== "function") {
47+
if (typeof strategy.validate === "string") {
48+
if (!(strategy.validate in ValidationStrategy)) {
49+
throw new TypeError(`Definition for key "${name}" missing valid validation strategy.`);
50+
}
51+
} else if (typeof strategy.validate !== "function") {
5152
throw new TypeError(`Definition for key "${name}" must have a validate() method.`);
5253
}
5354
}
@@ -182,7 +183,10 @@ class ObjectSchema {
182183

183184
// now apply remaining validation strategy
184185
try {
185-
strategy.validate.call(strategy, object[key]);
186+
const validate = (typeof strategy.validate === "string")
187+
? ValidationStrategy[strategy.validate]
188+
: strategy.validate;
189+
validate.call(strategy, object[key]);
186190
} catch (ex) {
187191
ex.message = `Key "${key}": ` + ex.message;
188192
throw ex;

‎src/validation-strategy.js‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @filedescription Validation Strategy
3+
*/
4+
5+
"use strict";
6+
7+
//-----------------------------------------------------------------------------
8+
// Class
9+
//-----------------------------------------------------------------------------
10+
11+
/**
12+
* Container class for several different validation strategies.
13+
*/
14+
class ValidationStrategy {
15+
16+
/**
17+
* Validates that a value is an array.
18+
* @param {*} value The value to validate.
19+
* @returns {void}
20+
* @throws {TypeError} If the value is invalid.
21+
*/
22+
static array(value) {
23+
if (!Array.isArray(value)) {
24+
throw new TypeError("Expected an array.");
25+
}
26+
}
27+
28+
/**
29+
* Validates that a value is a boolean.
30+
* @param {*} value The value to validate.
31+
* @returns {void}
32+
* @throws {TypeError} If the value is invalid.
33+
*/
34+
static boolean(value) {
35+
if (typeof value !== "boolean") {
36+
throw new TypeError("Expected a Boolean.");
37+
}
38+
}
39+
40+
/**
41+
* Validates that a value is a number.
42+
* @param {*} value The value to validate.
43+
* @returns {void}
44+
* @throws {TypeError} If the value is invalid.
45+
*/
46+
static number(value) {
47+
if (typeof value !== "number") {
48+
throw new TypeError("Expected a number.");
49+
}
50+
}
51+
52+
/**
53+
* Validates that a value is a object.
54+
* @param {*} value The value to validate.
55+
* @returns {void}
56+
* @throws {TypeError} If the value is invalid.
57+
*/
58+
static object(value) {
59+
if (!value || typeof value !== "object") {
60+
throw new TypeError("Expected an object.");
61+
}
62+
}
63+
64+
/**
65+
* Validates that a value is a object or null.
66+
* @param {*} value The value to validate.
67+
* @returns {void}
68+
* @throws {TypeError} If the value is invalid.
69+
*/
70+
static "object?"(value) {
71+
if (typeof value !== "object") {
72+
throw new TypeError("Expected an object or null.");
73+
}
74+
}
75+
76+
/**
77+
* Validates that a value is a string.
78+
* @param {*} value The value to validate.
79+
* @returns {void}
80+
* @throws {TypeError} If the value is invalid.
81+
*/
82+
static string(value) {
83+
if (typeof value !== "string") {
84+
throw new TypeError("Expected a string.");
85+
}
86+
}
87+
88+
/**
89+
* Validates that a value is a non-empty string.
90+
* @param {*} value The value to validate.
91+
* @returns {void}
92+
* @throws {TypeError} If the value is invalid.
93+
*/
94+
static "string!"(value) {
95+
if (typeof value !== "string" || value.length === 0) {
96+
throw new TypeError("Expected a non-empty string.");
97+
}
98+
}
99+
100+
}
101+
102+
exports.ValidationStrategy = ValidationStrategy;

‎tests/object-schema.js‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,28 @@ describe("ObjectSchema", () => {
5959
}, /Definition for key "foo" must have a validate\(\) method/);
6060
});
6161

62+
it("should throw an error when merge is an invalid string", () => {
63+
assert.throws(() => {
64+
new ObjectSchema({
65+
foo: {
66+
merge: "bar",
67+
validate() { }
68+
}
69+
});
70+
}, /key "foo" missing valid merge strategy/);
71+
});
72+
73+
it("should throw an error when validate is an invalid string", () => {
74+
assert.throws(() => {
75+
new ObjectSchema({
76+
foo: {
77+
merge: "assign",
78+
validate: "s"
79+
}
80+
});
81+
}, /key "foo" missing valid validation strategy/);
82+
});
83+
6284
});
6385

6486

@@ -85,6 +107,7 @@ describe("ObjectSchema", () => {
85107
assert.throws(() => {
86108
schema.merge({ foo: true }, { foo: true });
87109
}, /Key "foo": Boom!/);
110+
88111
});
89112

90113
it("should call the merge() strategy for one key when called", () => {
@@ -335,6 +358,38 @@ describe("ObjectSchema", () => {
335358
}, /Key "foo": Invalid key/);
336359
});
337360

361+
it("should throw an error when an expected key is found but is invalid with a string validator", () => {
362+
363+
schema = new ObjectSchema({
364+
foo: {
365+
merge() {
366+
return "bar";
367+
},
368+
validate: "string"
369+
}
370+
});
371+
372+
assert.throws(() => {
373+
schema.validate({ foo: true });
374+
}, /Key "foo": Expected a string/);
375+
});
376+
377+
it("should throw an error when an expected key is found but is invalid with a number validator", () => {
378+
379+
schema = new ObjectSchema({
380+
foo: {
381+
merge() {
382+
return "bar";
383+
},
384+
validate: "number"
385+
}
386+
});
387+
388+
assert.throws(() => {
389+
schema.validate({ foo: true });
390+
}, /Key "foo": Expected a number/);
391+
});
392+
338393
it("should throw an error when a required key is missing", () => {
339394

340395
schema = new ObjectSchema({

0 commit comments

Comments
 (0)