Skip to content

Commit

Permalink
Added null type, removed getDefault, added CHANGELOG
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonhorst committed Feb 16, 2015
1 parent f85a8d0 commit 56cdb2a
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 95 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#CHANGELOG

##2.0

- Added `null` type. Previously, `null` was treated the same as `undefined`. Now they behave differently.
- Removed `getDefault`
92 changes: 89 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,102 @@ console.log(confine.normalize(object, schema)) /* {

#### `confine.validateSchema(schema)`

- Returns a boolean indicating if `schema` is valid.
- Returns a boolean indicating if `schema` is valid. This should be checked for any untrusted schema before calling any other method - using an invalid schema with the other `confine` methods results in undetermined behavior.

#### `confine.validate(obj, schema)`

- Returns a boolean indicating if `obj` is valid according to `schema`

#### `confine.normalize(obj, schema)`

- Returns an adjusted representation of `obj`, filling in defaults and removing unnecessary fields.
- Throws an error if `validateSchema` returns `false`
- Returns an adjusted representation of `obj`, removing invalid or unnecessary fields and filling in defaults.

## Confine Schemas

Confine uses a simplified version of JSON Schema to describe JSON objects. Each schema is a JSON object that contains a `type` property. 7 types are built-in, and custom types can also be added. Types may make use of additional properties, as defined below. Additionally, all types make use of 2 additional properties: `default` and `enum`.

- `default` is an object that will be returned by `confine.normalize` or `confine.getDefault` if the provided input is `undefined`. It must be a valid object itself (that is to say, `confine.validate(schema.default, schema)` must return `true`).
- `enum` is an array that enumerates all possible options the value can take. Any input not listed in this array will be rejected. Every array emtry must be a valid object itself (that is to say, `_.every(schema.enum, function (e) {return confine.validate(e, schema)})` must return `true`.

Please see `test/test.js` for examples of all of these types in use.

### `object`

Specifies a JSON object mapping string keys to any JSON entity. `properties` should be itself an object mapping string keys to sub-schemas.

```js
{ type: 'object'
properties: {
myInt: { type: 'integer' }
myString: { type: 'string' } } }

// { myInt: 3, myString: 'something' }
```

### `array`

Specifies a JSON array - an ordered list of entities, all of the same type. `items` should be a single sub-schema that all array entries match.

```js
{ type: 'array',
items: { type: 'integer' } }

// [ 1, 2, 3 ]
```

### `number`

Specifies a JSON number (integer or decimal). This is held to the same limitations of all numbers in Javascript. `max` and `min` can (inclusively) limit possible number ranges.

```js
{ type: 'number',
min: 1.5,
max: 11 }

// 7.2
```

### `integer`

Specifies an integer. This is held to the same limitations of all integers in Javascript. `max` and `min` can (inclusively) limit possible number ranges.

```js
{ type: 'integer',
min: 4,
max: 8 }

// 6
```

### `string`

Specifies a JSON string. `regex` can limit the possible input given a Javascript RegExp object, or a string that can be evaulated as a regular expression.

```js
{ type: 'string',
regex: /.*/ }

// 'something'
```

### `boolean`

Specifies a JSON boolean.

```js
{ type: 'boolean' }

/// false
```

### `null`

Specifies JSON null.

```js
{ type: 'null' }

/// null

## Custom Types

Expand Down
11 changes: 0 additions & 11 deletions lib/error.js

This file was deleted.

40 changes: 15 additions & 25 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
var _ = require('lodash')
var n = require('./utils').n
var SchemaValidationError = require('./error').SchemaValidation

var defaultSchemas = {
array: require('./types/array'),
boolean: require('./types/boolean'),
integer: require('./types/number'),
null: require('./types/null'),
number: require('./types/number'),
boolean: require('./types/boolean'),
object: require('./types/object'),
string: require('./types/string'),
array: require('./types/array')
string: require('./types/string')
}

function Confine () {
this.types = defaultSchemas
}

Confine.prototype.validate = function (obj, schema) {
if (n(obj)) return true
if (_.isUndefined(obj)) return true
return this.types[schema.type].validate(obj, schema, this)
}

