Socket
Socket
Sign inDemoInstall

js-schema

Package Overview
Dependencies
5
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.1 to 0.5.0

HISTORY.md

24

lib/extensions/Array.js
var schema = require('../schema')
, EqualitySchema = require('../patterns/equality')
, anything = require('../patterns/anything')
, _ = require('underscore')
, RandExp = require('randexp')

@@ -56,2 +58,19 @@ var ArraySchema = module.exports = function(itemSchema, max, min) {

leafs : function() {
var item_leafs = this.itemSchema.leafs ? this.itemSchema.leafs()
: { certain : [ this.itemSchema ], uncertain : [] }
if (this.min > 0) return item_leafs
return { certain : []
, uncertain : _.union(item_leafs.certain, item_leafs.uncertain)
}
},
getId : function() {
if (!this.id) this.id = new RandExp(/^[a-zA-Z]{4,6}$/).gen()
return this.id
},
toJSON : function() {

@@ -64,2 +83,4 @@ var schema = { type : 'array' }

if (this.id) schema.id = this.id
return schema

@@ -84,3 +105,4 @@ }

if (args.length === 2) args[2] = args[1]
return new ArraySchema(schema(args[0]), args[1], args[2])
return new ArraySchema(schema.fromJS(args[0]), args[1], args[2])
}

@@ -87,0 +109,0 @@

10

