@humanwhocodes/object-schema
Advanced tools
Comparing version 0.2.0 to 1.0.0
{ | ||
"name": "@humanwhocodes/object-schema", | ||
"version": "0.2.0", | ||
"version": "1.0.0", | ||
"description": "An object schema merger/validator", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
@@ -32,11 +32,6 @@ # JavaScript ObjectSchema Package | ||
const schema = new ObjectSchema(); | ||
const schema = new ObjectSchema({ | ||
// there is also schema.defineStrategy() to just define one at a time | ||
schema.defineStrategies([ | ||
// define a strategy for the "downloads" key | ||
{ | ||
name: "downloads", | ||
// define a definition for the "downloads" key | ||
downloads: { | ||
required: true, | ||
@@ -54,4 +49,3 @@ merge(value1, value2) { | ||
// define a strategy for the "versions" key | ||
{ | ||
name: "versions", | ||
version: { | ||
required: true, | ||
@@ -67,3 +61,3 @@ merge(value1, value2) { | ||
} | ||
]); | ||
}); | ||
@@ -117,13 +111,12 @@ const record1 = { | ||
```js | ||
const schema = new ObjectSchema(); | ||
schema.defineStrategy({ | ||
name: "date", | ||
merge() { | ||
return undefined; | ||
}, | ||
validate(value) { | ||
Date.parse(value); // throws an error when invalid | ||
const schema = new ObjectSchema({ | ||
date: { | ||
merge() { | ||
return undefined; | ||
}, | ||
validate(value) { | ||
Date.parse(value); // throws an error when invalid | ||
} | ||
} | ||
}) | ||
}); | ||
@@ -145,5 +138,4 @@ const object1 = { date: "5/5/2005" }; | ||
schema.defineStrategies([ | ||
{ | ||
name: "date", | ||
const schema = new ObjectSchema({ | ||
date: { | ||
merge() { | ||
@@ -156,6 +148,3 @@ return undefined; | ||
}, | ||
// the key "time" requires that "date" be present | ||
{ | ||
name: "time", | ||
time: { | ||
requires: ["date"], | ||
@@ -169,5 +158,4 @@ merge(first, second) { | ||
} | ||
}); | ||
]); | ||
// throws error: Key "time" requires keys "date" | ||
@@ -174,0 +162,0 @@ schema.validate({ |
@@ -14,2 +14,30 @@ /** | ||
/** | ||
* Validates a schema strategy. | ||
* @param {string} name The name of the key this strategy is for. | ||
* @param {Object} strategy The strategy for the object key. | ||
* @param {boolean} [strategy.required=true] Whether the key is required. | ||
* @param {string[]} [strategy.requires] Other keys that are required when | ||
* this key is present. | ||
* @param {Function} strategy.merge A method to call when merging two objects | ||
* with the same key. | ||
* @param {Function} strategy.validate A method to call when validating an | ||
* object with the key. | ||
* @returns {void} | ||
* @throws {Error} When the strategy is missing a name. | ||
* @throws {Error} When the strategy is missing a merge() method. | ||
* @throws {Error} When the strategy is missing a validate() method. | ||
*/ | ||
function validateDefinition(name, strategy) { | ||
if (typeof strategy.merge !== "function") { | ||
throw new Error(`Definition for key "${name}" must have a merge() method.`); | ||
} | ||
if (typeof strategy.validate !== "function") { | ||
throw new Error(`Definition for key "${name}" must have a validate() method.`); | ||
} | ||
} | ||
//----------------------------------------------------------------------------- | ||
@@ -27,4 +55,8 @@ // Class | ||
*/ | ||
constructor() { | ||
constructor(definitions) { | ||
if (!definitions) { | ||
throw new Error("Schema definitions missing."); | ||
} | ||
/** | ||
@@ -39,62 +71,20 @@ * Track all strategies in the schema by key. | ||
* Separately track any keys that are required for faster validation. | ||
* @type {Array} | ||
* @type {Map} | ||
* @property requiredKeys | ||
*/ | ||
this[requiredKeys] = []; | ||
} | ||
this[requiredKeys] = new Map(); | ||
/** | ||
* Defines a new strategy for an object key. | ||
* @param {Object} strategy The strategy for the object key. | ||
* @param {string} strategy.name The name of the key the strategy applies to. | ||
* @param {boolean} [strategy.required=true] Whether the key is required. | ||
* @param {string[]} [strategy.requires] Other keys that are required when | ||
* this key is present. | ||
* @param {Function} strategy.merge A method to call when merging two objects | ||
* with the same key. | ||
* @param {Function} strategy.validate A method to call when validating an | ||
* object with the key. | ||
* @returns {void} | ||
* @throws {Error} When the strategy is missing a name. | ||
* @throws {Error} When the strategy is missing a merge() method. | ||
* @throws {Error} When the strategy is missing a validate() method. | ||
*/ | ||
defineStrategy(strategy) { | ||
// add in all strategies | ||
for (const key of Object.keys(definitions)) { | ||
validateDefinition(key, definitions[key]); | ||
if (typeof strategy.name !== "string") { | ||
throw new Error("Strategy must have a \"name\" property."); | ||
} | ||
this[strategies].set(key, definitions[key]); | ||
if (typeof strategy.merge !== "function") { | ||
throw new Error(`Strategy for key "${strategy.name}" must have a merge() method.`); | ||
if (definitions[key].required) { | ||
this[requiredKeys].set(key, definitions[key]); | ||
} | ||
} | ||
if (typeof strategy.validate !== "function") { | ||
throw new Error(`Strategy for key "${strategy.name}" must have a validate() method.`); | ||
} | ||
this[strategies].set(strategy.name, strategy); | ||
if (strategy.required) { | ||
this[requiredKeys].push(strategy); | ||
} | ||
} | ||
/** | ||
* Defines multiple strategies at the same time. This is helpful for | ||
* defining an array of strategies elsewhere and just passing the | ||
* array directly instead of making individual method calls for each one. | ||
* @param {Strategy[]} strategies An array of strategies to define. | ||
* @returns {void} | ||
* @throws {Error} When the strategy is missing a name. | ||
* @throws {Error} When the strategy is missing a merge() method. | ||
* @throws {Error} When the strategy is missing a validate() method. | ||
*/ | ||
defineStrategies(strategies) { | ||
for (const strategy of strategies) { | ||
this.defineStrategy(strategy); | ||
} | ||
} | ||
/** | ||
* Determines if a strategy has been registered for the given object key. | ||
@@ -104,3 +94,3 @@ * @param {string} key The object key to find a strategy for. | ||
*/ | ||
hasStrategyFor(key) { | ||
hasKey(key) { | ||
return this[strategies].has(key); | ||
@@ -152,3 +142,3 @@ } | ||
// check to see if the key is defined | ||
if (!this.hasStrategyFor(key)) { | ||
if (!this.hasKey(key)) { | ||
throw new Error(`Unexpected key "${key}" found.`); | ||
@@ -168,9 +158,14 @@ } | ||
// now apply remaining validation strategy | ||
strategy.validate.call(strategy, object); | ||
try { | ||
strategy.validate.call(strategy, object); | ||
} catch (ex) { | ||
ex.message = `Key "${key}": ` + ex.message; | ||
throw ex; | ||
} | ||
} | ||
// ensure required keys aren't missing | ||
for (const strategy of this[requiredKeys]) { | ||
if (!(strategy.name in object)) { | ||
throw new Error(`Missing required key "${strategy.name}".`); | ||
for (const [key] of this[requiredKeys]) { | ||
if (!(key in object)) { | ||
throw new Error(`Missing required key "${key}".`); | ||
} | ||
@@ -177,0 +172,0 @@ } |
@@ -23,25 +23,23 @@ /** | ||
beforeEach(() => { | ||
schema = new ObjectSchema(); | ||
}); | ||
describe("new ObjectSchema()", () => { | ||
describe("defineStrategy()", () => { | ||
it("should add a new key when a strategy is passed", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() {}, | ||
validate() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() {}, | ||
validate() {} | ||
} | ||
}); | ||
assert.isTrue(schema.hasStrategyFor("foo")); | ||
assert.isTrue(schema.hasKey("foo")); | ||
}); | ||
it("should throw an error when a strategy is missing a name", () => { | ||
it("should throw an error when a strategy is missing a merge() method", () => { | ||
assert.throws(() => { | ||
schema.defineStrategy({ | ||
merge() {}, | ||
validate() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
validate() { } | ||
} | ||
}); | ||
}, /Strategy must have a "name" property/); | ||
}, /Definition for key "foo" must have a merge\(\) method/); | ||
}); | ||
@@ -51,7 +49,4 @@ | ||
assert.throws(() => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
validate() {} | ||
}); | ||
}, /Strategy for key "foo" must have a merge\(\) method/); | ||
schema = new ObjectSchema(); | ||
}, /Schema definitions missing/); | ||
}); | ||
@@ -61,7 +56,8 @@ | ||
assert.throws(() => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { }, | ||
} | ||
}); | ||
}, /Strategy for key "foo" must have a validate\(\) method/); | ||
}, /Definition for key "foo" must have a validate\(\) method/); | ||
}); | ||
@@ -71,47 +67,8 @@ | ||
describe("defineStrategies()", () => { | ||
it("should add a new key when a strategy is passed", () => { | ||
schema.defineStrategies([{ | ||
name: "foo", | ||
merge() { }, | ||
validate() { } | ||
}]); | ||
assert.isTrue(schema.hasStrategyFor("foo")); | ||
}); | ||
it("should throw an error when a strategy is missing a name", () => { | ||
assert.throws(() => { | ||
schema.defineStrategies([{ | ||
merge() { }, | ||
validate() { } | ||
}]); | ||
}, /Strategy must have a "name" property/); | ||
}); | ||
it("should throw an error when a strategy is missing a merge() method", () => { | ||
assert.throws(() => { | ||
schema.defineStrategies([{ | ||
name: "foo", | ||
validate() { } | ||
}]); | ||
}, /Strategy for key "foo" must have a merge\(\) method/); | ||
}); | ||
it("should throw an error when a strategy is missing a validate() method", () => { | ||
assert.throws(() => { | ||
schema.defineStrategies([{ | ||
name: "foo", | ||
merge() { } | ||
}]); | ||
}, /Strategy for key "foo" must have a validate\(\) method/); | ||
}); | ||
}); | ||
describe("merge()", () => { | ||
it("should throw an error when an unexpected key is found", () => { | ||
let schema = new ObjectSchema({}); | ||
assert.throws(() => { | ||
@@ -123,10 +80,12 @@ schema.merge({ foo: true }, { foo: true }); | ||
it("should call the merge() strategy for one key when called", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() {} | ||
} | ||
}); | ||
const result = schema.merge({ foo: true }, { foo: false }); | ||
@@ -137,8 +96,9 @@ assert.propertyVal(result, "foo", "bar"); | ||
it("should omit returning the key when the merge() strategy returns undefined", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() { | ||
return undefined; | ||
}, | ||
validate() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return undefined; | ||
}, | ||
validate() { } | ||
} | ||
}); | ||
@@ -151,16 +111,16 @@ | ||
it("should call the merge() strategy for two keys when called", () => { | ||
schema.defineStrategies([{ | ||
name: "foo", | ||
merge() { | ||
return "bar"; | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() { } | ||
}, | ||
validate() {} | ||
}, | ||
{ | ||
name: "bar", | ||
merge() { | ||
return "baz"; | ||
}, | ||
validate() {} | ||
}]); | ||
bar: { | ||
merge() { | ||
return "baz"; | ||
}, | ||
validate() {} | ||
} | ||
}); | ||
@@ -173,16 +133,16 @@ const result = schema.merge({ foo: true, bar: 1 }, { foo: true, bar: 2 }); | ||
it("should call the merge() strategy for two keys when called on three objects", () => { | ||
schema.defineStrategies([{ | ||
name: "foo", | ||
merge() { | ||
return "bar"; | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() { } | ||
}, | ||
validate() {} | ||
}, | ||
{ | ||
name: "bar", | ||
merge() { | ||
return "baz"; | ||
}, | ||
validate() {} | ||
}]); | ||
bar: { | ||
merge() { | ||
return "baz"; | ||
}, | ||
validate() { } | ||
} | ||
}); | ||
@@ -203,2 +163,3 @@ const result = schema.merge( | ||
it("should throw an error when an unexpected key is found", () => { | ||
let schema = new ObjectSchema({}); | ||
assert.throws(() => { | ||
@@ -210,6 +171,9 @@ schema.validate({ foo: true }); | ||
it("should not throw an error when an expected key is found", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() {}, | ||
validate() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() {} | ||
} | ||
}); | ||
@@ -221,13 +185,16 @@ | ||
it("should not throw an error when expected keys are found", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() {}, | ||
validate() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() {} | ||
}, | ||
bar: { | ||
merge() { | ||
return "baz"; | ||
}, | ||
validate() {} | ||
} | ||
}); | ||
schema.defineStrategy({ | ||
name: "bar", | ||
merge() {}, | ||
validate() {} | ||
}); | ||
@@ -238,14 +205,17 @@ schema.validate({ foo: true, bar: true }); | ||
it("should not throw an error when expected keys are found with required keys", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() {}, | ||
validate() {} | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() { } | ||
}, | ||
bar: { | ||
requires: ["foo"], | ||
merge() { | ||
return "baz"; | ||
}, | ||
validate() { } | ||
} | ||
}); | ||
schema.defineStrategy({ | ||
name: "bar", | ||
requires: ["foo"], | ||
merge() {}, | ||
validate() {} | ||
}); | ||
@@ -256,21 +226,23 @@ schema.validate({ foo: true, bar: true }); | ||
it("should throw an error when expected keys are found without required keys", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() { }, | ||
validate() { } | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() { } | ||
}, | ||
baz: { | ||
merge() { | ||
return "baz"; | ||
}, | ||
validate() { } | ||
}, | ||
bar: { | ||
name: "bar", | ||
requires: ["foo", "baz"], | ||
merge() { }, | ||
validate() { } | ||
} | ||
}); | ||
schema.defineStrategy({ | ||
name: "baz", | ||
merge() { }, | ||
validate() { } | ||
}); | ||
schema.defineStrategy({ | ||
name: "bar", | ||
requires: ["foo", "baz"], | ||
merge() { }, | ||
validate() { } | ||
}); | ||
assert.throws(() => { | ||
@@ -283,7 +255,11 @@ schema.validate({ bar: true }); | ||
it("should throw an error when an expected key is found but is invalid", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
merge() { }, | ||
validate() { | ||
throw new Error("Invalid key: " + this.name); | ||
schema = new ObjectSchema({ | ||
foo: { | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() { | ||
throw new Error("Invalid key."); | ||
} | ||
} | ||
@@ -294,12 +270,14 @@ }); | ||
schema.validate({ foo: true }); | ||
}, /Invalid key: foo/); | ||
}, /Key "foo": Invalid key/); | ||
}); | ||
it("should throw an error when a required key is missing", () => { | ||
schema.defineStrategy({ | ||
name: "foo", | ||
required: true, | ||
merge() { }, | ||
validate() { | ||
throw new Error("Invalid key: " + this.name); | ||
schema = new ObjectSchema({ | ||
foo: { | ||
required: true, | ||
merge() { | ||
return "bar"; | ||
}, | ||
validate() {} | ||
} | ||
@@ -306,0 +284,0 @@ }); |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
22685
467
164