@deskpro/react-forms
Version:
Forms library for React
774 lines (660 loc) • 25.4 kB
JavaScript
;
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 }));
}