var assign = require('object-assign'); var mschema = require('mschema'); var v = require('validator'); /** * Custom types. * To add a custom type, add a key with the type name and a value * in the form {parent: {...}, assertValid: function(d) {...}} * where parent is the base mschema spec and validate is a function * accepting one data point and throwing an array of errors if invalid. * * Error array format is derived from mschema and is in the form * [{property: x, constraint: x, actual: x, expected: x, message: x} ... ] */ var types = { /** * Larger text inputs. Currently just proxies to string type. */ 'text': { parent: {type: 'string'}, // Let parent handle validation assertValid: function(spec, value) {} }, 'video': { parent: {type: 'string'}, // Let parent handle validation assertValid: function(spec, value) {} }, 'enum': { parent: {type: 'string'}, assertValid: function(spec, value) { value = value || ''; if (spec.options.indexOf(value.trim()) === -1) { throw [{ constraint: 'enum', actual: value, expected: JSON.stringify(spec.options), message: 'Invalid value' }]; } } }, 'captioned-image-list': { parent: [{ uri: { type: 'string' }, // TODO Implement URI type caption: { type: 'string' } }], assertValid: function(spec, value) { var message; var actual; if (!value || !value.length) { throw [{ message: 'Not an array', expected: JSON.stringify(this.parent), actual: value }]; } else { value.forEach(function(obj) { if (!(obj.uri && obj.caption)) { throw [{ message: 'Array contains invalid object', expected: JSON.stringify(this.parent), actual: obj }]; } }); } } } } /** * OKSchema! * Meant as a thin wrapper around some existing schema validation * module, mostly to allow for the extension of types. */ function OKSchema(spec) { if (!(this instanceof OKSchema)) return new OKSchema(spec); if (!spec) throw new Error('No spec provided to OKSchema'); spec = assign({}, spec); // Cache the mschema version of our spec this._mschemaSpec = Object.keys(spec).reduce(function(cache, prop) { // If custom type, return its parent spec var type = spec[prop].type; if (types[type]) { cache[prop] = types[type].parent; // Otherwise, it's already in mschema format } else { cache[prop] = spec[prop]; } return cache; }, {}); Object.defineProperty(this, 'spec', { value: spec, writable: false }); } OKSchema.prototype.assertValid = function(data) { data = data || {}; var spec = this.spec; // Run through custom validators, they'll throw if invalid Object.keys(data).forEach(function(prop) { var type = spec[prop].type; if (types[type]) { types[type].assertValid(spec[prop], data[prop]); } }); var result = mschema.validate(data, this.toMschema()); if (!result.valid) throw result.errors; }; /** * Return our custom spec as an mschema spec */ OKSchema.prototype.toMschema = function() { return this._mschemaSpec; }; module.exports = OKSchema;