UNPKG

@polyn/blueprint

Version:

An easy to use, flexible, and powerful validation library for nodejs and browsers

111 lines 26.9 kB
"use strict";function _typeof(obj){"@babel/helpers - typeof";return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(obj){return typeof obj}:function(obj){return obj&&"function"==typeof Symbol&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj},_typeof(obj)}function ownKeys(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);enumerableOnly&&(symbols=symbols.filter(function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable})),keys.push.apply(keys,symbols)}return keys}function _objectSpread(target){for(var i=1;i<arguments.length;i++){var source=null!=arguments[i]?arguments[i]:{};i%2?ownKeys(Object(source),!0).forEach(function(key){_defineProperty(target,key,source[key])}):Object.getOwnPropertyDescriptors?Object.defineProperties(target,Object.getOwnPropertyDescriptors(source)):ownKeys(Object(source)).forEach(function(key){Object.defineProperty(target,key,Object.getOwnPropertyDescriptor(source,key))})}return target}function _defineProperty(obj,key,value){key=_toPropertyKey(key);if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}function _defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if("value"in descriptor)descriptor.writable=true;Object.defineProperty(target,_toPropertyKey(descriptor.key),descriptor)}}function _createClass(Constructor,protoProps,staticProps){if(protoProps)_defineProperties(Constructor.prototype,protoProps);if(staticProps)_defineProperties(Constructor,staticProps);Object.defineProperty(Constructor,"prototype",{writable:false});return Constructor}function _toPropertyKey(arg){var key=_toPrimitive(arg,"string");return _typeof(key)==="symbol"?key:String(key)}function _toPrimitive(input,hint){if(_typeof(input)!=="object"||input===null)return input;var prim=input[Symbol.toPrimitive];if(prim!==undefined){var res=prim.call(input,hint||"default");if(_typeof(res)!=="object")return res;throw new TypeError("@@toPrimitive must return a primitive value.")}return(hint==="string"?String:Number)(input)}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function")}}// Node, or global ;(function(root){// eslint-disable-line no-extra-semi "use strict";var module={factories:{}};Object.defineProperty(module,"exports",{get:function get(){return null},set:function set(val){module.factories[val.name]=val.factory},// this property should show up when this object's property names are enumerated enumerable:true,// this property may not be deleted configurable:false});module.exports={name:"blueprint",factory:function factory(is){"use strict";var validators={};var ValueOrError=/*#__PURE__*/_createClass(function ValueOrError(input){_classCallCheck(this,ValueOrError);this.err=input.err||null;this.value=is.defined(input.value)?input.value:null;if(is.array(input.messages)){this.messages=input.messages}else if(input.err){this.messages=[input.err.message]}else{this.messages=null}Object.freeze(this)});var ValidationContext=/*#__PURE__*/_createClass(function ValidationContext(input){_classCallCheck(this,ValidationContext);this.key=input.key;this.value=input.value;this.input=input.input;this.root=input.root;this.output=input.output;this.schema=input.schema});var Blueprint=/*#__PURE__*/_createClass(function Blueprint(input){_classCallCheck(this,Blueprint);this.name=input.name;this.schema=input.schema;this.validate=input.validate;Object.freeze(this)});/** * Makes a message factory that produces an error message on demand * @param {string} options.key - the property name * @param {any} options.value - the value being validated * @param {any} options.input - the object being validated * @param {any?} options.schema - the type definitions * @param {string?} options.type - the type this key should be */var makeDefaultErrorMessage=function makeDefaultErrorMessage(options){return function(){options=options||{};var key=options.key;var value=Object.keys(options).includes("value")?options.value:options.input&&options.input[key];var actualType=is.getType(value);var expectedType=options.type||options.schema&&options.schema[key];return"expected `".concat(key,"` {").concat(actualType,"} to be {").concat(expectedType,"}")}};/** * Support for ad-hoc polymorphism for `isValid` functions: they can throw, * return boolean, or return { isValid: 'boolean', value: 'any', message: 'string[]' }. * @curried * @param {function} isValid - the validation function * @param {ValidationContext} context - the validation context * @param {function} defaultMessageFactory - the default error message */var normalIsValid=function normalIsValid(isValid){return function(context,defaultMessageFactory){try{var result=isValid(context);if(is.boolean(result)){return result?new ValueOrError({value:context.value}):new ValueOrError({err:new Error(defaultMessageFactory())})}else if(result){return{err:result.err,value:result.value}}else{return new ValueOrError({err:new Error("ValidationError: the validator for `".concat(context.key,"` didn't return a value"))})}}catch(err){return new ValueOrError({err:err})}}};/** * If the caller passes in an instance of a class, or a function that * has prototype values, we shouldn't strip those away. Try to create * an object from the input's prototype, and return a plain object if * that fails * @param {any} input - the input that was passed to `validate` */var tryMakeFromProto=function tryMakeFromProto(input){try{return Object.create(Object.getPrototypeOf(input))}catch(e){return{}}};/** * Validates the input values against the schema expectations * @curried * @param {string} name - the name of the model being validated * @param {object} schema - the type definitions * @param {object} input - the values being validated */var validate=function validate(name,schema){return function(input,root){var outcomes=Object.keys(schema).reduce(function(output,key){var keyName=root?"".concat(name,".").concat(key):key;if(is.object(schema[key])){var child=validate("".concat(keyName),schema[key])(input[key],root||input);if(child.err){output.validationErrors=output.validationErrors.concat(child.messages)}output.value[key]=child.value;return output}var validator;if(is.function(schema[key])){validator=normalIsValid(schema[key])}else if(is.regexp(schema[key])){validator=normalIsValid(validators.expression(schema[key]))}else{validator=validators[schema[key]]}if(is.not.function(validator)){output.validationErrors.push("I don't know how to validate ".concat(schema[key]));return output}var context=new ValidationContext({key:"".concat(keyName),value:input&&input[key],input:input,root:root||input,output:output.value,schema:schema});var result=validator(context,makeDefaultErrorMessage(context));if(result&&result.err){output.validationErrors.push(result.err.message);return output}output.value[key]=result?result.value:input[key];return output},{/* output */validationErrors:[],value:tryMakeFromProto(input)});// /reduce if(outcomes.validationErrors.length){return new ValueOrError({err:new Error("Invalid ".concat(name,": ").concat(outcomes.validationErrors.join(", "))),messages:outcomes.validationErrors})}return new ValueOrError({value:outcomes.value})}};// /validate /** * Returns a validator (fluent interface) for validating the input values * against the schema expectations * @param {string} name - the name of the model being validated * @param {object} schema - the type definitions * @param {object} validate.input - the values being validated */var blueprint=function blueprint(name,schema){if(is.not.string(name)||is.not.object(schema)){throw new Error("blueprint requires a name {string}, and a schema {object}")}return new Blueprint({name:name,schema:schema,validate:validate(name,schema)})};/** * Registers a validator by name, so it can be used in blueprints * @param {string} name - the name of the validator * @param {function} validator - the validator */var registerValidator=function registerValidator(name,validator){if(is.not.string(name)||is.not.function(validator)){throw new Error("registerValidator requires a name {string}, and a validator {function}")}if(name==="expression"){validators[name]=validator}else{validators[name]=normalIsValid(validator)}return validator};// /registerValidator /** * Registers a validator, and a nullable validator by the given name, using * the given isValid function * @param {string} name - the name of the type * @param {function} isValid - the validator for testing one instance of this type (must return truthy/falsey) */var registerInstanceOfType=function registerInstanceOfType(name,isValid){var test=normalIsValid(isValid);return[// required registerValidator(name,function(context){var key=context.key;return test(context,makeDefaultErrorMessage({key:key,value:context.value,type:name}))}),// nullable registerValidator("".concat(name,"?"),function(context){var key=context.key,value=context.value;if(is.nullOrUndefined(value)){return{err:null,value:value}}else{return test(context,makeDefaultErrorMessage({key:key,value:context.value,type:name}))}})]};/** * Registers an array validator, and a nullable array validator by the given * name, using the given isValid function * @param {string} name - the name of the type * @param {function} isValid - the validator for testing one instance of this type (must return truthy/falsey) */var registerArrayOfType=function registerArrayOfType(instanceName,arrayName,isValid){var test=normalIsValid(isValid);var validateMany=function validateMany(context,errorMessageFactory){if(is.not.array(context.value)){return{err:new Error(errorMessageFactory()),value:null}}var errors=[];var values=[];context.value.forEach(function(value,index){var key="".concat(context.key,"[").concat(index,"]");var result=test({key:key,value:value,input:context.input,root:context.root},makeDefaultErrorMessage({key:key,value:value,type:instanceName}));if(result.err){// make sure the array key[index] is in the error message var message=result.err.message.indexOf("[".concat(index,"]"))>-1?result.err.message:"(`".concat(key,"`) ").concat(result.err.message);return errors.push(message)}return values.push(result.value)});if(errors.length){return{err:new Error(errors.join(", ")),value:null}}return{err:null,value:values}};return[// required registerValidator(arrayName,function(context){var key=context.key;return validateMany(context,makeDefaultErrorMessage({key:key,value:context.value,type:arrayName}))}),// nullable registerValidator("".concat(arrayName,"?"),function(context){var key=context.key,value=context.value;if(is.nullOrUndefined(value)){return{err:null,value:value}}else{return validateMany(context,makeDefaultErrorMessage({key:key,value:context.value,type:arrayName}))}})]};/** * Registers a validator, a nullable validator, an array validator, and * a nullable array validator based on the given name, using the * given validator function * @param {string} name - the name of the type * @param {function} validator - the validator for testing one instance of this type (must return truthy/falsey) */var registerType=function registerType(name,validator){if(is.not.string(name)||is.not.function(validator)){throw new Error("registerType requires a name {string}, and a validator {function}")}registerInstanceOfType(name,validator);registerArrayOfType(name,"".concat(name,"[]"),validator);var output={};output[name]=validators[name];output["".concat(name,"?")]=validators["".concat(name,"?")];output["".concat(name,"[]")]=validators["".concat(name,"[]")];output["".concat(name,"[]?")]=validators["".concat(name,"[]?")];return output};/** * Registers a blueprint that can be used as a validator * @param {string} name - the name of the model being validated * @param {object} schema - the type definitions */var registerBlueprint=function registerBlueprint(name,schema){var bp;if(schema&&schema.schema){// this must be an instance of a blueprint bp=blueprint(name,schema.schema)}else{bp=blueprint(name,schema)}var cleanMessage=function cleanMessage(key,message){return message.replace("Invalid ".concat(bp.name,": "),"").replace(/expected `/g,"expected `".concat(key,"."))};registerType(bp.name,function(_ref){var key=_ref.key,value=_ref.value;var result=bp.validate(value);if(result.err){result.err.message=cleanMessage(key,result.err.message)}return result});return bp};/** * Registers a regular expression validator by name, so it can be used in blueprints * @param {string} name - the name of the validator * @param {string|RegExp} expression - the expression that will be used to validate the values */var registerExpression=function registerExpression(name,expression){if(is.not.string(name)||is.not.regexp(expression)&&is.not.string(expression)){throw new Error("registerExpression requires a name {string}, and an expression {expression}")}var regex=is.string(expression)?new RegExp(expression):expression;return registerType(name,function(_ref2){var key=_ref2.key,value=_ref2.value;return regex.test(value)===true?new ValueOrError({value:value}):new ValueOrError({err:new Error("expected `".concat(key,"` to match ").concat(regex.toString()))})})};var getValidators=function getValidators(){return _objectSpread({},validators)};var getValidator=function getValidator(name){if(!validators[name]){return}return _objectSpread({},validators[name])};var comparatorToValidator=function comparatorToValidator(comparator){var validator;if(is.function(comparator)){validator=normalIsValid(comparator)}else if(is.regexp(comparator)){validator=normalIsValid(validators.expression(comparator))}else{validator=validators[comparator]}return validator};/** * Fluent interface to support optional function based validators * (i.e. like gt, lt, range, custom), and to use default values when * the value presented is null, or undefined. * @param {any} comparator - the name of the validator, or a function that performs validation */var optional=function optional(comparator){var defaultVal;var from;var validator=comparatorToValidator(comparator);var valueOrDefaultValue=function valueOrDefaultValue(value){if(is.function(defaultVal)){return{value:defaultVal()}}else if(is.defined(defaultVal)){return{value:defaultVal}}else{return{value:value}}};var output=function output(ctx){var context;if(from){context=_objectSpread(_objectSpread({},ctx),{value:from(ctx)})}else{context=ctx}var _context=context,value=_context.value;if(is.nullOrUndefined(value)){return valueOrDefaultValue(value)}else{return validator(context)}};/** * A value factory for producing a value, given the constructor context * @param {function} callback - a callback function that accepts IValidationContext and produces a value */output.from=function(callback){if(is.function(callback)){from=callback}return output};/** * Sets a default value to be used when a value is not given for this property * @param {any} defaultValue - the value to use when this property is null or undefined */output.withDefault=function(defaultValue){defaultVal=defaultValue;return output};return output};/** * Fluent interface to support optional function based validators * (i.e. like gt, lt, range, custom), and to use default values when * the value presented is null, or undefined. * @param {any} comparator - the name of the validator, or a function that performs validation */var required=function required(comparator){var from;var validator=comparatorToValidator(comparator);var output=function output(ctx){var context;if(from){context=_objectSpread(_objectSpread({},ctx),{value:from(ctx)})}else{context=ctx}return validator(context)};/** * A value factory for producing a value, given the constructor context * @param {function} callback - a callback function that accepts IValidationContext and produces a value */output.from=function(callback){if(is.function(callback)){from=callback}return output};return output};return{blueprint:blueprint,registerValidator:registerValidator,registerType:registerType,registerBlueprint:registerBlueprint,registerExpression:registerExpression,optional:optional,required:required,// below are undocumented / subject to breaking changes registerInstanceOfType:registerInstanceOfType,registerArrayOfType:registerArrayOfType,getValidators:getValidators,getValidator:getValidator}}};module.exports={name:"is",factory:function factory(){"use strict";var is={getType:undefined,defined:undefined,nullOrUndefined:undefined,function:undefined,func:undefined,promise:undefined,asyncFunction:undefined,asyncFunc:undefined,object:undefined,array:undefined,string:undefined,boolean:undefined,date:undefined,regexp:undefined,number:undefined,nullOrWhitespace:undefined,decimal:undefined,primitive:undefined,arrayOf:undefined,not:{defined:undefined,nullOrUndefined:undefined,function:undefined,func:undefined,promise:undefined,asyncFunction:undefined,asyncFunc:undefined,object:undefined,array:undefined,string:undefined,boolean:undefined,date:undefined,regexp:undefined,number:undefined,nullOrWhitespace:undefined,decimal:undefined,primitive:undefined,arrayOf:undefined}};var primitives=["boolean","null","undefined","number","bigint","string","symbol"];/** * Produces the printed type (i.e. [object Object], [object Function]), * removes everything except for the type, and returns the lowered form. * (i.e. boolean, number, string, function, asyncfunction, promise, array, * date, regexp, object) */is.getType=function(obj){return Object.prototype.toString.call(obj).replace(/(^\[object )|(\]$)/g,"").toLowerCase()};is.defined=function(obj){return is.getType(obj)!=="undefined"};is.not.defined=function(obj){return is.defined(obj)===false};is.nullOrUndefined=function(obj){return is.not.defined(obj)||obj===null};is.not.nullOrUndefined=function(obj){return is.nullOrUndefined(obj)===false};is.not.nullOrWhitespace=function(str){if(typeof str==="undefined"||str===null){return false}else if(Array.isArray(str)){return true}// ([^\s]*) = is not whitespace // /^$|\s+/ = is empty or whitespace return /([^\s])/.test(str)};is.nullOrWhitespace=function(str){return is.not.nullOrWhitespace(str)===false};is.function=function(obj){var type=is.getType(obj);return type==="function"||type==="asyncfunction"||type==="promise"};is.func=is.function;// typescript support is.not.function=function(obj){return is.function(obj)===false};is.not.func=is.not.function;// typescript support is.promise=function(obj){var type=is.getType(obj);return type==="asyncfunction"||type==="promise"};is.not.promise=function(obj){return is.promise(obj)===false};is.asyncFunction=function(obj){return is.promise(obj)};is.asyncFunc=is.asyncFunction;// consistency for typescript is.not.asyncFunction=function(obj){return is.asyncFunction(obj)===false};is.not.asyncFunc=is.not.asyncFunction;// consistency for typescript is.object=function(obj){return is.getType(obj)==="object"};is.not.object=function(obj){return is.object(obj)===false};is.array=function(obj){return is.getType(obj)==="array"};is.not.array=function(obj){return is.array(obj)===false};is.string=function(obj){return is.getType(obj)==="string"};is.not.string=function(obj){return is.string(obj)===false};is.boolean=function(obj){return is.getType(obj)==="boolean"};is.not.boolean=function(obj){return is.boolean(obj)===false};is.date=function(obj){return is.getType(obj)==="date"&&is.function(obj.getTime)&&!isNaN(obj.getTime())};is.not.date=function(obj){return is.date(obj)===false};is.regexp=function(obj){return is.getType(obj)==="regexp"};is.not.regexp=function(obj){return is.regexp(obj)===false};is.number=function(obj){return is.getType(obj)==="number"};is.not.number=function(obj){return is.number(obj)===false};is.decimal=function(num,places){if(is.not.number(num)){return false}if(!places&&is.number(num)){return true}var padded=+(+num||0).toFixed(20);// pad to the right for whole numbers return padded.toFixed(places)==="".concat(+num)};is.not.decimal=function(val,places){return is.decimal(val,places)===false};is.primitive=function(input){return primitives.indexOf(is.getType(input))>-1};is.not.primitive=function(input){return is.primitive(input)===false};is.arrayOf=function(type){if(!is[type]){throw new Error("is does not support evaluation of {".concat(type,"}"))}return function(input){return input.find(function(v){return is.not[type](v)})===undefined}};is.not.arrayOf=function(type){if(!is[type]){throw new Error("is does not support evaluation of {".concat(type,"}"))}return function(input){return is.arrayOf(type)(input)===false}};return is}};module.exports={name:"numberValidators",factory:function factory(is){"use strict";var makeErrorMessage=function makeErrorMessage(options){return"expected `".concat(options.key,"` to be ").concat(options.comparator," ").concat(options.boundary)};var gt=function gt(min){if(is.not.number(min)){throw new Error("gt requires a minimum number to compare values to")}return function(_ref3){var key=_ref3.key,value=_ref3.value;if(is.number(value)&&value>min){return{err:null,value:value}}return{err:new Error(makeErrorMessage({key:key,comparator:"greater than",boundary:min})),value:null}}};var gte=function gte(min){if(is.not.number(min)){throw new Error("gte requires a minimum number to compare values to")}return function(_ref4){var key=_ref4.key,value=_ref4.value;if(is.number(value)&&value>=min){return{err:null,value:value}}return{err:new Error(makeErrorMessage({key:key,comparator:"greater than, or equal to",boundary:min})),value:null}}};var lt=function lt(max){if(is.not.number(max)){throw new Error("lt requires a maximum number to compare values to")}return function(_ref5){var key=_ref5.key,value=_ref5.value;if(is.number(value)&&value<max){return{err:null,value:value}}return{err:new Error(makeErrorMessage({key:key,comparator:"less than",boundary:max})),value:null}}};var lte=function lte(max){if(is.not.number(max)){throw new Error("lte requires a maximum number to compare values to")}return function(_ref6){var key=_ref6.key,value=_ref6.value;if(is.number(value)&&value<=max){return{err:null,value:value}}return{err:new Error(makeErrorMessage({key:key,comparator:"less than, or equal to",boundary:max})),value:null}}};var range=function range(options){if(!options){throw new Error("You must specify a range")}else if(is.not.number(options.gt)&&is.not.number(options.gte)){throw new Error("You must specify `gt`, or `gte` {number} when defining a range")}else if(is.not.number(options.lt)&&is.not.number(options.lte)){throw new Error("You must specify `lt`, or `lte` {number} when defining a range")}var gtExpression=options.gt?gt(options.gt):gte(options.gte);var ltExpression=options.lt?lt(options.lt):lte(options.lte);return function(input){var ltOutcome=ltExpression(input);if(ltOutcome.err){return ltOutcome}return gtExpression(input)}};var optional=function optional(comparator){return function(input){var validator=comparator(input);return function(context){var value=context.value;if(is.nullOrUndefined(value)){return{value:value}}else{return validator(context)}}}};return{gt:gt,gte:gte,lt:lt,lte:lte,range:range,// backward compatibility - can be removed in v3 __optional:{gt:optional(gt),gte:optional(gte),lt:optional(lt),lte:optional(lte),range:optional(range)}}}};module.exports={name:"registerCommonTypes",factory:function factory(is,Blueprint){"use strict";var registerType=Blueprint.registerType;var types=["function","asyncFunction","promise","object","array","boolean","date","number","decimal","regexp","primitive"// 'string' registered separately, below ];var errorMessage=function errorMessage(type){return function(key,value){return"expected `".concat(key,"` {").concat(is.getType(value),"} to be {").concat(type,"}")}};types.forEach(function(type){registerType(type,function(_ref7){var key=_ref7.key,value=_ref7.value;return is[type](value)?{err:null,value:value}:{err:new Error(errorMessage(type)(key,value))}})});registerType("string",function(_ref8){var key=_ref8.key,value=_ref8.value;if(is.string(value)){var trimmed=value.trim();if(trimmed.length){return{value:trimmed}}return{err:new Error("expected `".concat(key,"` {").concat(is.getType(value),"} to not be an empty string"))}}else{return{err:new Error(errorMessage("string")(key,value))}}});registerType("any",function(_ref9){var key=_ref9.key,value=_ref9.value;return is.not.nullOrUndefined(value)?{err:null,value:value}:{err:new Error(errorMessage("any")(key,value))}})}};module.exports={name:"registerDecimals",factory:function factory(is,Blueprint){"use strict";var registerValidator=Blueprint.registerValidator;// support up to 15 decimal places for decimal precision var _loop=function _loop(i){registerValidator("decimal:".concat(i),function(_ref10){var key=_ref10.key,value=_ref10.value;return is.decimal(value,i)?{err:null,value:value}:{err:new Error("expected `".concat(key,"` to be a {decimal} with ").concat(i," places")),value:null}});registerValidator("decimal:".concat(i,"?"),function(_ref11){var key=_ref11.key,value=_ref11.value;if(is.nullOrUndefined(value)){return{err:null,value:value}}else if(is.decimal(value,i)){return{err:null,value:value}}else{return{err:new Error("expected `".concat(key,"` to be a {decimal} with ").concat(i," places")),value:null}}})};for(var i=1;i<=15;i+=1){_loop(i)}}};module.exports={name:"registerExpressions",factory:function factory(Blueprint){"use strict";var registerValidator=Blueprint.registerValidator;registerValidator("expression",function(regex){return function(_ref12){var key=_ref12.key,value=_ref12.value;return regex.test(value)===true?{value:value}:{err:new Error("expected `".concat(key,"` to match ").concat(regex.toString()))}}})}};var is=module.factories.is();var numberValidators=module.factories.numberValidators(is);var blueprint=module.factories.blueprint(is);// backward compatibility - can be removed in v3 Object.keys(numberValidators.__optional).forEach(function(key){blueprint.optional[key]=numberValidators.__optional[key]});delete numberValidators.__optional;root.polyn=root.polyn||{};root.polyn.blueprint=Object.freeze(Object.assign({is:is},numberValidators,blueprint));module.factories.registerCommonTypes(is,root.polyn.blueprint);module.factories.registerDecimals(is,root.polyn.blueprint);module.factories.registerExpressions(root.polyn.blueprint);// we don't need these anymore delete module.factories})(window);