Comparing version 0.2.0 to 0.3.0
21
index.js
module.exports = require('./lib/schema') | ||
// Core schemas | ||
require('./lib/core/reference') | ||
require('./lib/patterns/reference') | ||
require('./lib/core/nothing') | ||
require('./lib/core/anything') | ||
require('./lib/patterns/nothing') | ||
require('./lib/patterns/anything') | ||
require('./lib/core/object') | ||
require('./lib/patterns/object') | ||
require('./lib/core/or') | ||
require('./lib/core/and') | ||
require('./lib/patterns/or') | ||
require('./lib/core/instanceof') | ||
require('./lib/core/schema') | ||
require('./lib/patterns/instanceof') | ||
require('./lib/patterns/schema') | ||
require('./lib/patterns/regexp') | ||
// Extensions | ||
require('./lib/extensions/Boolean') | ||
require('./lib/extensions/Number') | ||
require('./lib/extensions/Function') | ||
require('./lib/extensions/String') | ||
require('./lib/extensions/Object') | ||
require('./lib/extensions/Array') | ||
require('./lib/extensions/Function') |
var schema = require('../schema') | ||
var ArraySchema = module.exports = function(itemSchema) { | ||
this.itemSchema = schema(itemSchema) | ||
var ArraySchema = module.exports = function(itemSchema, max, min) { | ||
if (itemSchema !== undefined) this.itemSchema = schema(itemSchema) | ||
this.min = min | ||
this.max = max | ||
@@ -10,27 +12,63 @@ return new schema(this) | ||
ArraySchema.prototype = { | ||
validate : function(instance) { | ||
for (var i = 0; i < instance.length; i++) { | ||
if (!this.itemSchema.validate(instance[i])) return false | ||
compile : function() { | ||
var compiled = { } | ||
// Instance must be an instance of Array | ||
compiled.fn = 'if (!(instance instanceof Array)) return false;' | ||
// Checking length | ||
if (this.min !== undefined || this.max !== undefined) { | ||
var checks = [] | ||
if (this.min === this.max) { | ||
checks.push('instance.length !== ' + this.min) | ||
} else { | ||
if (this.min !== undefined) checks.push('instance.length < ' + this.min) | ||
if (this.max !== undefined) checks.push('instance.length > ' + this.max) | ||
} | ||
compiled.fn += 'if (' + checks.join(' || ') + ') return false;' | ||
} | ||
return true | ||
// Checking conformance to the given item schema | ||
if (this.itemSchema !== undefined) { | ||
compiled.references = [this.itemSchema] | ||
compiled.fn += 'for (var i = 0; i < instance.length; i++) {' | ||
+ ' if (!{0}(instance[i])) return false;' | ||
+ '}' | ||
} | ||
// If every check passes, return true | ||
compiled.fn += 'return true;' | ||
return compiled | ||
}, | ||
compile : function() { | ||
var compiled = { } | ||
generate : function() { | ||
var min = this.min === undefined ? 0 : this.min | ||
, max = this.max === undefined ? 10 : this.max | ||
, length = min + Math.floor(Math.random()*(max - min + 1)) | ||
, array = [] | ||
compiled.references = [this.itemSchema] | ||
for (var i = 0; i < length; i++) array.push(this.itemSchema.generate()) | ||
compiled.fn = 'if (!(instance instanceof Array)) return false;' | ||
compiled.fn += 'for (var i = 0; i < instance.length; i++) {' | ||
compiled.fn += ' if (!{0}(instance[i])) return false;' | ||
compiled.fn += '}' | ||
compiled.fn += 'return true;' | ||
return array | ||
}, | ||
toJSON : function() { | ||
var schema = { type : 'array' } | ||
return compiled | ||
if (this.min !== undefined) schema.minItems = this.min | ||
if (this.max !== undefined) schema.maxItems = this.max | ||
if (this.itemSchema) schema.items = this.itemSchema.toJSON() | ||
return schema | ||
} | ||
} | ||
Array.of = function(itemSchema) { | ||
return new ArraySchema(itemSchema) | ||
Array.of = function() { | ||
// Possible signatures : (schema) | ||
// (length, schema) | ||
// (minLength, maxLength, schema) | ||
var args = Array.prototype.reverse.call(arguments) | ||
if (args.length === 2) args[2] = args[1] | ||
return new ArraySchema(args[0], args[1], args[2]) | ||
} |
@@ -1,2 +0,2 @@ | ||
var ReferenceSchema = require('../core/reference') | ||
var ReferenceSchema = require('../patterns/reference') | ||
@@ -3,0 +3,0 @@ Function.Reference = function(f) { |
var schema = require('../schema') | ||
var NumberSchema = module.exports = function(minimum, exclusiveMinimum, maximum, exclusiveMaximum) { | ||
var NumberSchema = module.exports = function(minimum, exclusiveMinimum, maximum, exclusiveMaximum, divisibleBy) { | ||
this.minimum = minimum | ||
@@ -8,2 +8,3 @@ this.exclusiveMinimum = exclusiveMinimum | ||
this.exclusiveMaximum = exclusiveMaximum | ||
this.divisibleBy = divisibleBy | ||
@@ -14,34 +15,85 @@ return new schema(this) | ||
NumberSchema.prototype = { | ||
validate : function(instance) { | ||
if (!(Object(instance) instanceof Number)) return false | ||
compile : function() { | ||
var references = [this.minimum, this.maximum, this.divisibleBy] | ||
, checks = ['Object(instance) instanceof Number'] | ||
if (this.minimum !== undefined) { | ||
if (instance < this.minimum || (this.exclusiveMinimum && instance == this.minimum)) return false | ||
checks.push('instance ' + (this.exclusiveMinimum ? '>' : '>=') + ' {0}') | ||
} | ||
if (this.maximum !== undefined) { | ||
if (instance > this.maximum || (this.exclusiveMaximum && instance == this.maximum)) return false | ||
checks.push('instance ' + (this.exclusiveMaximum ? '<' : '<=') + ' {1}') | ||
} | ||
return true | ||
if (this.divisibleBy !== undefined) { | ||
checks.push('instance % {2} === 0') | ||
} | ||
return { references : references, expression : checks.join(' && ') } | ||
}, | ||
min : function(minimum) { | ||
return new NumberSchema(minimum, false, this.maximum, this.exclusiveMaximum) | ||
return new NumberSchema(minimum, false, this.maximum, this.exclusiveMaximum, this.divisibleBy) | ||
}, | ||
above : function(minimum) { | ||
return new NumberSchema(minimum, true, this.maximum, this.exclusiveMaximum) | ||
return new NumberSchema(minimum, true, this.maximum, this.exclusiveMaximum, this.divisibleBy) | ||
}, | ||
max : function(maximum) { | ||
return new NumberSchema(this.minimum, this.exclusiveMinimum, maximum, false) | ||
return new NumberSchema(this.minimum, this.exclusiveMinimum, maximum, false, this.divisibleBy) | ||
}, | ||
below : function(maximum) { | ||
return new NumberSchema(this.minimum, this.exclusiveMinimum, maximum, true) | ||
return new NumberSchema(this.minimum, this.exclusiveMinimum, maximum, true, this.divisibleBy) | ||
}, | ||
step : function(divisibleBy) { | ||
return new NumberSchema(this.minimum, this.exclusiveMinimum, this.maximum, this.exclusiveMaximum, divisibleBy) | ||
}, | ||
generate : function() { | ||
var length, random | ||
, min = this.minimum | ||
, max = this.maximum | ||
, step = this.divisibleBy | ||
// If there's no decalred maximum or minimum then assigning a reasonable value | ||
if (min == null || min === -Infinity) { | ||
if (max == null || max === Infinity) { | ||
min = 0 | ||
max = 10 | ||
} else { | ||
min = max > 0 ? 0 : max*2 | ||
} | ||
} else if (max == null || max === Infinity) { | ||
max = this.min < 0 ? 0 : this.min*2 | ||
} | ||
// Choosing random number from a continuous set | ||
if (step == null || step === 0) { | ||
length = max - min | ||
do { | ||
random = min + Math.random()*length | ||
} while ((this.exclusiveMinimum && random === min) || (this.exclusiveMaximum && random === max)) | ||
// Choosing random number from a discrete set | ||
} else { | ||
min = Math.ceil(min / step) * step | ||
max = Math.floor(max / step) * step | ||
if (this.exclusiveMinimum && min === this.minimum) min += step | ||
if (this.exclusiveMaximum && max === this.maximum) max -= step | ||
if (min > max) return undefined | ||
length = Math.round((max - min) / step) + 1 | ||
random = min + step * Math.floor(Math.random()*length) | ||
} | ||
return random | ||
}, | ||
toJSON : function() { | ||
var schema = { type : 'number', required : true } | ||
var schema = { type : 'number' } | ||
@@ -58,2 +110,4 @@ if (this.minimum !== undefined) { | ||
if (this.divisibleBy !== undefined) schema.divisibleBy = this.divisibleBy | ||
return schema | ||
@@ -63,6 +117,10 @@ } | ||
Number.schema = new NumberSchema() | ||
Number.min = Number.schema.min | ||
Number.above = Number.schema.above | ||
Number.max = Number.schema.max | ||
Number.below = Number.schema.below | ||
Number.schema = new NumberSchema() | ||
Number.min = Number.schema.min | ||
Number.above = Number.schema.above | ||
Number.max = Number.schema.max | ||
Number.below = Number.schema.below | ||
Number.step = Number.schema.step | ||
Number.generate = Number.schema.generate | ||
Number.Integer = Number.step(1) |
@@ -1,2 +0,2 @@ | ||
var ReferenceSchema = require('../core/reference') | ||
var ReferenceSchema = require('../patterns/reference') | ||
@@ -3,0 +3,0 @@ Object.Reference = function(o) { |
@@ -64,2 +64,3 @@ var native_functions = [Boolean, Number, String, Object, Array, Function, Date] | ||
assembly.subroutines = assembly.subroutines || [] | ||
assembly.references = assembly.references || [] | ||
@@ -66,0 +67,0 @@ var name |
@@ -34,4 +34,4 @@ var def = require('def.js') | ||
schema.random = function(sch) { | ||
return schema(sch).random() | ||
schema.generate = function(sch) { | ||
return schema(sch).generate() | ||
} |
@@ -10,3 +10,3 @@ { | ||
], | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"homepage": "https://github.com/molnarg/js-schema", | ||
@@ -18,3 +18,4 @@ "repository": { | ||
"dependencies": { | ||
"def.js" : "*" | ||
"def.js" : "0.1.4", | ||
"randexp" : "*" | ||
}, | ||
@@ -21,0 +22,0 @@ "devDependencies": {}, |
139
README.md
js-schema | ||
========= | ||
js-schema is essentially a new way to describe JSON schemas using a | ||
much cleaner and simpler syntax. Think of it like regexp for objects. | ||
js-schema is a new way of describing object schemas in JavaScript. It has a clean and simple syntax. | ||
Usecases include object validation and random object generation. | ||
@@ -10,3 +10,3 @@ A simple example | ||
Object validation and filtering: | ||
Defining a schema: | ||
@@ -16,19 +16,33 @@ ```javascript | ||
var Duck = schema({ | ||
quack : Function, | ||
feed : Function, | ||
age : Number.min(0).max(5) | ||
color : ['yellow', 'brown'] | ||
var Duck = schema({ // A duck | ||
quack : Function, // - can quack | ||
feed : Function, // - can be fed | ||
age : Number.min(0).max(5), // - is 0 to 5 years old | ||
color : ['yellow', 'brown'] // - has either yellow or brown color | ||
}); | ||
``` | ||
The resulting function can be used for checking or validating objects: | ||
```javascript | ||
var myDuck = { quack : function() {}, feed : function() {}, age : 2, color : 'yellow' }; | ||
var myCat = { purr : function() {}, feed : function() {}, age : 3, color : 'black' }; | ||
var animals = [myDuck, myCat, /*...*/ ]; | ||
var animals = [myDuck, myCat, {}, /*...*/ ]; | ||
console.log( Duck(myDuck) ); // true | ||
console.log( Duck(myCat) ); // false | ||
console.log( animals.filter(Duck) ); // every Duck-like object | ||
console.log( animals.filter(Duck) ); // every Duck-like object | ||
console.log( animals.filter(schema({ feed : Function })) ); // every animal that can be fed | ||
``` | ||
It is also possible to generate random objects for testing purposes: | ||
```javascript | ||
console.log( Duck.generate() ); | ||
var testcases = Array.of(10, Duck).generate(); | ||
test(testcases); | ||
``` | ||
Usage | ||
@@ -66,6 +80,6 @@ ===== | ||
2. `Class` (where `Class` is a function) matches `x` if `x instanceof Class` | ||
3. `[[pattern1, pattern2, ...]]` matches `x` if _all_ of the given patterns match `x` | ||
3. `/regexp/` matches `x` if `/regexp/.test(x) === true` | ||
4. `[pattern1, pattern2, ...]` matches `x` if _any_ of the given patterns match `x` | ||
5. `{ a : pattern1, b : pattern2, ... }` matches `x` if `pattern1` matches `x.a`, `pattern2` | ||
matches `x.b`, etc. | ||
5. `{ 'a' : pattern1, 'b' : pattern2, ... }` matches `x` if `pattern1` matches `x.a`, | ||
`pattern2` matches `x.b`, etc. For details see the object pattern subsection. | ||
6. `undefined` matches `x` if `x` _is not_ `null` or `undefined` | ||
@@ -85,39 +99,52 @@ 7. `null` matches `x` if `x` _is_ `null` or `undefined` | ||
```javascript | ||
validator = schema({ // (5) 'object' pattern | ||
a : [[ String, {length : 5} ]], // (3) 'and' pattern | ||
// (2) 'instanceof' pattern | ||
// (5) 'object' pattern | ||
// (8) 'primitive' pattern | ||
b : [Color, 'red', 'blue'], // (4) 'or' pattern | ||
// (2) 'instanceof' pattern | ||
// (8) 'primitive' pattern | ||
c : undefined, // (6) 'anything' pattern | ||
d : null // (7) 'nothing' pattern | ||
validate = schema({ // (5) 'object' pattern | ||
a : [Color, 'red', 'blue'], // (4) 'or' pattern | ||
// (2) 'instanceof' pattern | ||
// (8) 'primitive' pattern | ||
b : /The meaning of life is \d+/, // (3) regexp pattern | ||
c : undefined, // (6) 'anything' pattern | ||
d : null // (7) 'nothing' pattern | ||
}); | ||
console.log( validate(x) ); | ||
``` | ||
The `schema` function compiles the pattern, and returns the value of the following expression | ||
(the validator function): | ||
`validate(x)` returns true if all of these are true: | ||
* `x.a` is either 'red' or 'blue' or an instance of the Color class | ||
* `x.b` is a string that matches the /The meaning of life is \d+/ regexp | ||
* `x` does have a property called `c` | ||
* `x` doesn't have a property called `d`, or it does but it is null or undefined | ||
### The object pattern ### | ||
The object pattern is more complex than the others. Using the object pattern it is possible to | ||
define optional properties, regexp properties, etc. This extra information can be encoded in | ||
the property names. | ||
The property names in an object pattern are always regular expressions, and the given schema | ||
applies to instance properties whose name match this regexp. The number of expected matches can | ||
also be specified with `?`, `+` or `*` as the first character of the property name. `?` means | ||
0 or 1, `*` means 0 or more, and `+` means 1 or more. A single `*` as a property name | ||
matches any instance property that is not matched by other regexps. | ||
An example of using these: | ||
```javascript | ||
(functon(r0){ | ||
return function(instance){ | ||
return instance != null && ( | ||
(Object(instance["a"]) instanceof String) && | ||
(instance["a"] != null && (instance["a"]["length"] === 5)) | ||
) && ( | ||
(Object(instance["b"]) instanceof r0) || | ||
(instance["b"] === "red") || (instance["b"] === "blue") | ||
) && ( | ||
instance["c"] != null | ||
) && ( | ||
instance["d"] == null | ||
); | ||
}; | ||
}(Color)); | ||
var x = { /* ... */ }; | ||
validate = schema({ | ||
'name' : String, // x.name must be string | ||
'colou?r' : String // x must have a string type property called either | ||
// 'color' or 'colour' but not both | ||
'?location' : String, // if x has a property called 'location' then it must be string | ||
'*identifier-.*' : Number, // if the name of a property of x matches /identifier-.*/ then | ||
// it must be a number | ||
'+serialnumber-.*' : Number, // if the name of a property of x matches /serialnumber-.*/ then | ||
// it must be a number and there should be at least one such property | ||
'*' : Boolean // any other property that doesn't match any of these rules | ||
// must be Boolean | ||
}); | ||
assert( validate(x) === true ); | ||
``` | ||
As you can see, the compiled function is nearly optimal, and looks like what anyone would | ||
write when following the rules described above. | ||
Extensions | ||
@@ -136,9 +163,12 @@ ========== | ||
`Array.of(pattern)` matches `x` if `x instanceof Array` and `pattern` matches every element of `x`. | ||
The `Array.of` method has three signatures: | ||
- `Array.of(pattern)` matches `x` if `x instanceof Array` and `pattern` matches every element of `x`. | ||
- `Array.of(length, pattern)` additionally checks the length of the instance and returns true only if it equals to `length`. | ||
- `Array.of(minLength, maxLength, pattern)` is similar, but checks if the length is in the given interval. | ||
### Numbers ### | ||
There are four functions that can be used for describing number ranges: `min`, `max`, `below` | ||
and `above`. All of these are chainable, so for example `Number.min(a).below(b)` matches `x` if | ||
`a <= x && x < b`. | ||
There are five functions that can be used for describing number ranges: `min`, `max`, `below`, | ||
`above` and `step`. All of these are chainable, so for example `Number.min(a).below(b)` matches `x` | ||
if `a <= x && x < b`. The `Number.step(a)` matches `x` if `x` is a divisble by `a`. | ||
@@ -149,4 +179,4 @@ Future plans | ||
Better JSON Schema support. js-schema should be able to parse any valid JSON schema and generate | ||
JSON Schema for most of the patterns (there are cases when this is not possible in general, | ||
e.g. patterns that have external references like the instanceof pattern). | ||
JSON Schema for most of the patterns (this is not possible in general, e.g. patterns that have | ||
external references like the instanceof pattern). | ||
@@ -164,13 +194,2 @@ Defining and validating resursive data structures: | ||
Generating random objects based on schema description: | ||
```javascript | ||
// generating random trees | ||
console.log( schema.random(Tree) ); // {left : 0.123, right : {left : 0.2, right : 0.9}} | ||
// generating an array of random trees for testing | ||
var testcases = schema.random(Array.of(Tree)); | ||
test(testcases); | ||
``` | ||
Using the random object generation, it should be possible to build a QucikCheck-like testing | ||
@@ -177,0 +196,0 @@ framework, which could be used to generate testcases for js-schema (yes, I like resursion). |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
28008
21
508
211
2
+ Addedrandexp@*
+ Addeddef.js@0.1.4(transitive)
+ Addeddrange@1.1.1(transitive)
+ Addedrandexp@0.5.3(transitive)
+ Addedret@0.2.2(transitive)
- Removeddef.js@0.1.6(transitive)
Updateddef.js@0.1.4