immutable-validation
Advanced tools
Comparing version
@@ -96,4 +96,8 @@ (function webpackUniversalModuleDefinition(root, factory) { | ||
var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); | ||
var _createClass = (function () { 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, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } | ||
@@ -106,3 +110,84 @@ | ||
var hasProp = Object.prototype.hasOwnProperty; | ||
var SELF = 'self'; | ||
function buildRuleReducer(dataToValidate) { | ||
return function (messagesForProp, rule) { | ||
var _runRule = runRule(rule, dataToValidate); | ||
var isValid = _runRule.isValid; | ||
var message = _runRule.message; | ||
if (!isValid) { | ||
return messagesForProp.add(message); | ||
} | ||
return messagesForProp; | ||
}; | ||
} | ||
function resultReducer(isValid, _ref) { | ||
var _ref2 = _slicedToArray(_ref, 2); | ||
var prop = _ref2[1]; | ||
if (isValid === false) return false; | ||
if (prop.has('isValid')) return prop.get('isValid'); | ||
if (prop.has(SELF) && prop.get(SELF).count()) return false; | ||
return prop.entrySeq().filter(function (_ref3) { | ||
var _ref32 = _slicedToArray(_ref3, 1); | ||
var key = _ref32[0]; | ||
return key !== 'isValid' && key !== SELF; | ||
}).reduce(resultReducer, isValid); | ||
} | ||
function runRule(rule, val) { | ||
var result = rule(val); | ||
if (!hasProp.call(result, 'isValid') || !hasProp.call(result, 'message')) { | ||
throw new Error('Rules must return a result with the properties `isValid` and `message`. Use ImmutableValidation.rule()'); | ||
} | ||
return result; | ||
} | ||
function getIterable(propName, accessor, data) { | ||
var iter = accessor(data); | ||
if (!(0, _util.isIterable)(iter)) { | ||
throw new TypeError('`' + propName + '` is a non-iterable value. ruleForEach applies a validation rule to each item in an iterable'); | ||
} | ||
return iter; | ||
} | ||
function setMerged(vState, propName, newData) { | ||
if (vState.has(propName)) { | ||
newData = vState.get(propName).merge(newData); | ||
} | ||
return vState.set(propName, (0, _immutable.Map)(newData)); | ||
} | ||
var ValidatorWrapper = (function () { | ||
function ValidatorWrapper(chooseValidator) { | ||
_classCallCheck(this, ValidatorWrapper); | ||
this.getValidator = chooseValidator; | ||
} | ||
_createClass(ValidatorWrapper, [{ | ||
key: 'validate', | ||
value: function validate(dataToValidate) { | ||
var v = this.getValidator(dataToValidate); | ||
if (!Validator.isInstance(v)) { | ||
return (0, _immutable.Map)({ isValid: true }); | ||
} | ||
return v.validate(dataToValidate); | ||
} | ||
}]); | ||
return ValidatorWrapper; | ||
})(); | ||
var Validator = (function () { | ||
@@ -113,6 +198,4 @@ function Validator() { | ||
this.ruleSets = []; | ||
this.validationState = new _immutable.Map({ | ||
isValid: true, | ||
messages: new _immutable.Map() | ||
this.validationState = (0, _immutable.Map)({ | ||
isValid: true | ||
}); | ||
@@ -128,13 +211,38 @@ } | ||
return rules.length === 1 && Validator.isInstance(rules[0]) ? this._addValidatorToRuleSets(propName, accessor, rules[0]) : this._addRuleSetToRuleSets(propName, accessor, rules); | ||
return Validator.isInstance(rules[0]) ? this._addValidatorToRuleSets(propName, accessor, rules[0]) : this._addRuleSetToRuleSets(propName, accessor, rules); | ||
} | ||
}, { | ||
key: 'ruleForEach', | ||
value: function ruleForEach(propName, accessor) { | ||
for (var _len2 = arguments.length, rules = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { | ||
rules[_key2 - 2] = arguments[_key2]; | ||
} | ||
return Validator.isInstance(rules[0]) ? this._addForEachValidatorToRuleSets(propName, accessor, rules[0]) : this._addForEachRuleSetToRuleSets(propName, accessor, rules); | ||
} | ||
}, { | ||
key: 'validate', | ||
value: function validate(dataToValidate) { | ||
var newVState = this.ruleSets.reduce(function (messages, ruleSet) { | ||
return ruleSet(dataToValidate, messages); | ||
}, (0, _immutable.Map)()); | ||
var isValid = newVState.entrySeq().filter(function (_ref4) { | ||
var _ref42 = _slicedToArray(_ref4, 1); | ||
var key = _ref42[0]; | ||
return key !== 'isValid'; | ||
}).reduce(resultReducer, true); | ||
this.validationState = newVState.set('isValid', isValid); | ||
return this.validationState; | ||
} | ||
}, { | ||
key: '_addValidatorToRuleSets', | ||
value: function _addValidatorToRuleSets(propName, accessor, validator) { | ||
this.validationState = this.validationState.setIn(['messages', propName], new _immutable.Map()); | ||
this.ruleSets.push(function (dataToValidate, validationMessages) { | ||
var val = accessor(dataToValidate, propName); | ||
this.ruleSets.push(function (data, vState) { | ||
var val = accessor(data); | ||
var validatorResult = validator.validate(val); | ||
return validationMessages.set(propName, validatorResult); | ||
return setMerged(vState, propName, validatorResult); | ||
}); | ||
@@ -147,19 +255,24 @@ | ||
value: function _addRuleSetToRuleSets(propName, accessor, rules) { | ||
var _this = this; | ||
this.ruleSets.push(function (data, vState) { | ||
var existing = vState.getIn([propName, SELF]) || (0, _immutable.Set)(); | ||
var ruleReducer = buildRuleReducer(accessor(data)); | ||
var messages = rules.reduce(ruleReducer, existing); | ||
return vState.set(propName, (0, _immutable.Map)(_defineProperty({}, SELF, messages))); | ||
}); | ||
this.validationState = this.validationState.setIn(['messages', propName], new _immutable.Set()); | ||
return this; | ||
} | ||
}, { | ||
key: '_addForEachValidatorToRuleSets', | ||
value: function _addForEachValidatorToRuleSets(propName, accessor, validator) { | ||
this.ruleSets.push(function (data, vState) { | ||
var iter = getIterable(propName, accessor, data); | ||
var validatorResultMap = (0, _immutable.Map)(); | ||
this.ruleSets.push(function (dataToValidate, validationMessages) { | ||
return rules.reduce(function (allMessages, rule) { | ||
var messages = allMessages.get(propName); | ||
for (var i = 0; i < (0, _util.iterableLength)(iter); i++) { | ||
var validatorResult = validator.validate((0, _util.get)(iter, i)); | ||
validatorResultMap = validatorResultMap.set(i, validatorResult); | ||
} | ||
var val = accessor(dataToValidate, propName); | ||
var _runRule2 = _this._runRule(rule, val); | ||
var isValid = _runRule2.isValid; | ||
var message = _runRule2.message; | ||
return isValid ? allMessages.set(propName, messages['delete'](message)) : allMessages.set(propName, messages.add(message)); | ||
}, validationMessages); | ||
return setMerged(vState, propName, validatorResultMap); | ||
}); | ||
@@ -170,26 +283,18 @@ | ||
}, { | ||
key: '_runRule', | ||
value: function _runRule(rule, val) { | ||
var result = rule(val); | ||
if (!hasProp.call(result, 'isValid') || !hasProp.call(result, 'message')) { | ||
throw new Error('Rules must return a result with the properties `isValid` and `message`. Use ImmutableValidation.rule()'); | ||
} | ||
return result; | ||
} | ||
}, { | ||
key: 'validate', | ||
value: function validate(dataToValidate) { | ||
var newMessages = this.ruleSets.reduce(function (messages, ruleSet) { | ||
return ruleSet(dataToValidate, messages); | ||
}, this.validationState.get('messages')); | ||
key: '_addForEachRuleSetToRuleSets', | ||
value: function _addForEachRuleSetToRuleSets(propName, accessor, rules) { | ||
this.ruleSets.push(function (data, vState) { | ||
var iter = getIterable(propName, accessor, data); | ||
var resultMap = (0, _immutable.Map)(); | ||
var isValid = newMessages.every(function (prop) { | ||
return !prop.count(); | ||
}); | ||
for (var i = 0; i < (0, _util.iterableLength)(iter); i++) { | ||
var ruleReducer = buildRuleReducer((0, _util.get)(iter, i)); | ||
var itemResult = rules.reduce(ruleReducer, (0, _immutable.Set)()); | ||
resultMap = resultMap.set(i, (0, _immutable.Map)(_defineProperty({}, SELF, itemResult))); | ||
} | ||
this.validationState = this.validationState.withMutations(function (vState) { | ||
return vState.set('isValid', isValid).set('messages', newMessages); | ||
return setMerged(vState, propName, resultMap); | ||
}); | ||
return this.validationState; | ||
return this; | ||
} | ||
@@ -199,4 +304,14 @@ }], [{ | ||
value: function isInstance(obj) { | ||
return (0, _util.isFunction)(obj.validate) && (0, _util.isFunction)(obj.ruleFor); | ||
if (!obj) return false; | ||
return (0, _util.isFunction)(obj.validate); | ||
} | ||
}, { | ||
key: 'which', | ||
value: function which(chooseValidator) { | ||
if (!(0, _util.isFunction)(chooseValidator)) { | ||
throw new TypeError('Call Validator.which with a function returning a validator or null'); | ||
} | ||
return new ValidatorWrapper(chooseValidator); | ||
} | ||
}]); | ||
@@ -230,3 +345,8 @@ | ||
exports.isString = isString; | ||
exports.isObject = isObject; | ||
exports.partial = partial; | ||
exports.isIterable = isIterable; | ||
exports.iterableLength = iterableLength; | ||
exports.unwrapImmutable = unwrapImmutable; | ||
exports.get = get; | ||
exports.asCallable = asCallable; | ||
@@ -258,2 +378,6 @@ | ||
function isObject(obj) { | ||
return toType(obj) === 'object'; | ||
} | ||
function partial(fn) { | ||
@@ -273,2 +397,25 @@ for (var _len = arguments.length, argsToApply = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
function isIterable(maybeIter) { | ||
if (!maybeIter || typeof maybeIter !== 'object') return false; | ||
return 'length' in maybeIter || 'count' in maybeIter && isFunction(maybeIter.count); | ||
} | ||
function iterableLength(iter) { | ||
if (!isIterable(iter)) return 0; | ||
if (isFunction(iter.count)) return iter.count(); | ||
return iter.length; | ||
} | ||
function unwrapImmutable(maybeImmutable) { | ||
if (!maybeImmutable) return maybeImmutable; | ||
if (isFunction(maybeImmutable.toJS)) return maybeImmutable.toJS(); | ||
return maybeImmutable; | ||
} | ||
function get(maybeImmutable, propOrIndex) { | ||
if (!maybeImmutable || typeof maybeImmutable !== 'object') return maybeImmutable; | ||
if (isFunction(maybeImmutable.get)) return maybeImmutable.get(propOrIndex); | ||
return maybeImmutable[propOrIndex]; | ||
} | ||
/** | ||
@@ -275,0 +422,0 @@ * Ensures that the given argument can be called as a function: |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("immutable")):"function"==typeof define&&define.amd?define(["immutable"],t):"object"==typeof exports?exports.ImmutableValidation=t(require("immutable")):e.ImmutableValidation=t(e.Immutable)}(this,function(e){return function(e){function t(r){if(n[r])return n[r].exports;var u=n[r]={exports:{},id:r,loaded:!1};return e[r].call(u.exports,u,u.exports,t),u.loaded=!0,u.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t["default"]=e,t}function u(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),a=u(i),o=n(4),s=r(o),l=n(5),c=u(l);t["default"]={Validator:a["default"],facts:s,rule:c["default"]},e.exports=t["default"]},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=n(2),a=n(3),o=Object.prototype.hasOwnProperty,s=function(){function e(){r(this,e),this.ruleSets=[],this.validationState=new i.Map({isValid:!0,messages:new i.Map})}return u(e,[{key:"ruleFor",value:function(t,n){for(var r=arguments.length,u=Array(r>2?r-2:0),i=2;r>i;i++)u[i-2]=arguments[i];return 1===u.length&&e.isInstance(u[0])?this._addValidatorToRuleSets(t,n,u[0]):this._addRuleSetToRuleSets(t,n,u)}},{key:"_addValidatorToRuleSets",value:function(e,t,n){return this.validationState=this.validationState.setIn(["messages",e],new i.Map),this.ruleSets.push(function(r,u){var i=t(r,e),a=n.validate(i);return u.set(e,a)}),this}},{key:"_addRuleSetToRuleSets",value:function(e,t,n){var r=this;return this.validationState=this.validationState.setIn(["messages",e],new i.Set),this.ruleSets.push(function(u,i){return n.reduce(function(n,i){var a=n.get(e),o=t(u,e),s=r._runRule(i,o),l=s.isValid,c=s.message;return l?n.set(e,a["delete"](c)):n.set(e,a.add(c))},i)}),this}},{key:"_runRule",value:function(e,t){var n=e(t);if(!o.call(n,"isValid")||!o.call(n,"message"))throw new Error("Rules must return a result with the properties `isValid` and `message`. Use ImmutableValidation.rule()");return n}},{key:"validate",value:function(e){var t=this.ruleSets.reduce(function(t,n){return n(e,t)},this.validationState.get("messages")),n=t.every(function(e){return!e.count()});return this.validationState=this.validationState.withMutations(function(e){return e.set("isValid",n).set("messages",t)}),this.validationState}}],[{key:"isInstance",value:function(e){return a.isFunction(e.validate)&&a.isFunction(e.ruleFor)}}]),e}();t["default"]=s,e.exports=t["default"]},function(t,n){t.exports=e},function(e,t){"use strict";function n(e){return e}function r(){return void 0}function u(e){return null===e?"null":void 0===e?"undefined":{}.toString.call(e).match(/\s([a-zA-Z]+)/)[1].toLowerCase()}function i(e){return"function"===u(e)}function a(e){return"string"===u(e)}function o(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];return function(){for(var t=arguments.length,r=Array(t),u=0;t>u;u++)r[u]=arguments[u];return e.apply(this,n.concat(r))}}function s(e,t){return i(e)?e:i(t)?o(t,e):"identity"===t?o(n,e):r}Object.defineProperty(t,"__esModule",{value:!0}),t.identity=n,t.noop=r,t.toType=u,t.isFunction=i,t.isString=a,t.partial=o,t.asCallable=s},function(e,t){"use strict";function n(e){return!!e}function r(e){return function(t){return t.length>=e}}function u(e){return function(t){return t.length<=e}}function i(e,t){return function(n){var r=parseInt(n,10);return r>=e&&t>=r}}Object.defineProperty(t,"__esModule",{value:!0}),t.required=n,t.minLength=r,t.maxLength=u,t.between=i},function(e,t,n){"use strict";function r(e,t){return e=u.asCallable(e,function(){return!0}),t=u.asCallable(t,function(e){return u.isString(e)?e:""}),function(n){var r=e(n),u=t(n);return{isValid:r,message:u}}}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=r;var u=n(3);e.exports=t["default"]}])}); | ||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("immutable")):"function"==typeof define&&define.amd?define(["immutable"],e):"object"==typeof exports?exports.ImmutableValidation=e(require("immutable")):t.ImmutableValidation=e(t.Immutable)}(this,function(t){return function(t){function e(r){if(n[r])return n[r].exports;var u=n[r]={exports:{},id:r,loaded:!1};return t[r].call(u.exports,u,u.exports,e),u.loaded=!0,u.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e["default"]=t,e}function u(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),a=u(i),o=n(4),l=r(o),s=n(5),c=u(s);e["default"]={Validator:a["default"],facts:l,rule:c["default"]},t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function u(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t){return function(e,n){var r=o(n,t),u=r.isValid,i=r.message;return u?e:e.add(i)}}function a(t,e){var n=c(e,2),r=n[1];return t===!1?!1:r.has("isValid")?r.get("isValid"):r.has(p)&&r.get(p).count()?!1:r.entrySeq().filter(function(t){var e=c(t,1),n=e[0];return"isValid"!==n&&n!==p}).reduce(a,t)}function o(t,e){var n=t(e);if(!v.call(n,"isValid")||!v.call(n,"message"))throw new Error("Rules must return a result with the properties `isValid` and `message`. Use ImmutableValidation.rule()");return n}function l(t,e,n){var r=e(n);if(!h.isIterable(r))throw new TypeError("`"+t+"` is a non-iterable value. ruleForEach applies a validation rule to each item in an iterable");return r}function s(t,e,n){return t.has(e)&&(n=t.get(e).merge(n)),t.set(e,d.Map(n))}Object.defineProperty(e,"__esModule",{value:!0});var c=function(){function t(t,e){var n=[],r=!0,u=!1,i=void 0;try{for(var a,o=t[Symbol.iterator]();!(r=(a=o.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(l){u=!0,i=l}finally{try{!r&&o["return"]&&o["return"]()}finally{if(u)throw i}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),f=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),d=n(2),h=n(3),v=Object.prototype.hasOwnProperty,p="self",y=function(){function t(e){u(this,t),this.getValidator=e}return f(t,[{key:"validate",value:function(t){var e=this.getValidator(t);return b.isInstance(e)?e.validate(t):d.Map({isValid:!0})}}]),t}(),b=function(){function t(){u(this,t),this.ruleSets=[],this.validationState=d.Map({isValid:!0})}return f(t,[{key:"ruleFor",value:function(e,n){for(var r=arguments.length,u=Array(r>2?r-2:0),i=2;r>i;i++)u[i-2]=arguments[i];return t.isInstance(u[0])?this._addValidatorToRuleSets(e,n,u[0]):this._addRuleSetToRuleSets(e,n,u)}},{key:"ruleForEach",value:function(e,n){for(var r=arguments.length,u=Array(r>2?r-2:0),i=2;r>i;i++)u[i-2]=arguments[i];return t.isInstance(u[0])?this._addForEachValidatorToRuleSets(e,n,u[0]):this._addForEachRuleSetToRuleSets(e,n,u)}},{key:"validate",value:function(t){var e=this.ruleSets.reduce(function(e,n){return n(t,e)},d.Map()),n=e.entrySeq().filter(function(t){var e=c(t,1),n=e[0];return"isValid"!==n}).reduce(a,!0);return this.validationState=e.set("isValid",n),this.validationState}},{key:"_addValidatorToRuleSets",value:function(t,e,n){return this.ruleSets.push(function(r,u){var i=e(r),a=n.validate(i);return s(u,t,a)}),this}},{key:"_addRuleSetToRuleSets",value:function(t,e,n){return this.ruleSets.push(function(u,a){var o=a.getIn([t,p])||d.Set(),l=i(e(u)),s=n.reduce(l,o);return a.set(t,d.Map(r({},p,s)))}),this}},{key:"_addForEachValidatorToRuleSets",value:function(t,e,n){return this.ruleSets.push(function(r,u){for(var i=l(t,e,r),a=d.Map(),o=0;o<h.iterableLength(i);o++){var c=n.validate(h.get(i,o));a=a.set(o,c)}return s(u,t,a)}),this}},{key:"_addForEachRuleSetToRuleSets",value:function(t,e,n){return this.ruleSets.push(function(u,a){for(var o=l(t,e,u),c=d.Map(),f=0;f<h.iterableLength(o);f++){var v=i(h.get(o,f)),y=n.reduce(v,d.Set());c=c.set(f,d.Map(r({},p,y)))}return s(a,t,c)}),this}}],[{key:"isInstance",value:function(t){return t?h.isFunction(t.validate):!1}},{key:"which",value:function(t){if(!h.isFunction(t))throw new TypeError("Call Validator.which with a function returning a validator or null");return new y(t)}}]),t}();e["default"]=b,t.exports=e["default"]},function(e,n){e.exports=t},function(t,e){"use strict";function n(t){return t}function r(){return void 0}function u(t){return null===t?"null":void 0===t?"undefined":{}.toString.call(t).match(/\s([a-zA-Z]+)/)[1].toLowerCase()}function i(t){return"function"===u(t)}function a(t){return"string"===u(t)}function o(t){return"object"===u(t)}function l(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;e>r;r++)n[r-1]=arguments[r];return function(){for(var e=arguments.length,r=Array(e),u=0;e>u;u++)r[u]=arguments[u];return t.apply(this,n.concat(r))}}function s(t){return t&&"object"==typeof t?"length"in t||"count"in t&&i(t.count):!1}function c(t){return s(t)?i(t.count)?t.count():t.length:0}function f(t){return t&&i(t.toJS)?t.toJS():t}function d(t,e){return t&&"object"==typeof t?i(t.get)?t.get(e):t[e]:t}function h(t,e){return i(t)?t:i(e)?l(e,t):"identity"===e?l(n,t):r}Object.defineProperty(e,"__esModule",{value:!0}),e.identity=n,e.noop=r,e.toType=u,e.isFunction=i,e.isString=a,e.isObject=o,e.partial=l,e.isIterable=s,e.iterableLength=c,e.unwrapImmutable=f,e.get=d,e.asCallable=h},function(t,e){"use strict";function n(t){return!!t}function r(t){return function(e){return e.length>=t}}function u(t){return function(e){return e.length<=t}}function i(t,e){return function(n){var r=parseInt(n,10);return r>=t&&e>=r}}Object.defineProperty(e,"__esModule",{value:!0}),e.required=n,e.minLength=r,e.maxLength=u,e.between=i},function(t,e,n){"use strict";function r(t,e){return t=u.asCallable(t,function(){return!0}),e=u.asCallable(e,function(t){return u.isString(t)?t:""}),function(n){var r=t(n),u=e(n);return{isValid:r,message:u}}}Object.defineProperty(e,"__esModule",{value:!0}),e["default"]=r;var u=n(3);t.exports=e["default"]}])}); |
{ | ||
"name": "immutable-validation", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"description": "React form validation using top-down, immutable data", | ||
@@ -8,3 +8,3 @@ "main": "dist", | ||
"test": "karma start", | ||
"test:ci": "karma start --no-single-run --browsers Chrome", | ||
"test:ci": "karma start --no-single-run --browsers Chrome --auto-watch", | ||
"build": "webpack src/validation.js dist/ImmutableValidation.min.js", | ||
@@ -11,0 +11,0 @@ "build:dev": "webpack src/validation.js dist/ImmutableValidation.js --config webpack.config.dev.js", |
@@ -25,2 +25,6 @@ export function identity (x) { | ||
export function isObject (obj) { | ||
return toType(obj) === 'object'; | ||
} | ||
export function partial (fn, ...argsToApply) { | ||
@@ -32,2 +36,25 @@ return function (...args) { | ||
export function isIterable (maybeIter) { | ||
if (!maybeIter || typeof maybeIter !== 'object') return false; | ||
return ('length' in maybeIter || ('count' in maybeIter && isFunction(maybeIter.count))); | ||
} | ||
export function iterableLength (iter) { | ||
if (!isIterable(iter)) return 0; | ||
if (isFunction(iter.count)) return iter.count(); | ||
return iter.length; | ||
} | ||
export function unwrapImmutable (maybeImmutable) { | ||
if (!maybeImmutable) return maybeImmutable; | ||
if (isFunction(maybeImmutable.toJS)) return maybeImmutable.toJS(); | ||
return maybeImmutable; | ||
} | ||
export function get (maybeImmutable, propOrIndex) { | ||
if (!maybeImmutable || typeof maybeImmutable !== 'object') return maybeImmutable; | ||
if (isFunction(maybeImmutable.get)) return maybeImmutable.get(propOrIndex); | ||
return maybeImmutable[propOrIndex]; | ||
} | ||
/** | ||
@@ -34,0 +61,0 @@ * Ensures that the given argument can be called as a function: |
import { Map, Set } from 'immutable'; | ||
import { isFunction } from './util'; | ||
import { isFunction, isIterable, iterableLength, get } from './util'; | ||
const hasProp = Object.prototype.hasOwnProperty; | ||
const SELF = 'self'; | ||
function buildRuleReducer (dataToValidate) { | ||
return (messagesForProp, rule) => { | ||
const { isValid, message } = runRule(rule, dataToValidate); | ||
if (!isValid) { | ||
return messagesForProp.add(message); | ||
} | ||
return messagesForProp; | ||
} | ||
} | ||
function resultReducer (isValid, [ , prop ]) { | ||
if (isValid === false) return false; | ||
if (prop.has('isValid')) return prop.get('isValid'); | ||
if (prop.has(SELF) && prop.get(SELF).count()) return false; | ||
return prop.entrySeq() | ||
.filter(([ key ]) => key !== 'isValid' && key !== SELF) | ||
.reduce(resultReducer, isValid); | ||
} | ||
function runRule (rule, val) { | ||
const result = rule(val); | ||
if (!hasProp.call(result, 'isValid') || !hasProp.call(result, 'message')) { | ||
throw new Error('Rules must return a result with the properties `isValid` and `message`. Use ImmutableValidation.rule()'); | ||
} | ||
return result; | ||
} | ||
function getIterable (propName, accessor, data) { | ||
const iter = accessor(data); | ||
if (!isIterable(iter)) { | ||
throw new TypeError(`\`${propName}\` is a non-iterable value. ruleForEach applies a validation rule to each item in an iterable`); | ||
} | ||
return iter; | ||
} | ||
function setMerged (vState, propName, newData) { | ||
if (vState.has(propName)) { | ||
newData = vState.get(propName).merge(newData); | ||
} | ||
return vState.set(propName, Map(newData)); | ||
} | ||
class ValidatorWrapper { | ||
constructor (chooseValidator) { | ||
this.getValidator = chooseValidator; | ||
} | ||
validate (dataToValidate) { | ||
const v = this.getValidator(dataToValidate); | ||
if (!Validator.isInstance(v)) { | ||
return Map({ isValid: true }); | ||
} | ||
return v.validate(dataToValidate); | ||
} | ||
} | ||
class Validator { | ||
constructor () { | ||
this.ruleSets = []; | ||
this.validationState = new Map({ | ||
isValid: true, | ||
messages: new Map() | ||
this.validationState = Map({ | ||
isValid: true | ||
}); | ||
@@ -17,3 +79,3 @@ } | ||
ruleFor (propName, accessor, ...rules) { | ||
return (rules.length === 1 && Validator.isInstance(rules[0])) | ||
return (Validator.isInstance(rules[0])) | ||
? this._addValidatorToRuleSets(propName, accessor, rules[0]) | ||
@@ -23,9 +85,26 @@ : this._addRuleSetToRuleSets(propName, accessor, rules); | ||
ruleForEach (propName, accessor, ...rules) { | ||
return (Validator.isInstance(rules[0])) | ||
? this._addForEachValidatorToRuleSets(propName, accessor, rules[0]) | ||
: this._addForEachRuleSetToRuleSets(propName, accessor, rules); | ||
} | ||
validate (dataToValidate) { | ||
const newVState = this.ruleSets.reduce((messages, ruleSet) => | ||
ruleSet(dataToValidate, messages), Map()); | ||
const isValid = newVState.entrySeq() | ||
.filter(([ key ]) => key !== 'isValid') | ||
.reduce(resultReducer, true); | ||
this.validationState = newVState.set('isValid', isValid); | ||
return this.validationState; | ||
} | ||
_addValidatorToRuleSets (propName, accessor, validator) { | ||
this.validationState = this.validationState.setIn(['messages', propName], new Map()); | ||
this.ruleSets.push((dataToValidate, validationMessages) => { | ||
const val = accessor(dataToValidate, propName); | ||
this.ruleSets.push((data, vState) => { | ||
const val = accessor(data); | ||
const validatorResult = validator.validate(val); | ||
return validationMessages.set(propName, validatorResult); | ||
return setMerged(vState, propName, validatorResult); | ||
}); | ||
@@ -37,16 +116,23 @@ | ||
_addRuleSetToRuleSets (propName, accessor, rules) { | ||
this.validationState = this.validationState.setIn(['messages', propName], new Set()); | ||
this.ruleSets.push((data, vState) => { | ||
const existing = vState.getIn([ propName, SELF ]) || Set(); | ||
const ruleReducer = buildRuleReducer(accessor(data)); | ||
const messages = rules.reduce(ruleReducer, existing); | ||
return vState.set(propName, Map({ [SELF]: messages })); | ||
}); | ||
this.ruleSets.push((dataToValidate, validationMessages) => { | ||
return rules.reduce((allMessages, rule) => { | ||
const messages = allMessages.get(propName); | ||
return this; | ||
} | ||
const val = accessor(dataToValidate, propName); | ||
const { isValid, message } = this._runRule(rule, val); | ||
_addForEachValidatorToRuleSets (propName, accessor, validator) { | ||
this.ruleSets.push((data, vState) => { | ||
const iter = getIterable(propName, accessor, data); | ||
let validatorResultMap = Map(); | ||
return (isValid) | ||
? allMessages.set(propName, messages.delete(message)) | ||
: allMessages.set(propName, messages.add(message)); | ||
for (let i = 0; i < iterableLength(iter); i++) { | ||
const validatorResult = validator.validate(get(iter, i)); | ||
validatorResultMap = validatorResultMap.set(i, validatorResult); | ||
} | ||
}, validationMessages); | ||
return setMerged(vState, propName, validatorResultMap); | ||
}); | ||
@@ -57,30 +143,33 @@ | ||
_runRule (rule, val) { | ||
const result = rule(val); | ||
if (!hasProp.call(result, 'isValid') || !hasProp.call(result, 'message')) { | ||
throw new Error('Rules must return a result with the properties `isValid` and `message`. Use ImmutableValidation.rule()'); | ||
} | ||
return result; | ||
} | ||
_addForEachRuleSetToRuleSets (propName, accessor, rules) { | ||
this.ruleSets.push((data, vState) => { | ||
const iter = getIterable(propName, accessor, data); | ||
let resultMap = Map(); | ||
validate (dataToValidate) { | ||
const newMessages = this.ruleSets.reduce((messages, ruleSet) => | ||
ruleSet(dataToValidate, messages), this.validationState.get('messages')); | ||
for (let i = 0; i < iterableLength(iter); i++) { | ||
const ruleReducer = buildRuleReducer(get(iter, i)); | ||
const itemResult = rules.reduce(ruleReducer, Set()); | ||
resultMap = resultMap.set(i, Map({ [SELF]: itemResult })); | ||
} | ||
const isValid = newMessages.every((prop) => !prop.count()); | ||
this.validationState = this.validationState.withMutations((vState) => { | ||
return vState | ||
.set('isValid', isValid) | ||
.set('messages', newMessages); | ||
return setMerged(vState, propName, resultMap); | ||
}); | ||
return this.validationState; | ||
return this; | ||
} | ||
static isInstance (obj) { | ||
return (isFunction(obj.validate) && isFunction(obj.ruleFor)); | ||
if (!obj) return false; | ||
return (isFunction(obj.validate)); | ||
} | ||
static which (chooseValidator) { | ||
if (!isFunction(chooseValidator)) { | ||
throw new TypeError(`Call Validator.which with a function returning a validator or null`); | ||
} | ||
return new ValidatorWrapper(chooseValidator); | ||
} | ||
} | ||
export default Validator; |
@@ -0,1 +1,2 @@ | ||
/*eslint-env mocha */ | ||
import { expect } from 'chai'; | ||
@@ -10,2 +11,8 @@ import Immutable from 'immutable'; | ||
lastName: 'Smith', | ||
foodAllergies: [ 'Tree Nuts', 'Raisins' ], | ||
contactMethods: [ | ||
{ type: 'phone', value: 210444779 }, | ||
{ type: 'fax', value: 2104447796 }, | ||
{ type: 'email', value: 'johnsmith_gmail.com' } | ||
], | ||
address: { | ||
@@ -20,8 +27,19 @@ zipCode: '7870' | ||
const zipCodeRequired = rule(required, 'Please enter a ZIP code'); | ||
const zipCodeIsValid = rule((val) => val.length === 5); | ||
const zipCodeIsValid = rule((val) => val.length === 5, 'Bad zip code'); | ||
const phoneNumberIsValid = rule((val) => ('' + val).length === 10, 'Bad phone number'); | ||
const emailIsValid = rule((val) => val.indexOf('@') > 0, 'Bad email address'); | ||
const isRealAllergen = rule( | ||
(val) => ['Tree Nuts', 'Gluten'].indexOf(val) > -1, | ||
(val) => `No one is allergic to ${val}` | ||
); | ||
const isUsableContactMethod = rule( | ||
(val) => ['phone', 'email'].indexOf(val) > -1, | ||
(val) => `We can't use ${val} to contact you` | ||
); | ||
describe('Validator', () => { | ||
describe('when validating', () => { | ||
it('returns a validationState object', () => { | ||
const personValidator = new Validator(); | ||
@@ -31,3 +49,3 @@ | ||
expect(validationState).to.contain.keys('isValid', 'messages'); | ||
expect(validationState).to.contain.keys('isValid'); | ||
}); | ||
@@ -45,3 +63,3 @@ | ||
it('returns a keyed collection of messages for each rule set', () => { | ||
it('returns a property for each rule set that contains results for that rule set', () => { | ||
const personValidator = new Validator() | ||
@@ -53,25 +71,25 @@ .ruleFor('firstNameStuff', (p) => p.get('firstName'), fNameRequired) | ||
expect(validationState.messages).to.contain.keys('firstNameStuff', 'lastNameStuff'); | ||
expect(validationState).to.contain.keys('firstNameStuff', 'lastNameStuff'); | ||
}); | ||
it('fails validation if any rule in a set fails', () => { | ||
it('returns lists of messages describing failed validation rules for each rule set', () => { | ||
const personValidator = new Validator() | ||
.ruleFor('lastName', (p) => p.get('lastName'), lNameRequired, lNameStartsWithP); | ||
.ruleFor('lastName', (p) => p.get('lastName'), lNameStartsWithP); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.isValid).to.be.false; | ||
expect(validationState.lastName.self.length).to.equal(1); | ||
expect(validationState.lastName.self[0]).to.equal('Your last name must start with a P'); | ||
}); | ||
it('contains a message describing failed validation rules', () => { | ||
it('fails validation if any rule in a single set fails', () => { | ||
const personValidator = new Validator() | ||
.ruleFor('lastName', (p) => p.get('lastName'), lNameStartsWithP); | ||
.ruleFor('lastName', (p) => p.get('lastName'), lNameRequired, lNameStartsWithP); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.messages.lastName.length).to.equal(1); | ||
expect(validationState.messages.lastName[0]).to.equal('Your last name must start with a P'); | ||
expect(validationState.isValid).to.be.false; | ||
}); | ||
it('fails validation if any rule set contains a failing rule', () => { | ||
it('fails validation if any rule set contains failing rules', () => { | ||
const personValidator = new Validator() | ||
@@ -86,2 +104,15 @@ .ruleFor('firstName', (p) => p.get('firstName'), fNameRequired) | ||
it('can run multiple rule sets for the same property name', () => { | ||
const failingRule1 = rule(() => false, 'Failing rule 1'); | ||
const failingRule2 = rule(() => false, 'Failing rule 2'); | ||
const personValidator = new Validator() | ||
.ruleFor('firstName', (p) => p.get('firstName'), failingRule1) | ||
.ruleFor('firstName', (p) => p.get('firstName'), failingRule2); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.firstName.self.length).to.equal(2); | ||
}); | ||
it('throws when running a rule that does not return a well-formed response', () => { | ||
@@ -94,25 +125,108 @@ const personValidator = new Validator() | ||
it('returns a keyed validationState from nested validators', () => { | ||
const personValidator = new Validator() | ||
.ruleFor('firstName', (p) => p.get('firstName'), fNameRequired) | ||
.ruleFor('address', (p) => p.get('address'), new Validator() | ||
.ruleFor('zipCode', (a) => a.get('zipCode'), zipCodeRequired) | ||
); | ||
describe('a nested validator', () => { | ||
const validationState = personValidator.validate(personData).toJS(); | ||
it('returns a nested validationState', () => { | ||
const personValidator = new Validator() | ||
.ruleFor('firstName', (p) => p.get('firstName'), fNameRequired) | ||
.ruleFor('address', (p) => p.get('address'), new Validator() | ||
.ruleFor('zipCode', (a) => a.get('zipCode'), zipCodeRequired) | ||
); | ||
expect(validationState.messages.address).to.contain.keys('isValid', 'messages'); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.address).to.contain.keys('isValid', 'zipCode'); | ||
}); | ||
it('fails validation if the nested validator fails', () => { | ||
const personValidator = new Validator() | ||
.ruleFor('firstName', (p) => p.get('firstName'), fNameRequired) | ||
.ruleFor('address', (p) => p.get('address'), new Validator() | ||
.ruleFor('zipCode', (a) => a.get('zipCode'), zipCodeIsValid) | ||
); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.isValid).to.be.false; | ||
}); | ||
}); | ||
it('fails validation if a nested validator fails validation', () => { | ||
const personValidator = new Validator() | ||
.ruleFor('firstName', (p) => p.get('firstName'), fNameRequired) | ||
.ruleFor('address', (p) => p.get('address'), new Validator() | ||
.ruleFor('zipCode', (a) => a.get('zipCode'), zipCodeIsValid) | ||
); | ||
describe('using ruleForEach', () => { | ||
const validationState = personValidator.validate(personData).toJS(); | ||
it('runs the given rule set for every value in an iterable', () => { | ||
const personValidator = new Validator() | ||
.ruleForEach('foodAllergies', (p) => p.get('foodAllergies'), isRealAllergen); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.isValid).to.be.false; | ||
expect(validationState.isValid).to.be.false; | ||
expect(validationState.foodAllergies[1].self[0]).to.equal('No one is allergic to Raisins'); | ||
}); | ||
it('applies nested validators to every value in an iterable', () => { | ||
const personValidator = new Validator() | ||
.ruleForEach('contactMethods', (p) => p.get('contactMethods'), new Validator() | ||
.ruleFor('type', (c) => c.get('type'), isUsableContactMethod) | ||
); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.isValid).to.be.false; | ||
expect(validationState.contactMethods[1].type.self[0]).to.equal('We can\'t use fax to contact you'); | ||
}); | ||
it('throws if the value returned by the accessor function isn\'t iterable', () => { | ||
const personValidator = new Validator() | ||
.ruleForEach('lastName', (p) => p.get('lastName'), isRealAllergen); | ||
expect(() => personValidator.validate(personData)).to.throw(/is a non-iterable value/); | ||
}); | ||
}); | ||
describe('using Validator.which', () => { | ||
it('chooses a validator to use at runtime', () => { | ||
const contactMethodValidators = { | ||
email: new Validator() | ||
.ruleFor('value', (c) => c.get('value'), emailIsValid), | ||
phone: new Validator() | ||
.ruleFor('value', (c) => c.get('value'), phoneNumberIsValid) | ||
}; | ||
const personValidator = new Validator() | ||
.ruleForEach( | ||
'contactMethods', | ||
(p) => p.get('contactMethods'), | ||
Validator.which((cm) => contactMethodValidators[cm.get('type')]) | ||
); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.contactMethods[0].value.self[0]).to.equal('Bad phone number'); | ||
expect(validationState.contactMethods[2].value.self[0]).to.equal('Bad email address'); | ||
}); | ||
it('does not perform validation if no validator matches', () => { | ||
const personValidator = new Validator() | ||
.ruleForEach( | ||
'contactMethods', | ||
(p) => p.get('contactMethods'), | ||
Validator.which(() => null) | ||
); | ||
const validationState = personValidator.validate(personData).toJS(); | ||
expect(validationState.contactMethods[0].isValid).to.be.true; | ||
expect(validationState.contactMethods[1].isValid).to.be.true; | ||
expect(validationState.contactMethods[2].isValid).to.be.true; | ||
}); | ||
it('throws if not passed a function as its parameter', () => { | ||
expect(() => Validator.which()).to.throw(/a function returning a validator or null/); | ||
}); | ||
}); | ||
}); | ||
@@ -127,3 +241,3 @@ | ||
it('returns false if the checked object does not implement `validate` or `ruleFor`', () => { | ||
it('returns false if the checked object does not implement `validate`', () => { | ||
const noValidateMethod = { | ||
@@ -135,12 +249,5 @@ ruleFor () { | ||
const noRuleForMethod = { | ||
validate () { | ||
return undefined; | ||
} | ||
}; | ||
expect(Validator.isInstance(noValidateMethod)).to.be.false; | ||
expect(Validator.isInstance(noRuleForMethod)).to.be.false; | ||
}); | ||
}); | ||
}); |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
55430
45.18%19
5.56%1053
38.55%1
Infinity%