Expand All @@ -25,16 +24,18 @@ Confine.prototype.validateSchema = function (schema) {
// because we are validating with un-validated schemas, they may throw
try {
if (
// if there is an enum, it must be an array
(!_.isUndefined(schema.enum) && !_.isArray(schema.enum)) ||
// if there is a default and enum, default must be part of the enum
(!n(schema.default) && !n(schema.enum) && schema.enum.indexOf(schema.default) === -1) ||
(!_.isUndefined(schema.default) && !_.isUndefined(schema.enum) && !_.includes(schema.enum, schema.default)) ||
// if there is a default, it must validate
(!n(schema.default) && !this.validate(schema.default, schema)) ||
(!_.isUndefined(schema.default) && !this.validate(schema.default, schema)) ||
// if there is an enum, each item must validate
(!n(schema.enum) && !_.every(schema.enum, function (e) {
(!_.isUndefined(schema.enum) && !_.every(schema.enum, function (e) {
return self.validate(e, schema)
})) ||
// every enum must be unique
(!n(schema.enum) && !_.isEqual(_.uniq(schema.enum), schema.enum))
(!_.isUndefined(schema.enum) && !_.isEqual(_.uniq(schema.enum), schema.enum))
) {
return false
}
Expand All @@ -47,26 +48,15 @@ Confine.prototype.validateSchema = function (schema) {
}

Confine.prototype.normalize = function (obj, schema) {
if (!this.validateSchema(schema)) {
throw new SchemaValidationError(schema)
}

var type = this.types[schema.type]

if (!n(obj) && this.validate(obj, schema)) {
return type.normalize ? type.normalize(obj, schema, this) : obj
if (type.normalize) {
return type.normalize(obj, schema, this)
} else if (!_.isUndefined(obj) && this.validate(obj, schema)) {
return obj
} else {
return this.getDefault(schema)
}
}

Confine.prototype.getDefault = function (schema) {
if (!n(schema.default)) {
return schema.default
} else if (this.types[schema.type].getDefault) {
return this.types[schema.type].getDefault(schema, this)
}
// else return undefined
}

module.exports = Confine
18 changes: 7 additions & 11 deletions lib/types/array.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
var _ = require('lodash')
var n = require('../utils').n

module.exports = {
validate: function (obj, schema, manager) {
Expand All @@ -11,17 +10,14 @@ module.exports = {
)
},
validateSchema: function (schema, manager) {
return (
!n(schema.items) &&
manager.validateSchema(schema.items)
)
return manager.validateSchema(schema.items)
},
normalize: function (obj, schema, manager) {
return _.chain(obj).map(function (value) {
return manager.normalize(value, schema.items)
}).filter(_.negate(n)).value()
},
getDefault: function (schema, manager) {
return []
return _.chain(obj)
.map(function (value) {
return manager.normalize(value, schema.items)
})
.reject(_.isUndefined)
.value()
}
}
4 changes: 2 additions & 2 deletions lib/types/boolean.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
var _ = require('lodash')

module.exports = {
validate: function (obj, s) {
validate: function (obj) {
return _.isBoolean(obj)
},
validateSchema: function (s) {
validateSchema: function () {
return true
}
}
10 changes: 10 additions & 0 deletions lib/types/null.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var _ = require('lodash')

module.exports = {
validate: function (obj) {
return _.isNull(obj)
},
validateSchema: function () {
return true
}
}
18 changes: 10 additions & 8 deletions lib/types/number.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
var _ = require('lodash')
var n = require('../utils').n

function isInteger(value) {
return _.isNumber(value) && isFinite(value) && Math.floor(value) === value
}

module.exports = {
validate: function (obj, s) {
var validator = s.type === 'integer' ? Number.isInteger : _.isFinite
var validator = s.type === 'integer' ? isInteger : _.isFinite

return (
validator(obj) &&
(n(s.enum) || s.enum.indexOf(obj) > -1) &&
(n(s.max) || obj <= s.max) &&
(n(s.min) || obj >= s.min)
(_.isUndefined(s.max) || obj <= s.max) &&
(_.isUndefined(s.min) || obj >= s.min)
)
},

validateSchema: function (s) {
var validator = s.type === 'integer' ? Number.isInteger : _.isFinite

return (
(n(s.max) || validator(s.max)) &&
(n(s.min) || validator(s.min)) &&
(n(s.max) || n(s.min) || s.max >= s.min)
(_.isUndefined(s.max) || validator(s.max)) &&
(_.isUndefined(s.min) || validator(s.min)) &&
(_.isUndefined(s.max) || _.isUndefined(s.min) || s.max >= s.min)
)
}
}
20 changes: 9 additions & 11 deletions lib/types/object.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
var _ = require('lodash')
var n = require('../utils').n

module.exports = {
validate: function (obj, schema, manager) {
return (
_.isPlainObject(obj) &&
_.every(schema.properties, function (value, key) {
return n(obj[key]) || manager.validate(obj[key], value)
return _.isUndefined(obj[key]) || manager.validate(obj[key], value)
})
)
},
validateSchema: function (schema, manager) {
return (
!n(schema.properties) &&
!_.isUndefined(schema.properties) &&
_.every(schema.properties, function (value) {
return manager.validateSchema(value)
})
)
},
normalize: function (obj, schema, manager) {
return _.chain(schema.properties).mapValues(function (subSchema, propName) {
return manager.normalize(obj[propName], subSchema)
}).omit(n).value()
},
getDefault: function (schema, manager) {
return _.chain(schema.properties).mapValues(function(subSchema) {
return manager.getDefault(subSchema)
}).omit(n).value()
var realObj = obj || {}
return _.chain(schema.properties)
.mapValues(function (subSchema, propName) {
return manager.normalize(realObj[propName], subSchema)
})
.omit(_.isUndefined)
.value()
}
}
7 changes: 3 additions & 4 deletions lib/types/string.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
var _ = require('lodash')
var n = require('../utils').n

module.exports = {
validate: function (obj, s) {
return (
_.isString(obj) &&
(n(s.regex) || !!obj.match(s.regex))
(_.isUndefined(s.regex) || !!obj.match(s.regex))
)
},
validateSchema: function (s) {
// creating a RegExp with an invalid string throws
if (!n(s.regex) && _.isString(s.regex)) {
if (!_.isUndefined(s.regex) && _.isString(s.regex)) {
try {
/* eslint no-new: 0 */
new RegExp(s.regex)
} catch (e) {
return false
}
}
return n(s.regex) || _.isString(s.regex) || _.isRegExp(s.regex)
return _.isUndefined(s.regex) || _.isString(s.regex) || _.isRegExp(s.regex)
}
}
7 changes: 0 additions & 7 deletions lib/utils.js

This file was deleted.

0 comments on commit 56cdb2a

Please sign in to comment.