UNPKG

@deskpro/react-forms

Version:
774 lines (660 loc) 25.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** * @copyright 2014, Mathias Buus * @copyright 2015, Prometheus Research, LLC */ exports.default = compileValidator; var _generateObjectProperty = require('generate-object-property'); var _generateObjectProperty2 = _interopRequireDefault(_generateObjectProperty); var _generateFunction = require('generate-function'); var _generateFunction2 = _interopRequireDefault(_generateFunction); var _jsonpointer = require('jsonpointer'); var _jsonpointer2 = _interopRequireDefault(_jsonpointer); var _isArray = require('lodash/isArray'); var _isArray2 = _interopRequireDefault(_isArray); var _Format = require('./Format'); var _Format2 = _interopRequireDefault(_Format); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var HTTP_REF = /^https?:\/\//; var SPLIT_NAME = /[\[\]]/; var DEFAULT_ERROR_MESSAGES = { IS_REQUIRED: 'is required', DOES_NOT_CONFORM_TO_FORMAT: 'does not conform to: ', INVALID: 'invalid', IS_THE_WRONG_TYPE: 'is the wrong type', MUST_BE_UNIQUE: 'must be unique', HAS_ADDITIONAL_ITEMS: 'has additional items', HAS_ADDITIONAL_PROPERTIES: 'has additional properties', MUST_BE_AN_ENUM_VALUE: 'must be an enum value', DEPENDENCIES_NOT_SET: 'dependencies not set', REFERENCED_SCHEMA_DOES_NOT_MATCH: 'referenced schema does not match', NEGATIVE_SCHEMA_MATCHES: 'negative schema matches', PATTERN_MISMATCH: 'pattern mismatch', NO_SCHEMAS_MATCH: 'no schemas match', NO_OR_MORE_THAN_ONE_SCHEMAS_MATCH: 'no (or more than one) schemas match', HAS_A_REMAINDER: 'has a remainder', HAS_MORE_PROPERTIES_THAN_ALLOWED: 'has more properties than allowed', HAS_LESS_PROPERTIES_THAN_ALLOWED: 'has less properties than allowed', HAS_MORE_ITEMS_THAN_ALLOWED: 'has more items than allowed', HAS_LESS_ITEMS_THAN_ALLOWED: 'has less items than allowed', HAS_LONGER_LENGTH_THAN_ALLOWED: 'has longer length than allowed', HAS_LESS_LENGTH_THAN_ALLOWED: 'has less length than allowed', IS_LESS_THAN_MINIMUM: 'is less than minimum', IS_MORE_THAN_MAXIMUM: 'is more than maximum' }; function getSchemaByRef(obj, additionalSchemas, ptr) { if (HTTP_REF.test(ptr)) { return null; } function visit(sub) { if (sub && sub.id === ptr) { return sub; } if ((typeof sub === 'undefined' ? 'undefined' : _typeof(sub)) !== 'object' || !sub) { return null; } return Object.keys(sub).reduce(function (res, k) { return res || visit(sub[k]); }, null); } var res = visit(obj); if (res) { return res; } ptr = ptr.replace(/^#/, ''); ptr = ptr.replace(/\/$/, ''); try { return _jsonpointer2.default.get(obj, decodeURI(ptr)); } catch (err) { var other = additionalSchemas[ptr] || additionalSchemas[ptr.replace(/^#/, '')]; return other || null; } } var Runtime = { errorFromError: function errorFromError(errors, schema, keyPath, error) { var field = error.field ? keyPath + '.' + error.field : keyPath; errors.push(_extends({}, error, { field: field, schema: schema })); }, errorFromErrorList: function errorFromErrorList(errors, schema, keyPath, errorList) { for (var i = 0; i < errorList.length; i++) { var error = errorList[i]; if ((typeof error === 'undefined' ? 'undefined' : _typeof(error)) === 'object') { Runtime.errorFromError(errors, schema, keyPath, error); } else if (typeof error === 'string') { Runtime.errorFromString(errors, schema, keyPath, error); } } }, errorFromString: function errorFromString(errors, schema, keyPath, error) { errors.push({ field: keyPath, message: error, schema: schema }); }, errorFrom: function errorFrom(errors, schema, keyPath, error) { var typeOf = typeof error === 'undefined' ? 'undefined' : _typeof(error); if (typeOf === 'string') { Runtime.errorFromString(errors, schema, keyPath, error); } else if ((0, _isArray2.default)(error)) { Runtime.errorFromErrorList(errors, schema, keyPath, error); } else if (typeOf === 'object') { Runtime.errorFromError(errors, schema, keyPath, error); } }, arrayIsUnique: function arrayIsUnique(array) { var list = []; for (var i = 0; i < array.length; i++) { list.push(_typeof(array[i]) === 'object' ? JSON.stringify(array[i]) : array[i]); } for (var _i = 1; _i < list.length; _i++) { if (list.indexOf(list[_i]) !== _i) { return false; } } return true; } }; function formatName(field) { field = field.replace(/\[/g, '[\x01').split(SPLIT_NAME); var formatted = []; for (var i = 0; i < field.length; i++) { var part = field[i]; if (part[0] === '\x01') { formatted.push(JSON.stringify('.')); formatted.push(part.slice(1)); } else { formatted.push(JSON.stringify(part)); } } return formatted.join('+'); } var types = { any: function any() { return 'true'; }, enum: function _enum() { return 'true'; }, null: function _null(name) { return name + ' === null'; }, boolean: function boolean(name) { return 'typeof ' + name + ' === "boolean"'; }, array: function array(name) { return 'Array.isArray(' + name + ')'; }, object: function object(name) { return 'typeof ' + name + ' === "object" && ' + name + ' && !Array.isArray(' + name + ')'; }, number: function number(name) { return 'typeof ' + name + ' === "number"'; }, integer: function integer(name) { return 'typeof ' + name + ' === "number" && (Math.floor(' + name + ') === ' + name + ' || ' + name + ' > 9007199254740992 || ' + name + ' < -9007199254740992)'; // eslint-disable-line max-len }, string: function string(name) { return 'typeof ' + name + ' === "string"'; } }; function compile(schema, cache, root) { var opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var messages = opts.messages || DEFAULT_ERROR_MESSAGES; var reporter = opts.reporter; var formats = _extends({}, _Format2.default, opts.formats); var scope = _extends({ formats: formats }, Runtime); var verbose = opts ? !!opts.verbose : false; var undefinedAsObject = opts ? !!opts.undefinedAsObject : false; var nullAsObject = opts ? !!opts.nullAsObject : false; var nullAsUndefined = opts ? !!opts.nullAsUndefined : false; var undefinedAsArray = opts ? !!opts.undefinedAsArray : false; var nullAsArray = opts ? !!opts.nullAsArray : false; var greedy = opts && opts.greedy !== undefined ? opts.greedy : false; var syms = {}; function gensym(name) { return name + (syms[name] = (syms[name] || 0) + 1); } var reversePatterns = {}; var patterns = function patterns(p) { if (reversePatterns[p]) { return reversePatterns[p]; } var n = gensym('pattern'); scope[n] = new RegExp(p); reversePatterns[p] = n; return n; }; var vars = ['i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y', 'z']; var genloop = function genloop() { var v = vars.shift(); vars.push(v + v[0]); return v; }; function visit(name, _dataSym, node, reporter, filter) { var properties = node.properties; var type = node.type; var tuple = false; var dataSym = gensym('data'); validate('var %s = %s', dataSym, _dataSym); var nodeSym = gensym('node'); scope[nodeSym] = node; if (Array.isArray(node.items)) { // tuple type properties = {}; node.items.forEach(function (item, i) { properties[i] = item; }); type = 'array'; tuple = true; } var indent = 0; function error(msg, prop, value, schema) { validate('errors++'); if (reporter === true) { var errNameSym = formatName(prop || name); validate('if (validate.errors === null) validate.errors = []'); if (verbose) { validate('validate.errors.push({field:%s,message:%s,value:%s,schema:%s})', errNameSym, JSON.stringify(msg), value || name, schema || nodeSym); } else { validate('validate.errors.push({field:%s,message:%s,schema:%s})', errNameSym, JSON.stringify(msg), schema || nodeSym); } } } function errorFrom(sym) { validate('errors++'); if (reporter === true) { validate('if (validate.errors === null) validate.errors = []'); validate('errorFrom(validate.errors, %s, %s, %s)', nodeSym, formatName(name), sym); } } if (node.required === true) { indent++; if (nullAsUndefined) { validate('if (%s == undefined) {', dataSym); } else { validate('if (%s === undefined) {', dataSym); } error(messages.IS_REQUIRED); validate('} else {'); } else { if (node.type === 'object' && (undefinedAsObject || nullAsObject)) { // eslint-disable-line no-lonely-if,max-len if (undefinedAsObject && nullAsObject) { validate('if (%s == null) %s = {}', dataSym, dataSym); } else if (undefinedAsObject) { validate('if (%s === undefined) %s = {}', dataSym, dataSym); } else if (nullAsObject) { validate('if (%s === null) %s = {}', dataSym, dataSym); } } else if (node.type === 'array' && (undefinedAsArray || nullAsArray)) { if (undefinedAsArray && nullAsArray) { validate('if (%s == null) %s = []', dataSym, dataSym); } else if (undefinedAsArray) { validate('if (%s === undefined) %s = []', dataSym, dataSym); } else if (nullAsArray) { validate('if (%s === null) %s = []', dataSym, dataSym); } } else { indent++; if (nullAsUndefined) { validate('if (%s != undefined) {', dataSym); } else { validate('if (%s !== undefined) {', dataSym); } } } var valid = [].concat(type).map(function (t) { return types[t || 'any'](dataSym); }).join(' || ') || 'true'; if (valid !== 'true') { indent++; validate('if (!(%s)) {', valid); error(messages.IS_THE_WRONG_TYPE); validate('} else {'); } if (tuple) { if (node.additionalItems === false) { validate('if (%s.length > %d) {', dataSym, node.items.length); error(messages.HAS_ADDITIONAL_ITEMS); validate('}'); } else if (node.additionalItems) { var i = genloop(); validate('for (var %s = %d; %s < %s.length; %s++) {', i, node.items.length, i, dataSym, i); visit(name + '[' + i + ']', dataSym + '[' + i + ']', node.additionalItems, reporter, filter); validate('}'); } } if (node.format && (formats[node.format] || typeof node.format === 'function')) { if (type !== 'string' && formats[node.format]) { validate('if (%s) {', types.string(dataSym)); } var n = gensym('format'); if (typeof node.format === 'function') { scope[n] = node.format; } else { scope[n] = formats[node.format]; } if (typeof scope[n] === 'function') { var r = gensym('result'); validate('var %s = %s(%s, %s)', r, n, dataSym, nodeSym); validate('if (%s === false) {', r); error(messages.INVALID); validate('} else if (%s !== true) {', r); errorFrom(r); validate('}'); } else { validate('if (!%s.test(%s)) {', n, dataSym); error(messages.DOES_NOT_CONFORM_TO_FORMAT + node.format); validate('}'); } if (type !== 'string' && formats[node.format]) { validate('}'); } } if (Array.isArray(node.required)) { var checkRequired = function checkRequired(req) { if (nullAsUndefined) { validate('if (%s == undefined) {', (0, _generateObjectProperty2.default)(dataSym, req)); } else { validate('if (%s === undefined) {', (0, _generateObjectProperty2.default)(dataSym, req)); } var reqSchema = (0, _generateObjectProperty2.default)(nodeSym, 'properties') + ' ? ' + (0, _generateObjectProperty2.default)((0, _generateObjectProperty2.default)(nodeSym, 'properties'), req) + ' : undefined'; // eslint-disable-line max-len error(messages.IS_REQUIRED, (0, _generateObjectProperty2.default)(name, req), undefined, reqSchema); validate('missing++'); validate('}'); }; validate('if ((%s)) {', type !== 'object' ? types.object(dataSym) : 'true'); validate('var missing = 0'); node.required.map(checkRequired); validate('}'); if (!greedy) { validate('if (missing === 0) {'); indent++; } } if (node.uniqueItems) { if (type !== 'array') { validate('if (%s) {', types.array(dataSym)); } validate('if (!arrayIsUnique(%s)) {', dataSym); error(messages.MUST_BE_UNIQUE); validate('}'); if (type !== 'array') { validate('}'); } } if (node.enum) { var complex = node.enum.some(function (e) { return (typeof e === 'undefined' ? 'undefined' : _typeof(e)) === 'object'; }); var compare = complex ? function (e) { return 'JSON.stringify(' + dataSym + ') !== JSON.stringify(' + JSON.stringify(e) + ')'; } : function (e) { return dataSym + ' !== ' + JSON.stringify(e); }; validate('if (%s) {', node.enum.map(compare).join(' && ') || 'false'); error(messages.MUST_BE_AN_ENUM_VALUE); validate('}'); } if (node.dependencies) { if (type !== 'object') { validate('if (%s) {', types.object(dataSym)); } Object.keys(node.dependencies).forEach(function (key) { var deps = node.dependencies[key]; if (typeof deps === 'string') { deps = [deps]; } var exists = function exists(k) { return (0, _generateObjectProperty2.default)(dataSym, k) + ' !== undefined'; }; if (Array.isArray(deps)) { validate('if (%s !== undefined && !(%s)) {', (0, _generateObjectProperty2.default)(dataSym, key), deps.map(exists).join(' && ') || 'true'); error(messages.DEPENDENCIES_NOT_SET); validate('}'); } if ((typeof deps === 'undefined' ? 'undefined' : _typeof(deps)) === 'object') { validate('if (%s !== undefined) {', (0, _generateObjectProperty2.default)(dataSym, key)); visit(name, dataSym, deps, reporter, filter); validate('}'); } }); if (type !== 'object') { validate('}'); } } if (node.additionalProperties || node.additionalProperties === false) { if (type !== 'object') { validate('if (%s) {', types.object(dataSym)); } var _i2 = genloop(); var keys = gensym('keys'); var toCompare = function toCompare(p) { return keys + '[' + _i2 + '] !== ' + JSON.stringify(p); }; var toTest = function toTest(p) { return '!' + patterns(p) + '.test(' + keys + '[' + _i2 + '])'; }; var additionalProp = Object.keys(properties || {}).map(toCompare).concat(Object.keys(node.patternProperties || {}).map(toTest)).join(' && ') || 'true'; validate('var %s = Object.keys(%s)', keys, dataSym); validate(' for (var %s = 0; %s < %s.length; %s++) {', _i2, _i2, keys, _i2); validate(' if (%s) {', additionalProp); if (node.additionalProperties === false) { if (filter) { validate('delete %s', dataSym + '[' + keys + '[' + _i2 + ']]'); } error(messages.HAS_ADDITIONAL_PROPERTIES, null, JSON.stringify(name + '.') + ' + ' + keys + '[' + _i2 + ']'); } else { visit(name + '[' + keys + '[' + _i2 + ']]', dataSym + '[' + keys + '[' + _i2 + ']]', node.additionalProperties, reporter, filter); } validate(' }'); validate('}'); if (type !== 'object') { validate('}'); } } if (node.$ref) { var sub = getSchemaByRef(root, opts && opts.schemas || {}, node.$ref); if (sub) { var fn = cache[node.$ref]; if (!fn) { cache[node.$ref] = function proxy(data) { return fn(data); }; fn = compile(sub, cache, root, _extends({}, opts, { reporter: false })); } var _n = gensym('ref'); scope[_n] = fn; validate('if (!(%s(%s))) {', _n, dataSym); error(messages.REFERENCED_SCHEMA_DOES_NOT_MATCH); validate('}'); } } if (node.not) { var prev = gensym('prev'); validate('var %s = errors', prev); visit(name, dataSym, node.not, false, filter); validate('if (%s === errors) {', prev); error(messages.NEGATIVE_SCHEMA_MATCHES); validate('} else {'); validate('errors = %s', prev); validate('}'); } if (node.items && !tuple) { if (type !== 'array') { validate('if (%s) {', types.array(dataSym)); } var _i3 = genloop(); validate('for (var %s = 0; %s < %s.length; %s++) {', _i3, _i3, dataSym, _i3); visit(name + '[' + _i3 + ']', dataSym + '[' + _i3 + ']', node.items, reporter, filter); validate('}'); if (type !== 'array') { validate('}'); } } if (node.patternProperties) { if (type !== 'object') { validate('if (%s) {', types.object(dataSym)); } var _keys = gensym('keys'); var _i4 = genloop(); validate('var %s = Object.keys(%s)', _keys, dataSym); validate('for (var %s = 0; %s < %s.length; %s++) {', _i4, _i4, _keys, _i4); Object.keys(node.patternProperties).forEach(function (key) { var p = patterns(key); validate('if (%s.test(%s)) {', p, _keys + '[' + _i4 + ']'); visit(name + '[' + _keys + '[' + _i4 + ']]', dataSym + '[' + _keys + '[' + _i4 + ']]', node.patternProperties[key], reporter, filter); validate('}'); }); validate('}'); if (type !== 'object') { validate('}'); } } if (node.pattern) { var p = patterns(node.pattern); if (type !== 'string') { validate('if (%s) {', types.string(dataSym)); } validate('if (!(%s.test(%s))) {', p, dataSym); error(messages.PATTERN_MISMATCH); validate('}'); if (type !== 'string') { validate('}'); } } if (node.allOf) { node.allOf.forEach(function (sch) { visit(name, dataSym, sch, reporter, filter); }); } if (node.anyOf && node.anyOf.length) { var _prev = gensym('prev'); node.anyOf.forEach(function (sch, i) { if (i === 0) { validate('var %s = errors', _prev); } else { validate('if (errors !== %s) {', _prev); validate('errors = %s', _prev); } visit(name, dataSym, sch, false, false); }); node.anyOf.forEach(function (sch, i) { if (i) { validate('}'); } }); validate('if (%s !== errors) {', _prev); error(messages.NO_SCHEMAS_MATCH); validate('}'); } if (node.oneOf && node.oneOf.length) { var _prev2 = gensym('prev'); var passes = gensym('passes'); validate('var %s = errors', _prev2); validate('var %s = 0', passes); node.oneOf.forEach(function (sch) { visit(name, dataSym, sch, false, false); validate('if (%s === errors) {', _prev2); validate(' %s++', passes); validate('} else {'); validate(' errors = %s', _prev2); validate('}'); }); validate('if (%s !== 1) {', passes); error(messages.NO_OR_MORE_THAN_ONE_SCHEMAS_MATCH); validate('}'); } if (node.multipleOf !== undefined) { if (type !== 'number' && type !== 'integer') { validate('if (%s) {', types.number(dataSym)); } var factor = (node.multipleOf | 0) !== node.multipleOf ? Math.pow(10, node.multipleOf.toString().split('.').pop().length) : 1; if (factor > 1) { validate('if ((%d*%s) % %d) {', factor, dataSym, factor * node.multipleOf); } else { validate('if (%s % %d) {', dataSym, node.multipleOf); } error(messages.HAS_A_REMAINDER); validate('}'); if (type !== 'number' && type !== 'integer') { validate('}'); } } if (node.maxProperties !== undefined) { if (type !== 'object') { validate('if (%s) {', types.object(dataSym)); } validate('if (Object.keys(%s).length > %d) {', dataSym, node.maxProperties); error(messages.HAS_MORE_PROPERTIES_THAN_ALLOWED); validate('}'); if (type !== 'object') { validate('}'); } } if (node.minProperties !== undefined) { if (type !== 'object') { validate('if (%s) {', types.object(dataSym)); } validate('if (Object.keys(%s).length < %d) {', dataSym, node.minProperties); error(messages.HAS_LESS_PROPERTIES_THAN_ALLOWED); validate('}'); if (type !== 'object') { validate('}'); } } if (node.maxItems !== undefined) { if (type !== 'array') { validate('if (%s) {', types.array(dataSym)); } validate('if (%s.length > %d) {', dataSym, node.maxItems); error(messages.HAS_MORE_ITEMS_THAN_ALLOWED); validate('}'); if (type !== 'array') { validate('}'); } } if (node.minItems !== undefined) { if (type !== 'array') { validate('if (%s) {', types.array(dataSym)); } validate('if (%s.length < %d) {', dataSym, node.minItems); error(messages.HAS_LESS_ITEMS_THAN_ALLOWED); validate('}'); if (type !== 'array') { validate('}'); } } if (node.maxLength !== undefined) { if (type !== 'string') { validate('if (%s) {', types.string(dataSym)); } validate('if (%s.length > %d) {', dataSym, node.maxLength); error(messages.HAS_LONGER_LENGTH_THAN_ALLOWED); validate('}'); if (type !== 'string') { validate('}'); } } if (node.minLength !== undefined) { if (type !== 'string') { validate('if (%s) {', types.string(dataSym)); } validate('if (%s.length < %d) {', dataSym, node.minLength); error(messages.HAS_LESS_LENGTH_THAN_ALLOWED); validate('}'); if (type !== 'string') { validate('}'); } } if (node.minimum !== undefined) { validate('if (%s %s %d) {', dataSym, node.exclusiveMinimum ? '<=' : '<', node.minimum); error(messages.IS_LESS_THAN_MINIMUM); validate('}'); } if (node.maximum !== undefined) { validate('if (%s %s %d) {', dataSym, node.exclusiveMaximum ? '>=' : '>', node.maximum); error(messages.IS_MORE_THAN_MAXIMUM); validate('}'); } if (properties) { Object.keys(properties).forEach(function (p) { visit((0, _generateObjectProperty2.default)(name, p), (0, _generateObjectProperty2.default)(dataSym, p), properties[p], reporter, filter); }); } while (indent--) { validate('}'); } } var validate = (0, _generateFunction2.default)('function validate(data) {')('validate.errors = null')('var errors = 0'); visit('data', 'data', schema, reporter, opts && opts.filter); validate('return errors === 0')('}'); validate = validate.toFunction(scope); validate.errors = null; validate.__defineGetter__('error', function () { if (!validate.errors) { return ''; } else { return validate.errors.map(function (err) { return err.field + ' ' + err.message; }).join('\n'); } }); validate.toJSON = function () { return schema; }; return validate; } /** * Compile a new JSON Schema validator for the specified schema and options. */ function compileValidator(schema, opts) { if (typeof schema === 'string') { schema = JSON.parse(schema); } return compile(schema, {}, schema, _extends({}, opts, { reporter: true })); }