lib/extensions/Boolean.js
var schema = require('../schema')
var BooleanSchema = function() {
return new schema(this)
}
BooleanSchema.prototype = {
var booleanSchema = module.exports = new schema({
compile : function() {

@@ -19,6 +15,4 @@ return { expression : 'Object(instance) instanceof Boolean' }

}
}
})
var booleanSchema = new BooleanSchema()
schema.fromJSON.def(function(sch) {

@@ -25,0 +19,0 @@ if (!sch || sch.type !== 'boolean') return

var schema = require('../schema')
var NumberSchema = module.exports = function(minimum, exclusiveMinimum, maximum, exclusiveMaximum, divisibleBy) {
this.minimum = minimum
this.minimum = minimum != null ? minimum : -Infinity
this.exclusiveMinimum = exclusiveMinimum
this.maximum = maximum
this.maximum = minimum != null ? maximum : Infinity
this.exclusiveMaximum = exclusiveMaximum
this.divisibleBy = divisibleBy
this.divisibleBy = divisibleBy || 0

@@ -14,21 +14,2 @@ return new schema(this)

NumberSchema.prototype = {
compile : function() {
var references = [this.minimum, this.maximum, this.divisibleBy]
, checks = ['Object(instance) instanceof Number']
if (this.minimum !== undefined && this.minimum !== -Infinity) {
checks.push('instance ' + (this.exclusiveMinimum ? '>' : '>=') + ' {0}')
}
if (this.maximum !== undefined && this.maximum !== Infinity) {
checks.push('instance ' + (this.exclusiveMaximum ? '<' : '<=') + ' {1}')
}
if (this.divisibleBy !== undefined && this.divisibleBy !== 0) {
checks.push('instance % {2} === 0')
}
return { references : references, expression : checks.join(' && ') }
},
min : function(minimum) {

@@ -49,7 +30,26 @@ return new NumberSchema(minimum, false, this.maximum, this.exclusiveMaximum, this.divisibleBy)

},
step : function(divisibleBy) {
return new NumberSchema(this.minimum, this.exclusiveMinimum, this.maximum, this.exclusiveMaximum, divisibleBy)
},
compile : function() {
var references = [this.minimum, this.maximum, this.divisibleBy]
, checks = ['Object(instance) instanceof Number']
if (this.minimum !== -Infinity) {
checks.push('instance ' + (this.exclusiveMinimum ? '>' : '>=') + ' {0}')
}
if (this.maximum !== Infinity) {
checks.push('instance ' + (this.exclusiveMaximum ? '<' : '<=') + ' {1}')
}
if (this.divisibleBy !== 0) {
checks.push('instance % {2} === 0')
}
return { references : references, expression : checks.join(' && ') }
},
generate : function() {

@@ -61,16 +61,19 @@ var length, random

// 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
// If there's no declared maximum or minimum then assigning a reasonable value
if (min === -Infinity || max === Infinity) {
length = 10
while (Math.random() < 0.5) length = length * 10
if (min === -Infinity && max === Infinity) {
min = length / -2
max = length / +2
} else if (min === -Infinity) {
min = max - length
} else if (max === Infinity) {
max = min + length
}
} 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) {
if (step === 0) {
length = max - min

@@ -97,8 +100,11 @@ do {

},
toJSON : function() {
var integer = this.divisibleBy !== 0 && this.divisibleBy === Math.floor(this.divisibleBy)
var schema = { type : integer ? 'integer' : 'number' }
var step = this.divisibleBy
, integer = ( step !== 0 && step === Math.floor(step) )
, schema = { type : integer ? 'integer' : 'number' }
if (this.minimum !== undefined) {
if (step !== 0 && step !== 1) schema.divisibleBy = step
if (this.minimum !== -Infinity) {
schema.minimum = this.minimum

@@ -108,3 +114,3 @@ if (this.exclusiveMinimum === true) schema.exclusiveMinimum = true

if (this.maximum !== undefined) {
if (this.maximum !== Infinity) {
schema.maximum = this.maximum

@@ -114,5 +120,2 @@ if (this.exclusiveMaximum === true) schema.exclusiveMaximum = true

var step = this.divisibleBy
if (step !== undefined && step !== 0 && step !== 1) schema.divisibleBy = step
return schema

@@ -119,0 +122,0 @@ }

var native_functions = [Boolean, Number, String, Object, Array, Function, Date]
var instance_regexp = RegExp('(\\W|^)instance(\\W|$)', 'g')

@@ -6,2 +7,8 @@ var c = 0

var replace = function(assembly, regexp, target) {
var replace = function(str) { return str.replace(regexp, target) }
assembly.expression = replace(assembly.expression)
assembly.subroutines = assembly.subroutines.map(replace)
}
var linker = {

@@ -27,3 +34,2 @@ resolve_references : function(assembly) {

resolved = function(match, validate, call, param) {
var instance_regexp = RegExp('(\\W|^)instance(\\W|$)', 'g')
var expr = referenced_assembly.expression.replace(instance_regexp, '$1' + param + '$2')

@@ -60,11 +66,17 @@ return '(' + expr + ')'

// replace strings similar to "{1}.validate(parameter)", where the validate call is optional
var format = function(str) {
return str.replace(RegExp('\\{' + id + '\\}((\\.validate)?\\(([^)]*)\\))?', 'g'), resolved)
}
assembly.expression = format(assembly.expression)
assembly.subroutines = assembly.subroutines.map(format)
replace(assembly, RegExp('\\{' + id + '\\}((\\.validate)?\\(([^)]*)\\))?', 'g'), resolved)
}
},
link : function(assembly) {
// An assembly is a compiled version of a schema.
// Assembly.schema = schema({
// references : Array.of(Function),
// subroutines : Array.of(String),
// expression : String,
// '?fn' : String
// })
//
// If self is given, it means that references to that schema can be replaced with inline
// self references, because the schema is sealed, so its code will not be inlined in other schemas
link : function(assembly, self) {
assembly.subroutines = assembly.subroutines || []

@@ -78,2 +90,3 @@ assembly.references = assembly.references || []

assembly.expression = name + '(instance)'
delete assembly.fn
}

@@ -83,6 +96,17 @@

// If there's self given, finding and inlining references.
if (self) {
for (var key in assembly.references) {
if (assembly.references[key] !== self) continue
delete assembly.references[key]
replace(assembly, RegExp(key + '\\(', 'g'), 'self(')
}
}
var closure_arg_names = Object.keys(assembly.references)
var closure_args = closure_arg_names.map(function(key){ return assembly.references[key] })
var closure_body = assembly.subroutines.join('\n') + '\n'
+ 'return function(instance){ return ' + assembly.expression + '; }'
closure_body += assembly.fn ? 'var self = ' + name + ';\n'
: 'function self(instance){ return ' + assembly.expression + '; }\n'
closure_body += 'return self;'
var closure = Function.apply(null, closure_arg_names.concat(closure_body))

@@ -89,0 +113,0 @@ var validate = closure.apply(null, closure_args)

var schema = require('../schema')
, nothing = require('./nothing')
var AnythingSchema = function() {
return new schema(this)
}
AnythingSchema.prototype = {
var anything = module.exports = new schema({
compile : function() {

@@ -13,3 +10,3 @@ return { expression : 'instance != null' }

generate : function() {
var type = [Boolean, Number, String, Array, Object][Math.floor(Math.random()*5)]
var type = [nothing, Boolean, Number, String, Array, Object][Math.floor(Math.random()*6)]
return type.schema.generate()

@@ -21,12 +18,10 @@ },

}
}
})
var anythingSchema = module.exports = new AnythingSchema()
schema.fromJS.def(function(sch) {
if (sch === undefined) return anythingSchema
if (sch === undefined) return anything
})
schema.fromJSON.def(function(sch) {
if (sch.type === 'any') return anythingSchema
if (sch.type === 'any') return anything
})
var schema = require('../schema')
var NothingSchema = function() {
return new schema(this)
}
NothingSchema.prototype = {
var nothing = module.exports = new schema({
compile : function() {

@@ -13,18 +9,16 @@ return { expression : 'instance == null' }

generate : function() {
return null
return Math.random() < 0.5 ? null : undefined
},
toJSON : function() {
return { disallow : 'any' }
return { type : 'null' }
}
}
})
var nothingSchema = module.exports = new NothingSchema()
schema.fromJS.def(function(sch) {
if (sch === null) return nothingSchema
if (sch === null) return nothing
})
schema.fromJSON.def(function(sch) {
if (sch.disallow === 'any') return nothingSchema
if (sch.type === 'null') return nothing
})

@@ -6,2 +6,3 @@ var schema = require('../schema')

, nothing = require('./nothing')
, _ = require('underscore')

@@ -46,3 +47,3 @@ var ObjectSchema = module.exports = function(properties, other) {

if (!this.regexpProps.length && this.other !== anything) {
if (!this.regexpProps.length && this.other === anything) {
return { references : references, expression : checks.join(' && ') }

@@ -123,2 +124,29 @@ }

leafs : function() {
var certain = [], uncertain = []
this.properties.forEach(function(property) {
var property_leafs = property.value.leafs ? property.value.leafs()
: { certain : [ property.value ], uncertain : [] }
if (property.min > 0) {
certain.push(property_leafs.certain)
uncertain.push(property_leafs.uncertain)
} else {
uncertain.push(property_leafs.uncertain)
uncertain.push(property_leafs.certain)
}
})
return { certain : _.union.apply(null, certain)
, uncertain : _.union.apply(null, uncertain.concat([this.other]))
}
},
getId : function() {
if (!this.id) this.id = new RandExp(/^[a-zA-Z]{4,6}$/).gen()
return this.id
},
toJSON : function() {

@@ -146,2 +174,4 @@ var i, property, regexp, json = { type : 'object' }

if (this.id) json.id = this.id
return json

@@ -199,3 +229,3 @@ }

for (var key in object) {
value = schema(object[key])
value = schema.fromJS(object[key])

@@ -241,3 +271,3 @@ if (key === '*') {

if (sch.additionalProperties !== undefined) {
other = sch.additionalProperties === false ? schema(null) : schema.fromJSON(sch.additionalProperties)
other = sch.additionalProperties === false ? nothing : schema.fromJSON(sch.additionalProperties)
}

@@ -244,0 +274,0 @@

var schema = require('../schema')
, EqualitySchema = require('../patterns/equality')
, _ = require('underscore')
, RandExp = require('randexp')

@@ -24,2 +26,25 @@ var OrSchema = module.exports = function(schemas) {

leafs : function() {
// Certain and uncertain leafs of subschemas
var subschema_leafs = this.schemas.map(function(sub) {
return sub.leafs ? sub.leafs()
: { certain : [ sub ], uncertain : [] }
})
subschema_leafs.certain = subschema_leafs.map(function(leafs){ return leafs.certain })
subschema_leafs.uncertain = subschema_leafs.map(function(leafs){ return leafs.uncertain })
// If some leaf appears in all subschemas as certain then it is certain. Otherwise uncertain.
var all = _.union.apply(null, subschema_leafs.certain.concat(subschema_leafs.uncertain))
var certain = _.intersection.apply(null, subschema_leafs.certain)
var uncertain = _.difference(all, certain)
return { certain : certain, uncertain : uncertain }
},
getId : function() {
if (!this.id) this.id = new RandExp(/^[a-zA-Z]{4,6}$/).gen()
return this.id
},
toJSON : function() {

@@ -37,6 +62,10 @@ var jsons = this.schemas.map(schema.toJSON)

return { 'type' : jsons.map(function(json) {
var json = { 'type' : jsons.map(function(json) {
var simpleType = typeof json.type === 'string' && Object.keys(json).length === 1
return simpleType ? json.type : json
})}
if (this.id) schema.id = this.id
return json
}

@@ -47,3 +76,5 @@ }

schema.fromJS.def(function(schemas) {
if (schemas instanceof Array) return new OrSchema(schemas.map(schema.fromJS))
if (schemas instanceof Array) return new OrSchema(schemas.map(function(sch) {
return sch === undefined ? schema.self : schema.fromJS(sch)
}))
})

@@ -50,0 +81,0 @@

var schema = require('../schema')
, RandExp = require('randexp')
var defaultRandExp = new RandExp(/[a-zA-Z0-9]*/)
var defaultRandExp = new RandExp(/^[a-zA-Z0-9]{1,10}$/)

@@ -6,0 +6,0 @@ var RegexpSchema = module.exports = function(regexp) {

var def = require('def.js')
, linker = require('./linker')
, global = (function(){ return this }())
var schema = module.exports = function(schemaObject) {
// When called as simple function, forward everything to fromJS
if (this === global) return schema.fromJS.apply(global, arguments)
// When called as simple function, forward everything to fromJS, and then seal the resulting schema
if (!(this instanceof schema)) return schema.fromJS.apply(null, arguments).seal()
// When called with new, transform the parameter schema object to a compiled schema function
if (!schemaObject.compile && !schemaObject.validate) {
throw new Error('Schemas must have either compile or validate function.')
throw new Error('Schema definition objects must have either compile or validate function.')
}

@@ -17,4 +16,29 @@

for (var property in schemaObject) {
if (schemaObject[property] instanceof Function) validate[property] = schemaObject[property].bind(schemaObject)
validate.seal = function() {
if (schemaObject.sealed) return validate
schemaObject.sealed = true
if (schemaObject.leafs) {
var leafs = schemaObject.leafs()
if (leafs.certain.indexOf(schema.self) !== -1) {
throw new Error('There\'s no object that satisfies this schema because of necessary recursion.')
}
if (leafs.uncertain.indexOf(schema.self) !== -1) {
// If the validate function is compiled then recompiling it with self inlining
if (validate.assembly) {
var newValidate = linker.link(schemaObject.compile(), schema.self)
for (var key in validate) newValidate[key] = validate[key]
newValidate.schema = newValidate
validate = newValidate
}
// schema.self needs to be pointed to this schema, and then it must be reset
schema.self.set(validate)
schema.self = new SelfSchema()
}
}
delete validate.assembly
return validate
}

@@ -24,5 +48,37 @@

for (var key in schemaObject) {
if (schemaObject[key] instanceof Function && !schemaObject[key].schema) {
validate[key] = schemaObject[key].bind(schemaObject)
}
}
return validate
}
var SelfSchema = function() {
return new schema(this)
}
SelfSchema.prototype = {
set : function(target) {
if (!this.target) this.target = target
},
validate : function(instance) {
return this.target(instance)
},
generate : function() {
return this.target.generate()
},
toJSON : function() {
return { '$ref' : this.target.getId() }
}
}
schema.self = new SelfSchema()
schema.fromJS = def()

@@ -29,0 +85,0 @@

@@ -12,3 +12,3 @@ {

],
"version": "0.4.1",
"version": "0.5.0",
"homepage": "https://github.com/molnarg/js-schema",

@@ -21,3 +21,4 @@ "repository": {

"def.js" : "*",
"randexp" : "*"
"randexp" : "*",
"underscore" : "*"
},

@@ -24,0 +25,0 @@ "devDependencies": {},

@@ -8,4 +8,4 @@ js-schema

A simple example
================
Features
========

@@ -18,16 +18,16 @@ Defining a schema:

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
var Duck = schema({ // A duck
swim : Function, // - can swim
quack : Function, // - can quack
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:
The resulting function (`Duck`) 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 myDuck = { quack : function() {}, swim : function() {}, age : 2, color : 'yellow' },
myCat = { purr : function() {}, walk : function() {}, age : 3, color : 'black' },
animals = [ myDuck, myCat, {}, /*...*/ ];

@@ -37,15 +37,31 @@ console.log( Duck(myDuck) ); // true

console.log( animals.filter(Duck) ); // every Duck-like object
console.log( animals.filter(schema({ feed : Function })) ); // every animal that can be fed
var ducks = animals.filter( Duck ); // every Duck-like animal
var walking = animals.filter( schema({ walk : Function }) ); // every animal that can walk
```
It is also possible to generate random objects for testing purposes:
js-schema can generate random objects for a given schema for testing purposes:
```javascript
console.log( Duck.generate() );
var duck = schema.generate( Duck );
var testcases = schema.generate( Array.of(5, Duck) );
```
var testcases = Array.of(5, Duck).generate();
console.log(testcases);
It is also possible to define self-referencing data structures:
```javascript
var Tree = schema({ left : [ Number, Tree ], right : [ Number, Tree ] });
var tree = schema.generate( Tree );
```
The schema description is _compiled_ into validation function for achieving maximal performance.
```javascript
console.log( Tree.toString() );
// function self(instance) {
// return instance != null &&
// ((Object(instance["left" ]) instanceof Number) || self(instance["left" ])) &&
// ((Object(instance["right"]) instanceof Number) || self(instance["right"]));
// }
```
Usage

@@ -79,3 +95,3 @@ =====

There are 9 basic rules used for describing schemas:
There are 10 basic rules used for describing schemas:

@@ -90,5 +106,7 @@ 1. `Class` (where `Class` is a function, and has a function type property called `schema`)

`pattern2` matches `x.b`, etc. For details see the object pattern subsection.
7. `undefined` matches `x` if `x` _is not_ `null` or `undefined`.
7. `primitive` (where `primitive` is boolean, number, or string) matches `x` if `primitive === x`.
8. `null` matches `x` if `x` _is_ `null` or `undefined`.
9. `primitive` (where `primitive` is boolean, number, or string) matches `x` if `primitive === x`.
9. `undefined` matches anything.
10. `schema.self` references the schema returned by the last use of the `schema` function.
For details see the self-referencing subsection.

@@ -110,8 +128,10 @@ The order is important. When calling `schema(pattern)`, the rules are examined one by one,

// (2) 'instanceof' pattern
// (9) 'primitive' pattern
// (7) 'primitive' pattern
// (4) 'deep equality' pattern
b : Number, // (1) 'class schema' pattern
c : /The meaning of life is \d+/, // (3) regexp pattern
d : undefined, // (7) 'anything' pattern
e : null // (8) 'nothing' pattern
d : undefined, // (9) 'anything' pattern
e : [null, schema.self] // (5) 'or' pattern
// (8) 'nothing' pattern
// (10) 'self' pattern
});

@@ -127,4 +147,4 @@

* `x.c` is a string that matches the /The meaning of life is \d+/ regexp
* `x` does have a property called `d`
* `x` doesn't have a property called `e`, or it does but it is `null` or `undefined`
* `x` doesn't have a property called `e`, or it does but it is `null` or `undefined`,
or it is an object that matches this schema

@@ -163,24 +183,25 @@ ### The object pattern ###

Extensions
==========
### Self-referencing ###
### Objects ###
The easiest way to do self-referencing is using `schema.self`. However, to support a more
intuitive notation (as seen in the Tree example above) there is an other way to reference
the schema that is being described. When executing this:
`Object.reference(object)` matches `x` if `x === object`.
```javascript
var Tree = schema({ left : [ Number, Tree ], right : [ Number, Tree ] });
```
`Object.like(object)` matches `x` if `x` deep equals `object`.
js-schema sees in fact `{ left : [ Number, undefined ], right : [ Number, undefined ] }` as first
parameter, since the value of the `Tree` variable is undefined when the schema function is
called. Consider the meaning of `[ Number, undefined ]` according to the rules described above:
'this property must be either Number, or anything else'. It doesn't make much sense to include
'anything else' in an 'or' relation. If js-schema sees `undefined` in an or relation, it assumes
that this is in fact a self-reference.
### Functions ###
Use this feature carefully, because it may easily lead to bugs. Only use it when the return value
of the schema function is assigned to a newly defined variable.
`Function.reference(func)` matches `x` if `x === func`.
Extensions
==========
### Arrays ###
The `Array.like(array)` matches `x` if `x instanceof Array` and it deep equals `array`.
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 ###

@@ -205,2 +226,21 @@

### Arrays ###
The `Array.like(array)` matches `x` if `x instanceof Array` and it deep equals `array`.
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.
### Objects ###
`Object.reference(object)` matches `x` if `x === object`.
`Object.like(object)` matches `x` if `x` deep equals `object`.
### Functions ###
`Function.reference(func)` matches `x` if `x === func`.
Future plans

@@ -213,20 +253,9 @@ ============

Error reporting. js-schema should be able to report validation errors in a meaningful way instead
of just stopping and returning false. Error handling shouldn't be the default mode of operation
because it comes at a significant performance cost and it is not needed in all usecases.
Using the random object generation, it should be possible to build a QucikCheck-like testing
framework, which could be used to generate testcases for js-schema (yes, I like resursion).
Defining and validating resursive data structures:
```javascript
// Defining the data structure:
var Tree = schema({ left : [Tree, Number], right : [Tree, Number] });
// The schema function gets this as argument:
// { left : [undefined, Number], right : [undefined, Number] }
// Since providing 'undefined' as part of an 'or' patterns doesn't make sense,
// it must be a self-reference. Self-reference usually occur as part of 'or' patterns.
// validation
console.log( Tree({left : {left : 1, right : 2 }, right : 9}) ); // true
console.log( Tree({left : {left : 1, right : null}, right : 9}) ); // false
```
Contributing

@@ -233,0 +262,0 @@ ============

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc