├── babel.config.js ├── .gitignore ├── .github └── workflows │ └── tests.yml ├── package.json ├── LICENSE.md ├── dist ├── iodine.min.js ├── iodine.min.esm.js ├── iodine.min.modern.js ├── iodine.min.umd.js ├── iodine.min.js.map ├── iodine.min.esm.js.map ├── iodine.min.umd.js.map └── iodine.min.modern.js.map ├── LEGACY.md ├── README.md ├── src └── iodine.js └── tests └── test.js /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports={presets:[["@babel/preset-env",{targets:{node:"current"}}]]}; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | composer.lock 3 | vendor 4 | .DS_Store 5 | coverage 6 | .phpunit.result.cache 7 | .idea 8 | .php_cs.cache 9 | node_modules 10 | package-lock.json 11 | yarn.lock 12 | *.swp 13 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | 13 | - name: Run Jest 14 | uses: stefanoeb/jest-action@1.0.2 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@caneara/iodine", 3 | "version": "8.5.0", 4 | "description": "A micro client-side validation library", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/caneara/iodine.git" 8 | }, 9 | "keywords": [ 10 | "iodine", 11 | "validation", 12 | "data", 13 | "micro" 14 | ], 15 | "scripts": { 16 | "build": "microbundle", 17 | "watch": "microbundle watch", 18 | "test": "jest" 19 | }, 20 | "main": "dist/iodine.min.js", 21 | "source": "src/iodine.js", 22 | "author": "Caneara", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "@babel/preset-env": "^7.16.4", 26 | "jest": "^27.4.3", 27 | "microbundle": "^0.14.2" 28 | } 29 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © Caneara and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /dist/iodine.min.js: -------------------------------------------------------------------------------- 1 | class e{constructor(){this.locale=void 0,this.messages={after:"The date must be after: '[PARAM]'",afterOrEqual:"The date must be after or equal to: '[PARAM]'",array:"[FIELD] must be an array",before:"The date must be before: '[PARAM]'",beforeOrEqual:"The date must be before or equal to: '[PARAM]'",boolean:"[FIELD] must be true or false",date:"[FIELD] must be a date",different:"[FIELD] must be different to '[PARAM]'",endsWith:"[FIELD] must end with '[PARAM]'",email:"[FIELD] must be a valid email address",falsy:"[FIELD] must be a falsy value (false, 'false', 0 or '0')",in:"[FIELD] must be one of the following options: [PARAM]",integer:"[FIELD] must be an integer",json:"[FIELD] must be a parsable JSON object string",max:"[FIELD] must be less than or equal to [PARAM]",min:"[FIELD] must be greater than or equal to [PARAM]",maxLength:"[FIELD] must not be greater than '[PARAM]' in character length",minLength:"[FIELD] must not be less than '[PARAM]' character length",notIn:"[FIELD] must not be one of the following options: [PARAM]",numeric:"[FIELD] must be numeric",optional:"[FIELD] is optional",regexMatch:"[FIELD] must satisify the regular expression: [PARAM]",required:"[FIELD] must be present",same:"[FIELD] must be '[PARAM]'",startsWith:"[FIELD] must start with '[PARAM]'",string:"[FIELD] must be a string",truthy:"[FIELD] must be a truthy value (true, 'true', 1 or '1')",url:"[FIELD] must be a valid url",uuid:"[FIELD] must be a valid UUID"}}_compare(e,t,r,s=!1){return!!this.assertDate(e)&&!(!this.assertDate(t)&&!this.assertInteger(t))&&(t="number"==typeof t?t:t.getTime(),"less"===r&&s?e.getTime()<=t:"less"!==r||s?"more"===r&&s?e.getTime()>=t:"more"!==r||s?void 0:e.getTime()>t:e.getTime()"optional"!==e).map(e=>"string"==typeof e?[e,this._title(e.split(":").shift()),e.split(":").slice(1).join(":")]:[`${e.rule}:${e.param}`,this._title(e.rule),e.param]):[]}_title(e){return`${e[0].toUpperCase()}${e.slice(1)}`}_validate(e,t,r=null){for(let s in t=this._prepare(e,t))if(!this[`assert${t[s][1]}`].apply(this,[e,t[s][2]]))return{valid:!1,rule:t[s][0],error:r?r[t[s][0]]:this._error(t[s][0])};return{valid:!0,rule:"",error:""}}assert(e,t,r=null){if(Array.isArray(t))return this._validate(e,t,r);let s=Object.keys(t),a={valid:!0,fields:{}};for(let i=0;i=t}assertMaxLength(e,t){return"string"==typeof e&&e.length<=t}assertMinLength(e,t){return"string"==typeof e&&e.length>=t}assertNotIn(e,t){return!this.assertIn(e,t)}assertNumeric(e){return!isNaN(parseFloat(e))&&isFinite(e)}assertOptional(e){return[null,void 0,""].includes(e)}assertRegexMatch(e,t){return new RegExp(t).test(String(e))}assertRequired(e){return!this.assertOptional(e)}assertSame(e,t){return e==t}assertStartsWith(e,t){return this.assertString(e)&&e.startsWith(t)}assertString(e){return"string"==typeof e}assertTruthy(e){return[1,"1",!0,"true"].includes(e)}assertUrl(e){return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$").test(String(e).toLowerCase())}assertUuid(e){return new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$").test(String(e).toLowerCase())}rule(t,r){e.prototype[`assert${this._title(t)}`]=r}setErrorMessages(e){this.messages=e}setErrorMessage(e,t){this.messages[e]=t}setLocale(e){this.locale=e}setDefaultFieldName(e){this.default_field_name=e}}"undefined"!=typeof window&&(window.Iodine=new e),module.exports=e; 2 | //# sourceMappingURL=iodine.min.js.map 3 | -------------------------------------------------------------------------------- /dist/iodine.min.esm.js: -------------------------------------------------------------------------------- 1 | class e{constructor(){this.locale=void 0,this.messages={after:"The date must be after: '[PARAM]'",afterOrEqual:"The date must be after or equal to: '[PARAM]'",array:"[FIELD] must be an array",before:"The date must be before: '[PARAM]'",beforeOrEqual:"The date must be before or equal to: '[PARAM]'",boolean:"[FIELD] must be true or false",date:"[FIELD] must be a date",different:"[FIELD] must be different to '[PARAM]'",endsWith:"[FIELD] must end with '[PARAM]'",email:"[FIELD] must be a valid email address",falsy:"[FIELD] must be a falsy value (false, 'false', 0 or '0')",in:"[FIELD] must be one of the following options: [PARAM]",integer:"[FIELD] must be an integer",json:"[FIELD] must be a parsable JSON object string",max:"[FIELD] must be less than or equal to [PARAM]",min:"[FIELD] must be greater than or equal to [PARAM]",maxLength:"[FIELD] must not be greater than '[PARAM]' in character length",minLength:"[FIELD] must not be less than '[PARAM]' character length",notIn:"[FIELD] must not be one of the following options: [PARAM]",numeric:"[FIELD] must be numeric",optional:"[FIELD] is optional",regexMatch:"[FIELD] must satisify the regular expression: [PARAM]",required:"[FIELD] must be present",same:"[FIELD] must be '[PARAM]'",startsWith:"[FIELD] must start with '[PARAM]'",string:"[FIELD] must be a string",truthy:"[FIELD] must be a truthy value (true, 'true', 1 or '1')",url:"[FIELD] must be a valid url",uuid:"[FIELD] must be a valid UUID"}}_compare(e,t,r,s=!1){return!!this.assertDate(e)&&!(!this.assertDate(t)&&!this.assertInteger(t))&&(t="number"==typeof t?t:t.getTime(),"less"===r&&s?e.getTime()<=t:"less"!==r||s?"more"===r&&s?e.getTime()>=t:"more"!==r||s?void 0:e.getTime()>t:e.getTime()"optional"!==e).map(e=>"string"==typeof e?[e,this._title(e.split(":").shift()),e.split(":").slice(1).join(":")]:[`${e.rule}:${e.param}`,this._title(e.rule),e.param]):[]}_title(e){return`${e[0].toUpperCase()}${e.slice(1)}`}_validate(e,t,r=null){for(let s in t=this._prepare(e,t))if(!this[`assert${t[s][1]}`].apply(this,[e,t[s][2]]))return{valid:!1,rule:t[s][0],error:r?r[t[s][0]]:this._error(t[s][0])};return{valid:!0,rule:"",error:""}}assert(e,t,r=null){if(Array.isArray(t))return this._validate(e,t,r);let s=Object.keys(t),a={valid:!0,fields:{}};for(let i=0;i=t}assertMaxLength(e,t){return"string"==typeof e&&e.length<=t}assertMinLength(e,t){return"string"==typeof e&&e.length>=t}assertNotIn(e,t){return!this.assertIn(e,t)}assertNumeric(e){return!isNaN(parseFloat(e))&&isFinite(e)}assertOptional(e){return[null,void 0,""].includes(e)}assertRegexMatch(e,t){return new RegExp(t).test(String(e))}assertRequired(e){return!this.assertOptional(e)}assertSame(e,t){return e==t}assertStartsWith(e,t){return this.assertString(e)&&e.startsWith(t)}assertString(e){return"string"==typeof e}assertTruthy(e){return[1,"1",!0,"true"].includes(e)}assertUrl(e){return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$").test(String(e).toLowerCase())}assertUuid(e){return new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$").test(String(e).toLowerCase())}rule(t,r){e.prototype[`assert${this._title(t)}`]=r}setErrorMessages(e){this.messages=e}setErrorMessage(e,t){this.messages[e]=t}setLocale(e){this.locale=e}setDefaultFieldName(e){this.default_field_name=e}}"undefined"!=typeof window&&(window.Iodine=new e);export{e as default}; 2 | //# sourceMappingURL=iodine.min.esm.js.map 3 | -------------------------------------------------------------------------------- /dist/iodine.min.modern.js: -------------------------------------------------------------------------------- 1 | class e{constructor(){this.locale=void 0,this.messages={after:"The date must be after: '[PARAM]'",afterOrEqual:"The date must be after or equal to: '[PARAM]'",array:"[FIELD] must be an array",before:"The date must be before: '[PARAM]'",beforeOrEqual:"The date must be before or equal to: '[PARAM]'",boolean:"[FIELD] must be true or false",date:"[FIELD] must be a date",different:"[FIELD] must be different to '[PARAM]'",endsWith:"[FIELD] must end with '[PARAM]'",email:"[FIELD] must be a valid email address",falsy:"[FIELD] must be a falsy value (false, 'false', 0 or '0')",in:"[FIELD] must be one of the following options: [PARAM]",integer:"[FIELD] must be an integer",json:"[FIELD] must be a parsable JSON object string",max:"[FIELD] must be less than or equal to [PARAM]",min:"[FIELD] must be greater than or equal to [PARAM]",maxLength:"[FIELD] must not be greater than '[PARAM]' in character length",minLength:"[FIELD] must not be less than '[PARAM]' character length",notIn:"[FIELD] must not be one of the following options: [PARAM]",numeric:"[FIELD] must be numeric",optional:"[FIELD] is optional",regexMatch:"[FIELD] must satisify the regular expression: [PARAM]",required:"[FIELD] must be present",same:"[FIELD] must be '[PARAM]'",startsWith:"[FIELD] must start with '[PARAM]'",string:"[FIELD] must be a string",truthy:"[FIELD] must be a truthy value (true, 'true', 1 or '1')",url:"[FIELD] must be a valid url",uuid:"[FIELD] must be a valid UUID"}}_compare(e,t,r,s=!1){return!!this.assertDate(e)&&!(!this.assertDate(t)&&!this.assertInteger(t))&&(t="number"==typeof t?t:t.getTime(),"less"===r&&s?e.getTime()<=t:"less"!==r||s?"more"===r&&s?e.getTime()>=t:"more"!==r||s?void 0:e.getTime()>t:e.getTime()"optional"!==e).map(e=>"string"==typeof e?[e,this._title(e.split(":").shift()),e.split(":").slice(1).join(":")]:[`${e.rule}:${e.param}`,this._title(e.rule),e.param]):[]}_title(e){return`${e[0].toUpperCase()}${e.slice(1)}`}_validate(e,t,r=null){for(let s in t=this._prepare(e,t))if(!this[`assert${t[s][1]}`].apply(this,[e,t[s][2]]))return{valid:!1,rule:t[s][0],error:r?r[t[s][0]]:this._error(t[s][0])};return{valid:!0,rule:"",error:""}}assert(e,t,r=null){if(Array.isArray(t))return this._validate(e,t,r);let s=Object.keys(t),a={valid:!0,fields:{}};for(let i=0;i=t}assertMaxLength(e,t){return"string"==typeof e&&e.length<=t}assertMinLength(e,t){return"string"==typeof e&&e.length>=t}assertNotIn(e,t){return!this.assertIn(e,t)}assertNumeric(e){return!isNaN(parseFloat(e))&&isFinite(e)}assertOptional(e){return[null,void 0,""].includes(e)}assertRegexMatch(e,t){return new RegExp(t).test(String(e))}assertRequired(e){return!this.assertOptional(e)}assertSame(e,t){return e==t}assertStartsWith(e,t){return this.assertString(e)&&e.startsWith(t)}assertString(e){return"string"==typeof e}assertTruthy(e){return[1,"1",!0,"true"].includes(e)}assertUrl(e){return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$").test(String(e).toLowerCase())}assertUuid(e){return new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$").test(String(e).toLowerCase())}rule(t,r){e.prototype[`assert${this._title(t)}`]=r}setErrorMessages(e){this.messages=e}setErrorMessage(e,t){this.messages[e]=t}setLocale(e){this.locale=e}setDefaultFieldName(e){this.default_field_name=e}}"undefined"!=typeof window&&(window.Iodine=new e);export{e as default}; 2 | //# sourceMappingURL=iodine.min.modern.js.map 3 | -------------------------------------------------------------------------------- /dist/iodine.min.umd.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e||self).iodine=t()}(this,function(){class e{constructor(){this.locale=void 0,this.messages={after:"The date must be after: '[PARAM]'",afterOrEqual:"The date must be after or equal to: '[PARAM]'",array:"[FIELD] must be an array",before:"The date must be before: '[PARAM]'",beforeOrEqual:"The date must be before or equal to: '[PARAM]'",boolean:"[FIELD] must be true or false",date:"[FIELD] must be a date",different:"[FIELD] must be different to '[PARAM]'",endsWith:"[FIELD] must end with '[PARAM]'",email:"[FIELD] must be a valid email address",falsy:"[FIELD] must be a falsy value (false, 'false', 0 or '0')",in:"[FIELD] must be one of the following options: [PARAM]",integer:"[FIELD] must be an integer",json:"[FIELD] must be a parsable JSON object string",max:"[FIELD] must be less than or equal to [PARAM]",min:"[FIELD] must be greater than or equal to [PARAM]",maxLength:"[FIELD] must not be greater than '[PARAM]' in character length",minLength:"[FIELD] must not be less than '[PARAM]' character length",notIn:"[FIELD] must not be one of the following options: [PARAM]",numeric:"[FIELD] must be numeric",optional:"[FIELD] is optional",regexMatch:"[FIELD] must satisify the regular expression: [PARAM]",required:"[FIELD] must be present",same:"[FIELD] must be '[PARAM]'",startsWith:"[FIELD] must start with '[PARAM]'",string:"[FIELD] must be a string",truthy:"[FIELD] must be a truthy value (true, 'true', 1 or '1')",url:"[FIELD] must be a valid url",uuid:"[FIELD] must be a valid UUID"}}_compare(e,t,r,s=!1){return!!this.assertDate(e)&&!(!this.assertDate(t)&&!this.assertInteger(t))&&(t="number"==typeof t?t:t.getTime(),"less"===r&&s?e.getTime()<=t:"less"!==r||s?"more"===r&&s?e.getTime()>=t:"more"!==r||s?void 0:e.getTime()>t:e.getTime()"optional"!==e).map(e=>"string"==typeof e?[e,this._title(e.split(":").shift()),e.split(":").slice(1).join(":")]:[`${e.rule}:${e.param}`,this._title(e.rule),e.param]):[]}_title(e){return`${e[0].toUpperCase()}${e.slice(1)}`}_validate(e,t,r=null){for(let s in t=this._prepare(e,t))if(!this[`assert${t[s][1]}`].apply(this,[e,t[s][2]]))return{valid:!1,rule:t[s][0],error:r?r[t[s][0]]:this._error(t[s][0])};return{valid:!0,rule:"",error:""}}assert(e,t,r=null){if(Array.isArray(t))return this._validate(e,t,r);let s=Object.keys(t),a={valid:!0,fields:{}};for(let i=0;i=t}assertMaxLength(e,t){return"string"==typeof e&&e.length<=t}assertMinLength(e,t){return"string"==typeof e&&e.length>=t}assertNotIn(e,t){return!this.assertIn(e,t)}assertNumeric(e){return!isNaN(parseFloat(e))&&isFinite(e)}assertOptional(e){return[null,void 0,""].includes(e)}assertRegexMatch(e,t){return new RegExp(t).test(String(e))}assertRequired(e){return!this.assertOptional(e)}assertSame(e,t){return e==t}assertStartsWith(e,t){return this.assertString(e)&&e.startsWith(t)}assertString(e){return"string"==typeof e}assertTruthy(e){return[1,"1",!0,"true"].includes(e)}assertUrl(e){return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$").test(String(e).toLowerCase())}assertUuid(e){return new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$").test(String(e).toLowerCase())}rule(t,r){e.prototype[`assert${this._title(t)}`]=r}setErrorMessages(e){this.messages=e}setErrorMessage(e,t){this.messages[e]=t}setLocale(e){this.locale=e}setDefaultFieldName(e){this.default_field_name=e}}return"undefined"!=typeof window&&(window.Iodine=new e),e}); 2 | //# sourceMappingURL=iodine.min.umd.js.map 3 | -------------------------------------------------------------------------------- /LEGACY.md: -------------------------------------------------------------------------------- 1 | > WARNING: This readme is for an older version of Iodine. If you are using Iodine in a new project, then review the latest [readme](README.md) instead as it uses a newer API. 2 | 3 | > This readme refers to a now deprecated version of Iodine (and includes links that may no longer work). 4 | 5 | # Iodine 6 | 7 | Iodine.js is a micro client-side validation library. It has no dependencies and can be used in isolation or as part of a framework. Iodine also supports chainable rules, allowing you to verify that a piece of data satisfies multiple criteria. 8 | 9 | ## Installation 10 | 11 | The easiest way to pull Iodine into your project is via a CDN: 12 | 13 | ```html 14 | 15 | ``` 16 | 17 | You can also pull Iodine into your project via NPM: 18 | 19 | ```js 20 | npm i @kingshott/iodine 21 | ``` 22 | 23 | ## Usage 24 | 25 | Iodine is automatically added to the `window` namespace, making it available anywhere. This is the recommended way to use Iodine if your project does not involve compilation or imports. 26 | 27 | Alternatively, if you are comfortable using imports, or your project uses a build tool, then you can import Iodine like so: 28 | 29 | ```js 30 | import { Iodine } from '@kingshott/iodine'; 31 | 32 | const iodine = new Iodine(); 33 | ``` 34 | 35 | ## Single checks 36 | 37 | Iodine's rules are prefixed with the `is` keyword. So, to check if an item is an `integer`, you'd use the following code: 38 | 39 | ```js 40 | let item_1 = 7; 41 | let item_2 = 'string'; 42 | 43 | Iodine.isInteger(item_1); // true 44 | Iodine.isInteger(item_2); // false 45 | ``` 46 | 47 | Single checks return a `true` or `false` value, indicating whether the item passed validation. 48 | 49 | ## Multiple checks 50 | 51 | If you want to verify whether an item passes a set of rules, you can use the main `is` method. This method accepts two parameters. The first, is the item you want to check. The second, is an array of rules that should be run in sequence e.g. 52 | 53 | ```js 54 | let item_1 = 7; 55 | let item_2 = 'string'; 56 | 57 | Iodine.is(item_1, ['required', 'integer']); // true 58 | Iodine.is(item_2, ['required', 'integer']); // string - 'integer' 59 | ``` 60 | 61 | The `is` method will return `true` if the item passes every rule. 62 | 63 | If the item fails to validate, the first rule that it failed to satisfy will be returned e.g. `'integer'`. 64 | 65 | > Version 1 of Iodine only returned the rule name e.g. 'minimum'. Version 2+ returns the rule name and any supplied parameter e.g. 'minimum:7'. 66 | 67 | ### Strict checking 68 | 69 | If you want to know whether the value has passed the validation checks and don't care about which rule failed (if any), in other words you want the result purely as a `boolean`, then you can use the `isValid` helper method: 70 | 71 | ```js 72 | let item_1 = 7; 73 | let item_2 = 'string'; 74 | 75 | Iodine.isValid(item_1, ['required', 'integer']); // true 76 | Iodine.isValid(item_2, ['required', 'integer']); // false 77 | ``` 78 | 79 | ### Schema validation 80 | 81 | If you want to compare an object against a schema. In other words, you want to run a list of checks against a list of values, then you can use the `isValidSchema` helper method. **Note** : This method uses `isValid`under the hood, hence it returns a `boolean`. 82 | 83 | ```js 84 | Iodine.isValidSchema({ 85 | email : 'welcome@to.iodine', 86 | password : 'abcdefgh', 87 | fullname : 'John Doe', 88 | }, { 89 | email : ['required', 'email'], 90 | password : ['required', 'minLength:6'], 91 | fullname : ['required', 'minLength:3'], 92 | }); // true 93 | ``` 94 | 95 | ## Additional parameters 96 | 97 | Some rules require extra parameters e.g. 98 | 99 | ```js 100 | let item_1 = 7; 101 | let item_2 = 4; 102 | 103 | Iodine.isMin(item_1, 5); // true 104 | Iodine.isMin(item_2, 5); // false 105 | ``` 106 | 107 | For multiple checks, you can supply the parameters by appending them to the rule with a semicolon separator e.g. 108 | 109 | ```js 110 | let item_1 = 7; 111 | let item_2 = 4; 112 | 113 | Iodine.is(item_1, ['required', 'integer', 'min:5']); // true 114 | Iodine.is(item_2, ['required', 'integer', 'min:5']); // string - 'min:5' 115 | ``` 116 | 117 | ## Optional values 118 | 119 | When performing multiple checks, you may wish to allow for optional values. Iodine supports this with the `optional` rule: 120 | 121 | ```js 122 | let item_1 = 7; 123 | let item_2 = null; 124 | let item_3 = 'string'; 125 | 126 | Iodine.is(item_1, ['optional', 'integer']); // true 127 | Iodine.is(item_2, ['optional', 'integer']); // true 128 | Iodine.is(item_3, ['optional', 'integer']); // string - 'integer' 129 | ``` 130 | 131 | **IMPORTANT**: If you wish to allow for optional values, then you must supply `'optional'` as the first rule in the list. 132 | 133 | ## Asynchronous rules 134 | 135 | Iodine supports the use of asynchronous custom rules using `async / await`. 136 | 137 | To add an asynchronous rule, simply create a custom rule that returns a `Promise` e.g: 138 | 139 | ```js 140 | Iodine.addRule('timeoutEquals', (value, param) => new Promise(resolve => setTimeout(resolve(value == param), 10))); 141 | ``` 142 | 143 | You may then test a value against the rule by using the `await` keyword: 144 | 145 | ```js 146 | let result = await Iodine.isTimeoutEquals(1, 1); 147 | ``` 148 | 149 | You can also use multiple asynchronous rules when testing a value, or mix and match synchronous and asynchronous rules. One thing to keep in mind though, is that if any of the rules you want to use are asynchronous, then you must use the `asyncIs` or `asyncIsValid` methods e.g. 150 | 151 | ```js 152 | // Right 153 | await Iodine.asyncIs(1, ['required', 'timeoutEquals:1'])); 154 | await Iodine.asyncIsValid(1, ['required', 'integer', 'timeoutEquals:1'])); 155 | 156 | // Wrong 157 | await Iodine.Is(1, ['required', 'timeoutEquals:1'])); 158 | await Iodine.IsValid(1, ['required', 'integer', 'timeoutEquals:1'])); 159 | 160 | // Wrong 161 | Iodine.Is(1, ['required', 'timeoutEquals:1'])); 162 | Iodine.IsValid(1, ['required', 'integer', 'timeoutEquals:1'])); 163 | ``` 164 | 165 | > None of the standard rules included in the library are asynchronous, so unless you want to use Promise-based rules, then you can safely ignore this section on asynchronous rules. 166 | 167 | ## Error messages 168 | 169 | Iodine includes a default set of error messages for the English language. To retrieve an error message for a rule, use the `getErrorMessage` method: 170 | 171 | ```js 172 | Iodine.getErrorMessage('array'); // string 173 | ``` 174 | 175 | When dealing with rules that have parameters, the `getErrorMessage` method allows you to supply the rule either as a combined `string` or as two arguments (the rule and parameter) e.g. 176 | 177 | ```js 178 | Iodine.getErrorMessage('min:7'); // string 179 | Iodine.getErrorMessage('min', 7); // string 180 | ``` 181 | 182 | If you want the field name to appear within the error message, you can pass an object as the second parameter to the `getErrorMessage` method. 183 | 184 | ```js 185 | Iodine.getErrorMessage('min:7', { field: ''}); // string 186 | Iodine.getErrorMessage('min', { field: '', param: 7}); // string 187 | ``` 188 | 189 | ## Custom messages (localisation) 190 | 191 | You can easily replace the default error messages with your own via the `setErrorMessages` method. This method requires a single parameter, which is an `object` containing the messages. See the [_defaultMessages](src/iodine.js) method for an example of this. 192 | 193 | Iodine will automatically swap the `[FIELD]` and `[PARAM]` placeholders with the parameters supplied in the `getErrorMessage` method. As such, you should insert this placeholder at the appropriate position in your new error message e.g. 194 | 195 | ```js 196 | Iodine.setErrorMessages({ same: `Field must be '[PARAM]'` }); // English 197 | Iodine.setErrorMessages({ same: `Champ doit être '[PARAM]'` }); // French 198 | ``` 199 | 200 | If no field name is provided when calling `getErrorMessage`, by default it will be replaced with "Value". You can change this by calling `setDefaultFieldName` 201 | 202 | ```js 203 | Iodine.setDefaultFieldName('Valeur'); 204 | ``` 205 | 206 | You can also add or update a single error 207 | ```js 208 | Iodine.setErrorMessage("passwordConfirmation", "Does not match password"); 209 | ``` 210 | 211 | ## Available rules 212 | 213 | The following validation rules are available: 214 | 215 | | Rule | Description | 216 | | ----------------------------- | ------------------------------------------------------------------------------- | 217 | | isAfter(date/integer) | Verify that the item is a `Date` after a given `Date` or timestamp 218 | | isAfterOrEqual(date/integer) | Verify that the item is a `Date` after or equal to a given `Date` or timestamp 219 | | isArray | Verify that the item is an `array` 220 | | isBefore(date/integer) | Verify that the item is a `Date` before a given `Date` or timestamp 221 | | isBeforeOrEqual(date/integer) | Verify that the item is a `Date` before or equal to a given `Date` or timestamp 222 | | isBoolean | Verify that the item is either `true` or `false` 223 | | isDate | Verify that the item is a `Date` object 224 | | isDifferent(value) | Verify that the item is different to the supplied value (uses loose compare) 225 | | isEndingWith(value) | Verify that the item ends with the given value 226 | | isEmail | Verify that the item is a valid email address 227 | | isFalsy | Verify that the item is either `false`, `'false'`, `0` or `'0'` 228 | | isIn(array) | Verify that the item is within the given `array` 229 | | isInteger | Verify that the item is an `integer` 230 | | isJson | Verify that the item is a parsable JSON object `string` 231 | | isMaxLength(limit) | Verify that the item's character length does not exceed the given limit 232 | | isMinLength(limit) | Verify that the item's character length is not under the given limit 233 | | isMax(limit) | Verify that the item's numerical value does not exceed the given limit 234 | | isMin(limit) | Verify that the item's numerical value is not under the given limit 235 | | isNotIn(array) | Verify that the item is not within the given `array` 236 | | isNumeric | Verify that the item is `number` or a numeric `string` 237 | | isOptional | Allow for optional values (only for use with multiple checks) 238 | | isRegexMatch(exp) | Verify that the item satisfies the given regular expression 239 | | isRequired | Verify that the item is not `null`, `undefined` or an empty `string` 240 | | isSame(value) | Verify that the item is the same as the supplied value (uses loose compare) 241 | | isStartingWith(value) | Verify that the item starts with the given value 242 | | isString | Verify that the item is a `string` 243 | | isTruthy | Verify that the item is either `true`, `'true'`, `1` or `'1'` 244 | | isUrl | Verify that the item is a valid URL 245 | | isUuid | Verify that the item is a `UUID` 246 | 247 | Examine the tests for examples of how to use each rule. 248 | 249 | ## Deprecated Rules 250 | 251 | The following rules are deprecated and should not be used: 252 | 253 | | Rule | Description | Replacement | 254 | | ----------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------- | 255 | | isMaximum(limit) | Verify that the item does not exceed the given limit (number or char length) | isMax for numerical value. isMaxLength for character length 256 | | isMinimum(limit) | Verify that the item is not under the given limit (number or char length) | isMin for numerical value. isMinLength for character length 257 | 258 | 259 | ## Custom rules 260 | 261 | Iodine allows you to add your own custom validation rules through the `addRule` method. This method excepts two parameters. The first, is the name of the rule. The second, is the `closure` that Iodine should execute when calling the rule e.g. 262 | 263 | ```js 264 | Iodine.addRule('lowerCase', (value) => value === value.toLowerCase()); 265 | ``` 266 | 267 | **IMPORTANT**: Iodine will automatically make the first letter of the rule's name uppercase and prefix it with 'is'. You should therefore avoid adding the prefix yourself e.g. 268 | 269 | ```js 270 | Iodine.addRule('lowerCase'); // correct 271 | Iodine.addRule('isLowerCase'); // wrong 272 | ``` 273 | 274 | If your rule needs to accept a parameter, simply include it in your `closure` as the second argument e.g. 275 | 276 | ```js 277 | Iodine.addRule('equals', (value, param) => value == param); 278 | ``` 279 | 280 | You can also add error messages for your custom rules e.g. 281 | 282 | ```js 283 | Iodine.addRule('equals', (value, param) => value == param); 284 | Iodine.setErrorMessages({ equals : `[FIELD] must be equal to '[PARAM]'` }); 285 | ``` 286 | 287 | ## Contributing 288 | 289 | Thank you for considering a contribution to Iodine. You are welcome to submit a PR containing additional rules, however to be accepted, they must explain what they do, be useful to others, and include a suitable test to confirm they work correctly. 290 | 291 | After pulling the project, to install the dependencies: 292 | 293 | ```bash 294 | npm install 295 | ``` 296 | 297 | To run the tests 298 | ```bash 299 | npm run test 300 | ``` 301 | 302 | ## License 303 | 304 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 305 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iodine 2 | 3 | Iodine.js is a micro client-side validation library. It has no dependencies and can be used in isolation or as part of a framework. Iodine also supports chainable rules, allowing you to verify that a piece (or pieces) of data satisfy multiple criteria. 4 | 5 | ## Upgrading 6 | 7 | Version 8+ of Iodine involved a major rewrite with numerous breaking changes. It is therefore recommended that existing projects continue to use [version 7](LEGACY.md) (or lower), while version 8 (or higher) should be reserved for newer projects. 8 | 9 | ## Installation 10 | 11 | The easiest way to pull Iodine into your project is via a CDN (be sure to update the build number): 12 | 13 | ```html 14 | 15 | ``` 16 | 17 | You can also pull Iodine into your project via NPM: 18 | 19 | ```js 20 | npm i @caneara/iodine 21 | ``` 22 | 23 | ## Usage 24 | 25 | Iodine is automatically added to the `window` namespace, making it available anywhere. This is the recommended way to use Iodine if your project does not involve compilation or imports. Even if your project does involve compilation, it is often easier to just use the instance added to the `window` namespace. 26 | 27 | Alternatively, if you are comfortable using imports, or you want to create your own instance, then you can import Iodine like so: 28 | 29 | ```js 30 | import Iodine from '@caneara/iodine'; 31 | 32 | const instance = new Iodine(); 33 | ``` 34 | 35 | ## Basic Validation 36 | 37 | Iodine includes a bunch of validation rules that you can access via their associated methods. This makes it quick and easy to check if an item is, for example, an integer or a date. 38 | 39 | Iodine's rules are prefixed with `assert`. So, to check if an item is an `integer`, you'd use the following code: 40 | 41 | ```js 42 | let item_1 = 7; 43 | let item_2 = 'string'; 44 | 45 | Iodine.assertInteger(item_1); // true 46 | Iodine.assertInteger(item_2); // false 47 | ``` 48 | 49 | [See below](#available-rules) for a complete list of Iodine's included validation rules. 50 | 51 | ## Advanced Validation 52 | 53 | While checking if an item conforms to an individual validation rule can be useful, you'll often want to check if an item conforms to multiple rules. For example, an email address may be required, must be a string, and must satisfy an email address regular expression. 54 | 55 | To address these needs, Iodine offers 'single item checks' and 'multiple item checks'... 56 | 57 | ### Single Item Checks 58 | 59 | This approach is preferred if you have one item that you need to test against multiple rules (like the email address example described above). To perform a 'single item check', call the main `assert` method. The method takes two parameters. The first, is the item to check. The second, is an `array` of validation rules that should be run in sequence e.g. 60 | 61 | ```js 62 | let item_1 = 7; 63 | let item_2 = 'string'; 64 | 65 | Iodine.assert(item_1, ['required', 'integer']); 66 | Iodine.assert(item_2, ['required', 'integer']); 67 | ``` 68 | 69 | As you can see in the example, the validation rules are expressed using `strings`. To find the `string` representation for a validation rule, [review the existing list](#available-rules). 70 | 71 | Unlike individual assertions (which return a `boolean`), the `assert` method returns an `object` containing a report. When the item passes all of the rules, you'll get this: 72 | 73 | ```js 74 | { 75 | valid : true, 76 | rule : '', 77 | error : '', 78 | }; 79 | ``` 80 | 81 | If the item fails to validate, the report will contain the first rule that it failed to satisfy, along with the associated error message: 82 | 83 | ```js 84 | { 85 | valid : false, 86 | rule : 'integer', 87 | error : 'Value must be an integer', 88 | }; 89 | ``` 90 | 91 | ### Multiple Item Checks 92 | 93 | This approach is preferred when you need to check multiple items against a bunch of different validation rules e.g. when submitting a form containing several fields. 94 | 95 | As with 'single item checks', you should call the `assert` method, however for both parameters, you will need to supply an `object`. The first object should contain the items to be validated, while the second should consist of the rules for each item e.g. 96 | 97 | ```js 98 | 99 | const items = { 100 | name : 5, 101 | email : 'test@example.com', 102 | password : 'abcdefgh', 103 | }; 104 | 105 | const rules = { 106 | name : ['required', 'string'], 107 | email : ['required', 'email'], 108 | password : ['required'], 109 | }; 110 | 111 | Iodine.assert(items, rules); 112 | ``` 113 | 114 | Unlike 'single item checks', the report is slightly different. It contains a top level `valid` key that allows you to easily check if everything passed or something failed. It then contains a `fields` key, which contains sub-reports for each item. The sub-report is the same thing you'd get for a 'single item check'. Here's the report for the code example shown above: 115 | 116 | ```js 117 | { 118 | valid : false, 119 | fields : { 120 | name : { 121 | valid : false, 122 | rule : 'string', 123 | error : 'Value must be a string', 124 | }, 125 | email : { 126 | valid : true, 127 | rule : '', 128 | error : '', 129 | }, 130 | password : { 131 | valid : true, 132 | rule : '', 133 | error : '', 134 | } 135 | }, 136 | }; 137 | ``` 138 | 139 | ## Additional parameters 140 | 141 | Some rules require extra parameters e.g. 142 | 143 | ```js 144 | let item_1 = 7; 145 | let item_2 = 4; 146 | 147 | Iodine.assertMin(item_1, 5); // true 148 | Iodine.assertMin(item_2, 5); // false 149 | ``` 150 | 151 | For advanced validation, you can supply the parameters by appending them to the rule with a semicolon separator e.g. 152 | 153 | ```js 154 | let item_1 = 7; 155 | let item_2 = 4; 156 | 157 | Iodine.assert(item_1, ['required', 'integer', 'min:5']); 158 | Iodine.assert(item_2, ['required', 'integer', 'min:5']); 159 | ``` 160 | 161 | Or, if you prefer, you can supply the rule as an `object` instead of a `string` separated by a semicolon: 162 | 163 | ```js 164 | Iodine.assert(8, ['required', 'integer', { rule : 'min', param : 7 }, 'max:10']); 165 | ``` 166 | 167 | ## Optional values 168 | 169 | For advanced validation, you may wish to allow for optional values. Iodine supports this with the `optional` rule: 170 | 171 | ```js 172 | let item_1 = 7; 173 | let item_2 = null; 174 | let item_3 = 'string'; 175 | 176 | Iodine.assert(item_1, ['optional', 'integer']); 177 | Iodine.assert(item_2, ['optional', 'integer']); 178 | Iodine.assert(item_3, ['optional', 'integer']); 179 | ``` 180 | 181 | **IMPORTANT**: If you wish to allow for optional values, then it must be the first rule in the array. 182 | 183 | ## Custom Errors 184 | 185 | Iodine includes a default set of error messages for the English language. However, you can easily replace them via the `setErrorMessages` method. This method requires a single parameter, which is an `object` containing the messages. See Iodine's [constructor](src/iodine.js) for an example. 186 | 187 | Iodine will automatically replace the `[FIELD]` and `[PARAM]` placeholders when an error occurs. As such, you should insert these placeholders at the appropriate position in your new error message e.g. 188 | 189 | ```js 190 | Iodine.setErrorMessages({ same : `[FIELD] must be '[PARAM]'` }); // English 191 | Iodine.setErrorMessages({ same : `[FIELD] doit être '[PARAM]'` }); // French 192 | ``` 193 | 194 | ### Single Errors 195 | 196 | In many cases, you won't need to replace all of the error messages. You'll instead want to update one or add a new one. To do that, you should instead call `setErrorMessage` e.g. 197 | 198 | ```js 199 | Iodine.setErrorMessage('passwordConfirmation', 'Does not match password'); 200 | ``` 201 | 202 | ### Custom Errors by Field 203 | 204 | Sometimes, it may be necessary to define a specific error message for a field, or you need a label for a field that is different from the name of the variable used. 205 | 206 | To achieve this, pass an object to the `assert` method containing the rule as property and the custom error message as a value e.g. 207 | 208 | ```js 209 | Iodine.assert(value, ['required'], { 'required' : 'The "Label" must be present.' }); 210 | ``` 211 | 212 | You can also use the same approach for multiple fields e.g. 213 | 214 | ```js 215 | let items = { 216 | name : '', 217 | }; 218 | 219 | let rules = { 220 | name : ['required'] 221 | }; 222 | 223 | let errors = { 224 | name : { 225 | required : 'The "Label" must be present.' 226 | } 227 | }; 228 | 229 | Iodine.assert(items, rules, errors); 230 | ``` 231 | 232 | ### Default field name 233 | 234 | Since 'single item checks' don't support field names, Iodine uses the default instead (which is 'Value'). If 'Value' is not suitable, then you can call the `setDefaultFieldName` method and supply an alternative `string` value to use instead e.g. 235 | 236 | ```js 237 | Iodine.setDefaultFieldName('Valeur'); 238 | ``` 239 | 240 | Note that you must call `setDefaultFieldName` before calling `assert`. 241 | 242 | ## Available rules 243 | 244 | The following validation rules are available: 245 | 246 | | Rule | String Key | Description | 247 | | --------------------------------- | --------------- | ------------------------------------------------------------------------------- | 248 | | assertAfter(date/integer) | 'after' | Verify that the item is a `Date` after a given `Date` or timestamp 249 | | assertAfterOrEqual(date/integer) | 'afterOrEqual' | Verify that the item is a `Date` after or equal to a given `Date` or timestamp 250 | | assertArray | 'array' | Verify that the item is an `array` 251 | | assertBefore(date/integer) | 'before' | Verify that the item is a `Date` before a given `Date` or timestamp 252 | | assertBeforeOrEqual(date/integer) | 'beforeOrEqual' | Verify that the item is a `Date` before or equal to a given `Date` or timestamp 253 | | assertBoolean | 'boolean' | Verify that the item is either `true` or `false` 254 | | assertDate | 'date' | Verify that the item is a `Date` object 255 | | assertDifferent(value) | 'different' | Verify that the item is different to the supplied value (uses loose compare) 256 | | assertEnds(value) | 'ends' | Verify that the item ends with the given value 257 | | assertEmail | 'email' | Verify that the item is a valid email address 258 | | assertFalsy | 'falsy' | Verify that the item is either `false`, `'false'`, `0` or `'0'` 259 | | assertIn(array) | 'in' | Verify that the item is within the given `array` 260 | | assertInteger | 'integer' | Verify that the item is an `integer` 261 | | assertJson | 'json' | Verify that the item is a parsable JSON object `string` 262 | | assertMaxLength(limit) | 'maxLength' | Verify that the item's character length does not exceed the given limit 263 | | assertMinLength(limit) | 'minLength' | Verify that the item's character length is not under the given limit 264 | | assertMax(limit) | 'max' | Verify that the item's numerical value does not exceed the given limit 265 | | assertMin(limit) | 'min' | Verify that the item's numerical value is not under the given limit 266 | | assertNotIn(array) | 'notIn' | Verify that the item is not within the given `array` 267 | | assertNumeric | 'numeric' | Verify that the item is `number` or a numeric `string` 268 | | assertOptional | 'optional' | Allow for optional values (only for use with multiple checks) 269 | | assertRegexMatch(exp) | 'regexMatch' | Verify that the item satisfies the given regular expression 270 | | assertRequired | 'required' | Verify that the item is not `null`, `undefined` or an empty `string` 271 | | assertSame(value) | 'same' | Verify that the item is the same as the supplied value (uses loose compare) 272 | | assertStartsWith(value) | 'startsWith' | Verify that the item starts with the given value 273 | | assertString | 'string' | Verify that the item is a `string` 274 | | assertTruthy | 'truthy' | Verify that the item is either `true`, `'true'`, `1` or `'1'` 275 | | assertUrl | 'url' | Verify that the item is a valid URL 276 | | assertUuid | 'uuid' | Verify that the item is a `UUID` 277 | 278 | Examine the tests for examples of how to use each rule. 279 | 280 | ## Custom rules 281 | 282 | Iodine allows you to add your own custom validation rules through the `rule` method. This method accepts two parameters. The first, is the name of the rule. The second, is the `closure` that Iodine should execute when calling the rule e.g. 283 | 284 | ```js 285 | Iodine.rule('lowerCase', (value) => value === value.toLowerCase()); 286 | ``` 287 | 288 | **IMPORTANT**: Iodine will automatically make the first letter of the rule's name uppercase and prefix it with 'assert'. You should therefore avoid adding the prefix yourself e.g. 289 | 290 | ```js 291 | Iodine.rule('lowerCase'); // right 292 | Iodine.rule('assertLowerCase'); // wrong 293 | ``` 294 | 295 | If your rule needs to accept a parameter, simply include it in your `closure` as the second argument e.g. 296 | 297 | ```js 298 | Iodine.rule('equals', (value, param) => value == param); 299 | ``` 300 | 301 | You can also add error messages for your custom rules e.g. 302 | 303 | ```js 304 | Iodine.rule('equals', (value, param) => value == param); 305 | Iodine.setErrorMessage('equals', "[FIELD] must be equal to '[PARAM]'"); 306 | ``` 307 | 308 | ## Asynchronous rules 309 | 310 | Previous versions of Iodine supported asynchronous custom rules using `async / await`. This has since been removed to make the library easier to maintain. If you were using asynchronous rules, then the preferred strategy is to execute your asynchronous logic first, store the result, and then have Iodine validate it. 311 | 312 | ## Contributing 313 | 314 | Thank you for considering a contribution to Iodine. You are welcome to submit a PR containing additional rules, however to be accepted, they must explain what they do, be useful to others, and include a suitable test to confirm they work correctly. 315 | 316 | After pulling the project, to install the dependencies: 317 | 318 | ```bash 319 | npm install 320 | ``` 321 | 322 | To run the tests 323 | ```bash 324 | npm run test 325 | ``` 326 | 327 | ## License 328 | 329 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 330 | -------------------------------------------------------------------------------- /src/iodine.js: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Iodine - JavaScript Library 4 | |-------------------------------------------------------------------------- 5 | | 6 | | This library contains a collection of useful validation rules that can 7 | | be used to quickly verify whether items meet certain conditions. 8 | | 9 | */ 10 | export default class Iodine 11 | { 12 | /** 13 | * Constructor. 14 | * 15 | */ 16 | constructor() 17 | { 18 | this.locale = undefined; 19 | 20 | this.messages = { 21 | after : "The date must be after: '[PARAM]'", 22 | afterOrEqual : "The date must be after or equal to: '[PARAM]'", 23 | array : "[FIELD] must be an array", 24 | before : "The date must be before: '[PARAM]'", 25 | beforeOrEqual : "The date must be before or equal to: '[PARAM]'", 26 | boolean : "[FIELD] must be true or false", 27 | date : "[FIELD] must be a date", 28 | different : "[FIELD] must be different to '[PARAM]'", 29 | endsWith : "[FIELD] must end with '[PARAM]'", 30 | email : "[FIELD] must be a valid email address", 31 | falsy : "[FIELD] must be a falsy value (false, 'false', 0 or '0')", 32 | in : "[FIELD] must be one of the following options: [PARAM]", 33 | integer : "[FIELD] must be an integer", 34 | json : "[FIELD] must be a parsable JSON object string", 35 | max : "[FIELD] must be less than or equal to [PARAM]", 36 | min : "[FIELD] must be greater than or equal to [PARAM]", 37 | maxLength : "[FIELD] must not be greater than '[PARAM]' in character length", 38 | minLength : "[FIELD] must not be less than '[PARAM]' character length", 39 | notIn : "[FIELD] must not be one of the following options: [PARAM]", 40 | numeric : "[FIELD] must be numeric", 41 | optional : "[FIELD] is optional", 42 | regexMatch : "[FIELD] must satisify the regular expression: [PARAM]", 43 | required : "[FIELD] must be present", 44 | same : "[FIELD] must be '[PARAM]'", 45 | startsWith : "[FIELD] must start with '[PARAM]'", 46 | string : "[FIELD] must be a string", 47 | truthy : "[FIELD] must be a truthy value (true, 'true', 1 or '1')", 48 | url : "[FIELD] must be a valid url", 49 | uuid : "[FIELD] must be a valid UUID", 50 | }; 51 | } 52 | 53 | /** 54 | * @internal. 55 | * 56 | */ 57 | _compare(first, second, type, equals = false) 58 | { 59 | if (! this.assertDate(first)) return false; 60 | 61 | if (! this.assertDate(second) && ! this.assertInteger(second)) return false; 62 | 63 | second = typeof second === 'number' ? second : second.getTime(); 64 | 65 | if (type === 'less' && equals) return first.getTime() <= second; 66 | if (type === 'less' && ! equals) return first.getTime() < second; 67 | if (type === 'more' && equals) return first.getTime() >= second; 68 | if (type === 'more' && ! equals) return first.getTime() > second; 69 | } 70 | 71 | /** 72 | * @internal. 73 | * 74 | */ 75 | _error(rule, args = undefined) 76 | { 77 | let { param, field } = typeof args === 'object' ? args : { param : args, field : undefined }; 78 | 79 | const chunks = rule.split(':'); 80 | 81 | let key = chunks.shift(); 82 | 83 | param = param || chunks.join(':'); 84 | 85 | if (['after', 'afterOrEqual', 'before', 'beforeOrEqual'].includes(key)) { 86 | param = new Date(parseInt(param)).toLocaleTimeString(this.locale, { 87 | year : 'numeric', 88 | month : 'short', 89 | day : 'numeric', 90 | hour : '2-digit', 91 | minute : 'numeric', 92 | hour12 : false, 93 | }); 94 | } 95 | 96 | let message = [null, undefined, ''].includes(param) 97 | ? this.messages[key] 98 | : this.messages[key].replace('[PARAM]', param); 99 | 100 | return [null, undefined, ''].includes(field) 101 | ? message.replace('[FIELD]', this.default_field_name || 'Value') 102 | : message.replace('[FIELD]', field); 103 | } 104 | 105 | /** 106 | * @internal. 107 | * 108 | */ 109 | _missing() 110 | { 111 | return { 112 | valid : false, 113 | rule : 'None', 114 | error : 'Rules exist, but no value was provided to check', 115 | }; 116 | } 117 | 118 | /** 119 | * @internal. 120 | * 121 | */ 122 | _prepare(value, rules = []) 123 | { 124 | if (! rules.length) return []; 125 | 126 | if (rules[0] === 'optional' && this.assertOptional(value)) return []; 127 | 128 | return rules.filter(rule => rule !== 'optional').map(rule => 129 | typeof(rule) === 'string' 130 | ? [rule, this._title(rule.split(':').shift()), rule.split(':').slice(1).join(':')] 131 | : [`${ rule.rule }:${ rule.param }`, this._title(rule.rule), rule.param] 132 | ); 133 | } 134 | 135 | /** 136 | * @internal. 137 | * 138 | */ 139 | _title(value) 140 | { 141 | return `${value[0].toUpperCase()}${value.slice(1)}`; 142 | } 143 | 144 | /** 145 | * @internal. 146 | * 147 | */ 148 | _validate(value, rules, errors = null) 149 | { 150 | for (let index in rules = this._prepare(value, rules)) { 151 | if (! this[`assert${rules[index][1]}`].apply(this, [value, rules[index][2]])) { 152 | return { 153 | valid : false, 154 | rule : rules[index][0], 155 | error : errors 156 | ? errors[rules[index][0]] 157 | : this._error(rules[index][0]), 158 | }; 159 | } 160 | } 161 | 162 | return { 163 | valid : true, 164 | rule : '', 165 | error : '', 166 | }; 167 | } 168 | 169 | /** 170 | * Determine if the given content matches the given schema. 171 | * 172 | */ 173 | assert(values, schema, errors = null) 174 | { 175 | if (Array.isArray(schema)) { 176 | return this._validate(values, schema, errors); 177 | } 178 | 179 | let keys = Object.keys(schema); 180 | 181 | let result = { valid : true, fields : { } }; 182 | 183 | for (let i = 0; i < keys.length; i++) { 184 | result.fields[keys[i]] = values.hasOwnProperty(keys[i]) 185 | ? this._validate(values[keys[i]], schema[keys[i]], errors != null ? errors[keys[i]] : null) 186 | : this._missing(); 187 | 188 | if (! result.fields[keys[i]].valid) { 189 | result.valid = false; 190 | } 191 | } 192 | 193 | return result; 194 | } 195 | 196 | /** 197 | * Determine if the given date is after another given date. 198 | * 199 | */ 200 | assertAfter(value, after) 201 | { 202 | return this._compare(value, after, 'more', false); 203 | } 204 | 205 | /** 206 | * Determine if the given date is after or equal to another given date. 207 | * 208 | */ 209 | assertAfterOrEqual(value, after) 210 | { 211 | return this._compare(value, after, 'more', true); 212 | } 213 | 214 | /** 215 | * Determine if the given value is an array. 216 | * 217 | */ 218 | assertArray(value) 219 | { 220 | return Array.isArray(value); 221 | } 222 | 223 | /** 224 | * Determine if the given date is before another given date. 225 | * 226 | */ 227 | assertBefore(value, before) 228 | { 229 | return this._compare(value, before, 'less', false); 230 | } 231 | 232 | /** 233 | * Determine if the given date is before or equal to another given date. 234 | * 235 | */ 236 | assertBeforeOrEqual(value, before) 237 | { 238 | return this._compare(value, before, 'less', true); 239 | } 240 | 241 | /** 242 | * Determine if the given value is a boolean. 243 | * 244 | */ 245 | assertBoolean(value) 246 | { 247 | return [true, false].includes(value); 248 | } 249 | 250 | /** 251 | * Determine if the given value is a date object. 252 | * 253 | */ 254 | assertDate(value) 255 | { 256 | return (value && Object.prototype.toString.call(value) === '[object Date]' && ! isNaN(value)); 257 | } 258 | 259 | /** 260 | * Determine if the given value is different to another given value. 261 | * 262 | */ 263 | assertDifferent(value, different) 264 | { 265 | return value != different; 266 | } 267 | 268 | /** 269 | * Determine if the given value ends with another given value. 270 | * 271 | */ 272 | assertEndsWith(value, sub) 273 | { 274 | return this.assertString(value) && value.endsWith(sub); 275 | } 276 | 277 | /** 278 | * Determine if the given value is a valid email address. 279 | * 280 | */ 281 | assertEmail(value) 282 | { 283 | let regex = "^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"; 284 | 285 | return new RegExp(regex).test(String(value).toLowerCase()); 286 | } 287 | 288 | /** 289 | * Determine if the given value is falsy. 290 | * 291 | */ 292 | assertFalsy(value) 293 | { 294 | return [0, '0', false, 'false'].includes(value); 295 | } 296 | 297 | /** 298 | * Determine if the given value is within the given array of options. 299 | * 300 | */ 301 | assertIn(value, options) 302 | { 303 | return (typeof options === 'string' ? options.split(',') : options).includes(value); 304 | } 305 | 306 | /** 307 | * Determine if the given value is an integer. 308 | * 309 | */ 310 | assertInteger(value) 311 | { 312 | return Number.isInteger(value) && parseInt(value).toString() === value.toString(); 313 | } 314 | 315 | /** 316 | * Determine if the given value is a JSON string. 317 | * 318 | */ 319 | assertJson(value) 320 | { 321 | try { 322 | return typeof JSON.parse(value) === 'object'; 323 | } catch (e) { 324 | return false; 325 | } 326 | } 327 | 328 | /** 329 | * Determine if the given number is less than or equal to the maximum limit. 330 | * 331 | */ 332 | assertMax(value, limit) 333 | { 334 | return parseFloat(value) <= limit; 335 | } 336 | 337 | /** 338 | * Determine if the given number is greater than or equal to the minimum limit. 339 | * 340 | */ 341 | assertMin(value, limit) 342 | { 343 | return parseFloat(value) >= limit; 344 | } 345 | 346 | /** 347 | * Determine if the given value string length is less than or equal to the maximum limit. 348 | * 349 | */ 350 | assertMaxLength(value, limit) 351 | { 352 | return typeof value === 'string' ? value.length <= limit : false; 353 | } 354 | 355 | /** 356 | * Determine if the given value string length is greater than or equal to the minimum limit. 357 | * 358 | */ 359 | assertMinLength(value, limit) 360 | { 361 | return typeof value === 'string' ? value.length >= limit : false; 362 | } 363 | 364 | /** 365 | * Determine if the given value is not within the given array of options. 366 | * 367 | */ 368 | assertNotIn(value, options) 369 | { 370 | return ! this.assertIn(value, options); 371 | } 372 | 373 | /** 374 | * Determine if the given value is numeric (an integer or a float). 375 | * 376 | */ 377 | assertNumeric(value) 378 | { 379 | return ! isNaN(parseFloat(value)) && isFinite(value); 380 | } 381 | 382 | /** 383 | * Determine if the given value is optional. 384 | * 385 | */ 386 | assertOptional(value) 387 | { 388 | return [null, undefined, ''].includes(value); 389 | } 390 | 391 | /** 392 | * Determine if the given value satisifies the given regular expression. 393 | * 394 | */ 395 | assertRegexMatch(value, expression) 396 | { 397 | return new RegExp(expression).test(String(value)); 398 | } 399 | 400 | /** 401 | * Determine if the given value is present. 402 | * 403 | */ 404 | assertRequired(value) 405 | { 406 | return ! this.assertOptional(value); 407 | } 408 | 409 | /** 410 | * Determine if the given value is the same as another given value. 411 | * 412 | */ 413 | assertSame(value, same) 414 | { 415 | return value == same; 416 | } 417 | 418 | /** 419 | * Determine if the given value starts with another given value. 420 | * 421 | */ 422 | assertStartsWith(value, sub) 423 | { 424 | return this.assertString(value) && value.startsWith(sub); 425 | } 426 | 427 | /** 428 | * Determine if the given value is a string. 429 | * 430 | */ 431 | assertString(value) 432 | { 433 | return typeof value === 'string'; 434 | } 435 | 436 | /** 437 | * Determine if the given value is truthy. 438 | * 439 | */ 440 | assertTruthy(value) 441 | { 442 | return [1, '1', true, 'true'].includes(value); 443 | } 444 | 445 | /** 446 | * Determine if the given value is a valid URL. 447 | * 448 | */ 449 | assertUrl(value) 450 | { 451 | let regex = "^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$"; 452 | 453 | return new RegExp(regex).test(String(value).toLowerCase()); 454 | } 455 | 456 | /** 457 | * Determine if the given value is a valid UUID. 458 | * 459 | */ 460 | assertUuid(value) 461 | { 462 | let regex = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"; 463 | 464 | return new RegExp(regex).test(String(value).toLowerCase()); 465 | } 466 | 467 | /** 468 | * Attach a custom validation rule to the library. 469 | * 470 | */ 471 | rule(name, closure) 472 | { 473 | Iodine.prototype[`assert${this._title(name)}`] = closure; 474 | } 475 | 476 | /** 477 | * Replace the default error messages with a new set. 478 | * 479 | */ 480 | setErrorMessages(messages) 481 | { 482 | this.messages = messages; 483 | } 484 | 485 | /** 486 | * Add or replace an error message. 487 | * 488 | */ 489 | setErrorMessage(key, message) 490 | { 491 | this.messages[key] = message; 492 | } 493 | 494 | /** 495 | * Replace the default locale with a new value. 496 | * 497 | */ 498 | setLocale(locale) 499 | { 500 | this.locale = locale; 501 | } 502 | 503 | /** 504 | * Replace the default field name with a new value. 505 | * 506 | */ 507 | setDefaultFieldName(fieldName) 508 | { 509 | this.default_field_name = fieldName; 510 | } 511 | } 512 | 513 | /** 514 | * Create an instance of the library. 515 | * 516 | */ 517 | if (typeof window !== 'undefined') { 518 | window.Iodine = new Iodine(); 519 | } 520 | -------------------------------------------------------------------------------- /dist/iodine.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"iodine.min.js","sources":["../src/iodine.js"],"sourcesContent":["/*\n|--------------------------------------------------------------------------\n| Iodine - JavaScript Library\n|--------------------------------------------------------------------------\n|\n| This library contains a collection of useful validation rules that can\n| be used to quickly verify whether items meet certain conditions.\n|\n*/\nexport default class Iodine\n{\n /**\n * Constructor.\n *\n */\n constructor()\n {\n this.locale = undefined;\n\n this.messages = {\n after : \"The date must be after: '[PARAM]'\",\n afterOrEqual : \"The date must be after or equal to: '[PARAM]'\",\n array : \"[FIELD] must be an array\",\n before : \"The date must be before: '[PARAM]'\",\n beforeOrEqual : \"The date must be before or equal to: '[PARAM]'\",\n boolean : \"[FIELD] must be true or false\",\n date : \"[FIELD] must be a date\",\n different : \"[FIELD] must be different to '[PARAM]'\",\n endsWith : \"[FIELD] must end with '[PARAM]'\",\n email : \"[FIELD] must be a valid email address\",\n falsy : \"[FIELD] must be a falsy value (false, 'false', 0 or '0')\",\n in : \"[FIELD] must be one of the following options: [PARAM]\",\n integer : \"[FIELD] must be an integer\",\n json : \"[FIELD] must be a parsable JSON object string\",\n max : \"[FIELD] must be less than or equal to [PARAM]\",\n min : \"[FIELD] must be greater than or equal to [PARAM]\",\n maxLength : \"[FIELD] must not be greater than '[PARAM]' in character length\",\n minLength : \"[FIELD] must not be less than '[PARAM]' character length\",\n notIn : \"[FIELD] must not be one of the following options: [PARAM]\",\n numeric : \"[FIELD] must be numeric\",\n optional : \"[FIELD] is optional\",\n regexMatch : \"[FIELD] must satisify the regular expression: [PARAM]\",\n required : \"[FIELD] must be present\",\n same : \"[FIELD] must be '[PARAM]'\",\n startsWith : \"[FIELD] must start with '[PARAM]'\",\n string : \"[FIELD] must be a string\",\n truthy : \"[FIELD] must be a truthy value (true, 'true', 1 or '1')\",\n url : \"[FIELD] must be a valid url\",\n uuid : \"[FIELD] must be a valid UUID\",\n };\n }\n\n /**\n * @internal.\n *\n */\n _compare(first, second, type, equals = false)\n {\n if (! this.assertDate(first)) return false;\n\n if (! this.assertDate(second) && ! this.assertInteger(second)) return false;\n\n second = typeof second === 'number' ? second : second.getTime();\n\n if (type === 'less' && equals) return first.getTime() <= second;\n if (type === 'less' && ! equals) return first.getTime() < second;\n if (type === 'more' && equals) return first.getTime() >= second;\n if (type === 'more' && ! equals) return first.getTime() > second;\n }\n\n /**\n * @internal.\n *\n */\n _error(rule, args = undefined)\n {\n let { param, field } = typeof args === 'object' ? args : { param : args, field : undefined };\n\n const chunks = rule.split(':');\n\n let key = chunks.shift();\n\n param = param || chunks.join(':');\n\n if (['after', 'afterOrEqual', 'before', 'beforeOrEqual'].includes(key)) {\n param = new Date(parseInt(param)).toLocaleTimeString(this.locale, {\n year : 'numeric',\n month : 'short',\n day : 'numeric',\n hour : '2-digit',\n minute : 'numeric',\n hour12 : false,\n });\n }\n\n let message = [null, undefined, ''].includes(param)\n ? this.messages[key]\n : this.messages[key].replace('[PARAM]', param);\n\n return [null, undefined, ''].includes(field)\n ? message.replace('[FIELD]', this.default_field_name || 'Value')\n : message.replace('[FIELD]', field);\n }\n\n /**\n * @internal.\n *\n */\n _missing()\n {\n return {\n valid : false,\n rule : 'None',\n error : 'Rules exist, but no value was provided to check',\n };\n }\n\n /**\n * @internal.\n *\n */\n _prepare(value, rules = [])\n {\n if (! rules.length) return [];\n\n if (rules[0] === 'optional' && this.assertOptional(value)) return [];\n\n return rules.filter(rule => rule !== 'optional').map(rule =>\n typeof(rule) === 'string'\n ? [rule, this._title(rule.split(':').shift()), rule.split(':').slice(1).join(':')]\n : [`${ rule.rule }:${ rule.param }`, this._title(rule.rule), rule.param]\n );\n }\n\n /**\n * @internal.\n *\n */\n _title(value)\n {\n return `${value[0].toUpperCase()}${value.slice(1)}`;\n }\n\n /**\n * @internal.\n *\n */\n _validate(value, rules, errors = null)\n {\n for (let index in rules = this._prepare(value, rules)) {\n if (! this[`assert${rules[index][1]}`].apply(this, [value, rules[index][2]])) {\n return {\n valid : false,\n rule : rules[index][0],\n error : errors \n ? errors[rules[index][0]]\n : this._error(rules[index][0]),\n };\n }\n }\n\n return {\n valid : true,\n rule : '',\n error : '',\n };\n }\n\n /**\n * Determine if the given content matches the given schema.\n *\n */\n assert(values, schema, errors = null)\n {\n if (Array.isArray(schema)) {\n return this._validate(values, schema, errors);\n }\n\n let keys = Object.keys(schema);\n\n let result = { valid : true, fields : { } };\n\n for (let i = 0; i < keys.length; i++) {\n result.fields[keys[i]] = values.hasOwnProperty(keys[i])\n ? this._validate(values[keys[i]], schema[keys[i]], errors != null ? errors[keys[i]] : null)\n : this._missing();\n\n if (! result.fields[keys[i]].valid) {\n result.valid = false;\n }\n }\n\n return result;\n }\n\n /**\n * Determine if the given date is after another given date.\n *\n */\n assertAfter(value, after)\n {\n return this._compare(value, after, 'more', false);\n }\n\n /**\n * Determine if the given date is after or equal to another given date.\n *\n */\n assertAfterOrEqual(value, after)\n {\n return this._compare(value, after, 'more', true);\n }\n\n /**\n * Determine if the given value is an array.\n *\n */\n assertArray(value)\n {\n return Array.isArray(value);\n }\n\n /**\n * Determine if the given date is before another given date.\n *\n */\n assertBefore(value, before)\n {\n return this._compare(value, before, 'less', false);\n }\n\n /**\n * Determine if the given date is before or equal to another given date.\n *\n */\n assertBeforeOrEqual(value, before)\n {\n return this._compare(value, before, 'less', true);\n }\n\n /**\n * Determine if the given value is a boolean.\n *\n */\n assertBoolean(value)\n {\n return [true, false].includes(value);\n }\n\n /**\n * Determine if the given value is a date object.\n *\n */\n assertDate(value)\n {\n return (value && Object.prototype.toString.call(value) === '[object Date]' && ! isNaN(value));\n }\n\n /**\n * Determine if the given value is different to another given value.\n *\n */\n assertDifferent(value, different)\n {\n return value != different;\n }\n\n /**\n * Determine if the given value ends with another given value.\n *\n */\n assertEndsWith(value, sub)\n {\n return this.assertString(value) && value.endsWith(sub);\n }\n\n /**\n * Determine if the given value is a valid email address.\n *\n */\n assertEmail(value)\n {\n let regex = \"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is falsy.\n *\n */\n assertFalsy(value)\n {\n return [0, '0', false, 'false'].includes(value);\n }\n\n /**\n * Determine if the given value is within the given array of options.\n *\n */\n assertIn(value, options)\n {\n return (typeof options === 'string' ? options.split(',') : options).includes(value);\n }\n\n /**\n * Determine if the given value is an integer.\n *\n */\n assertInteger(value)\n {\n return Number.isInteger(value) && parseInt(value).toString() === value.toString();\n }\n\n /**\n * Determine if the given value is a JSON string.\n *\n */\n assertJson(value)\n {\n try {\n return typeof JSON.parse(value) === 'object';\n } catch (e) {\n return false;\n }\n }\n\n /**\n * Determine if the given number is less than or equal to the maximum limit.\n *\n */\n assertMax(value, limit)\n {\n return parseFloat(value) <= limit;\n }\n\n /**\n * Determine if the given number is greater than or equal to the minimum limit.\n *\n */\n assertMin(value, limit)\n {\n return parseFloat(value) >= limit;\n }\n\n /**\n * Determine if the given value string length is less than or equal to the maximum limit.\n *\n */\n assertMaxLength(value, limit)\n {\n return typeof value === 'string' ? value.length <= limit : false;\n }\n\n /**\n * Determine if the given value string length is greater than or equal to the minimum limit.\n *\n */\n assertMinLength(value, limit)\n {\n return typeof value === 'string' ? value.length >= limit : false;\n }\n\n /**\n * Determine if the given value is not within the given array of options.\n *\n */\n assertNotIn(value, options)\n {\n return ! this.assertIn(value, options);\n }\n\n /**\n * Determine if the given value is numeric (an integer or a float).\n *\n */\n assertNumeric(value)\n {\n return ! isNaN(parseFloat(value)) && isFinite(value);\n }\n\n /**\n * Determine if the given value is optional.\n *\n */\n assertOptional(value)\n {\n return [null, undefined, ''].includes(value);\n }\n\n /**\n * Determine if the given value satisifies the given regular expression.\n *\n */\n assertRegexMatch(value, expression)\n {\n return new RegExp(expression).test(String(value));\n }\n\n /**\n * Determine if the given value is present.\n *\n */\n assertRequired(value)\n {\n return ! this.assertOptional(value);\n }\n\n /**\n * Determine if the given value is the same as another given value.\n *\n */\n assertSame(value, same)\n {\n return value == same;\n }\n\n /**\n * Determine if the given value starts with another given value.\n *\n */\n assertStartsWith(value, sub)\n {\n return this.assertString(value) && value.startsWith(sub);\n }\n\n /**\n * Determine if the given value is a string.\n *\n */\n assertString(value)\n {\n return typeof value === 'string';\n }\n\n /**\n * Determine if the given value is truthy.\n *\n */\n assertTruthy(value)\n {\n return [1, '1', true, 'true'].includes(value);\n }\n\n /**\n * Determine if the given value is a valid URL.\n *\n */\n assertUrl(value)\n {\n let regex = \"^(https?:\\\\/\\\\/)?((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|((\\\\d{1,3}\\\\.){3}\\\\d{1,3}))(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*(\\\\?[;&a-z\\\\d%_.~+=-]*)?(\\\\#[-a-z\\\\d_]*)?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is a valid UUID.\n *\n */\n assertUuid(value)\n {\n let regex = \"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Attach a custom validation rule to the library.\n *\n */\n rule(name, closure)\n {\n Iodine.prototype[`assert${this._title(name)}`] = closure;\n }\n\n /**\n * Replace the default error messages with a new set.\n *\n */\n setErrorMessages(messages)\n {\n this.messages = messages;\n }\n\n /**\n * Add or replace an error message.\n *\n */\n setErrorMessage(key, message)\n {\n this.messages[key] = message;\n }\n\n /**\n * Replace the default locale with a new value.\n *\n */\n setLocale(locale)\n {\n this.locale = locale;\n }\n\n /**\n * Replace the default field name with a new value.\n *\n */\n setDefaultFieldName(fieldName)\n {\n this.default_field_name = fieldName;\n }\n}\n\n/**\n * Create an instance of the library.\n *\n */\nif (typeof window !== 'undefined') {\n window.Iodine = new Iodine();\n}\n"],"names":["Iodine","constructor","this","locale","undefined","messages","after","afterOrEqual","array","before","beforeOrEqual","boolean","date","different","endsWith","email","falsy","in","integer","json","max","min","maxLength","minLength","notIn","numeric","optional","regexMatch","required","same","startsWith","string","truthy","url","uuid","_compare","first","second","type","equals","assertDate","assertInteger","getTime","_error","rule","args","param","field","chunks","split","key","shift","join","includes","Date","parseInt","toLocaleTimeString","year","month","day","hour","minute","hour12","message","replace","default_field_name","_missing","valid","error","_prepare","value","rules","length","assertOptional","filter","map","_title","slice","toUpperCase","_validate","errors","index","apply","assert","values","schema","Array","isArray","keys","Object","result","fields","i","hasOwnProperty","assertAfter","assertAfterOrEqual","assertArray","assertBefore","assertBeforeOrEqual","assertBoolean","prototype","toString","call","isNaN","assertDifferent","assertEndsWith","sub","assertString","assertEmail","RegExp","test","String","toLowerCase","assertFalsy","assertIn","options","Number","isInteger","assertJson","JSON","parse","e","assertMax","limit","parseFloat","assertMin","assertMaxLength","assertMinLength","assertNotIn","assertNumeric","isFinite","assertRegexMatch","expression","assertRequired","assertSame","assertStartsWith","assertTruthy","assertUrl","assertUuid","name","closure","setErrorMessages","setErrorMessage","setLocale","setDefaultFieldName","fieldName","window"],"mappings":"AASe,MAAMA,EAMjBC,WAAAA,GAEIC,KAAKC,YAASC,EAEdF,KAAKG,SAAW,CACZC,MAAgB,oCAChBC,aAAgB,gDAChBC,MAAgB,2BAChBC,OAAgB,qCAChBC,cAAgB,iDAChBC,QAAgB,gCAChBC,KAAgB,yBAChBC,UAAgB,yCAChBC,SAAgB,kCAChBC,MAAgB,wCAChBC,MAAgB,2DAChBC,GAAgB,wDAChBC,QAAgB,6BAChBC,KAAgB,gDAChBC,IAAgB,gDAChBC,IAAgB,mDAChBC,UAAgB,iEAChBC,UAAgB,2DAChBC,MAAgB,4DAChBC,QAAgB,0BAChBC,SAAgB,sBAChBC,WAAgB,wDAChBC,SAAgB,0BAChBC,KAAgB,4BAChBC,WAAgB,oCAChBC,OAAgB,2BAChBC,OAAgB,0DAChBC,IAAgB,8BAChBC,KAAgB,+BAExB,CAMAC,QAAAA,CAASC,EAAOC,EAAQC,EAAMC,GAAS,GAEnC,QAAMrC,KAAKsC,WAAWJ,OAEhBlC,KAAKsC,WAAWH,KAAanC,KAAKuC,cAAcJ,MAEtDA,EAA2B,iBAAXA,EAAsBA,EAASA,EAAOK,UAEzC,SAATJ,GAAmBC,EAAiBH,EAAMM,WAAaL,EAC9C,SAATC,GAAqBC,EACZ,SAATD,GAAmBC,EAAiBH,EAAMM,WAAaL,EAC9C,SAATC,GAAqBC,OAAzB,EAAwCH,EAAMM,UAAYL,EAFlBD,EAAMM,UAAYL,EAG9D,CAMAM,MAAAA,CAAOC,EAAMC,OAAOzC,GAEhB,IAAI0C,MAAEA,EAAKC,MAAEA,GAA0B,iBAATF,EAAoBA,EAAO,CAAEC,MAAQD,EAAME,WAAQ3C,GAEjF,MAAM4C,EAASJ,EAAKK,MAAM,KAE1B,IAAIC,EAAMF,EAAOG,QAEjBL,EAAQA,GAASE,EAAOI,KAAK,KAEzB,CAAC,QAAS,eAAgB,SAAU,iBAAiBC,SAASH,KAC9DJ,EAAQ,IAAIQ,KAAKC,SAAST,IAAQU,mBAAmBtD,KAAKC,OAAQ,CAC9DsD,KAAS,UACTC,MAAS,QACTC,IAAS,UACTC,KAAS,UACTC,OAAS,UACTC,QAAS,KAIjB,IAAIC,EAAU,CAAC,UAAM3D,EAAW,IAAIiD,SAASP,GACvC5C,KAAKG,SAAS6C,GACdhD,KAAKG,SAAS6C,GAAKc,QAAQ,UAAWlB,GAE5C,MAAO,CAAC,UAAM1C,EAAW,IAAIiD,SAASN,GAChCgB,EAAQC,QAAQ,UAAW9D,KAAK+D,oBAAsB,SACtDF,EAAQC,QAAQ,UAAWjB,EACrC,CAMAmB,QAAAA,GAEI,MAAO,CACHC,OAAQ,EACRvB,KAAQ,OACRwB,MAAQ,kDAEhB,CAMAC,QAAAA,CAASC,EAAOC,EAAQ,IAEpB,OAAMA,EAAMC,OAEK,aAAbD,EAAM,IAAqBrE,KAAKuE,eAAeH,GAAe,GAE3DC,EAAMG,OAAO9B,GAAiB,aAATA,GAAqB+B,IAAI/B,GAChC,iBAAVA,EACL,CAACA,EAAM1C,KAAK0E,OAAOhC,EAAKK,MAAM,KAAKE,SAAUP,EAAKK,MAAM,KAAK4B,MAAM,GAAGzB,KAAK,MAC3E,CAAE,GAAGR,EAAKA,QAAUA,EAAKE,QAAU5C,KAAK0E,OAAOhC,EAAKA,MAAOA,EAAKE,QAP3C,EAS/B,CAMA8B,MAAAA,CAAON,GAEH,MAAQ,GAAEA,EAAM,GAAGQ,gBAAgBR,EAAMO,MAAM,IACnD,CAMAE,SAAAA,CAAUT,EAAOC,EAAOS,EAAS,MAE7B,IAAK,IAAIC,KAASV,EAAQrE,KAAKmE,SAASC,EAAOC,GAC3C,IAAMrE,KAAM,SAAQqE,EAAMU,GAAO,MAAMC,MAAMhF,KAAM,CAACoE,EAAOC,EAAMU,GAAO,KACpE,MAAO,CACHd,OAAQ,EACRvB,KAAQ2B,EAAMU,GAAO,GACrBb,MAAQY,EACJA,EAAOT,EAAMU,GAAO,IACpB/E,KAAKyC,OAAO4B,EAAMU,GAAO,KAKzC,MAAO,CACHd,OAAQ,EACRvB,KAAQ,GACRwB,MAAQ,GAEhB,CAMAe,MAAAA,CAAOC,EAAQC,EAAQL,EAAS,MAE5B,GAAIM,MAAMC,QAAQF,GACd,OAAWnF,KAAC6E,UAAUK,EAAQC,EAAQL,GAG1C,IAAIQ,EAAOC,OAAOD,KAAKH,GAEnBK,EAAS,CAAEvB,OAAQ,EAAMwB,OAAS,CAAA,GAEtC,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,EAAKhB,OAAQoB,IAC7BF,EAAOC,OAAOH,EAAKI,IAAMR,EAAOS,eAAeL,EAAKI,IAC9C1F,KAAK6E,UAAUK,EAAOI,EAAKI,IAAKP,EAAOG,EAAKI,IAAe,MAAVZ,EAAiBA,EAAOQ,EAAKI,IAAM,MACpF1F,KAAKgE,WAELwB,EAAOC,OAAOH,EAAKI,IAAIzB,QACzBuB,EAAOvB,OAAQ,GAIvB,OAAOuB,CACX,CAMAI,WAAAA,CAAYxB,EAAOhE,GAEf,OAAWJ,KAACiC,SAASmC,EAAOhE,EAAO,QAAQ,EAC/C,CAMAyF,kBAAAA,CAAmBzB,EAAOhE,GAEtB,OAAOJ,KAAKiC,SAASmC,EAAOhE,EAAO,QAAQ,EAC/C,CAMA0F,WAAAA,CAAY1B,GAER,OAAOgB,MAAMC,QAAQjB,EACzB,CAMA2B,YAAAA,CAAa3B,EAAO7D,GAEhB,OAAWP,KAACiC,SAASmC,EAAO7D,EAAQ,QAAQ,EAChD,CAMAyF,mBAAAA,CAAoB5B,EAAO7D,GAEvB,OAAWP,KAACiC,SAASmC,EAAO7D,EAAQ,QAAQ,EAChD,CAMA0F,aAAAA,CAAc7B,GAEV,MAAO,EAAC,GAAM,GAAOjB,SAASiB,EAClC,CAMA9B,UAAAA,CAAW8B,GAEP,OAAQA,GAAmD,kBAA1CmB,OAAOW,UAAUC,SAASC,KAAKhC,KAAgCiC,MAAMjC,EAC1F,CAMAkC,eAAAA,CAAgBlC,EAAOzD,GAEnB,OAAOyD,GAASzD,CACpB,CAMA4F,cAAAA,CAAenC,EAAOoC,GAElB,OAAOxG,KAAKyG,aAAarC,IAAUA,EAAMxD,SAAS4F,EACtD,CAMAE,WAAAA,CAAYtC,GAIR,OAAO,IAAIuC,OAFC,6IAEaC,KAAKC,OAAOzC,GAAO0C,cAChD,CAMAC,WAAAA,CAAY3C,GAER,MAAO,CAAC,EAAG,KAAK,EAAO,SAASjB,SAASiB,EAC7C,CAMA4C,QAAAA,CAAS5C,EAAO6C,GAEZ,OAA2B,iBAAZA,EAAuBA,EAAQlE,MAAM,KAAOkE,GAAS9D,SAASiB,EACjF,CAMA7B,aAAAA,CAAc6B,GAEV,OAAO8C,OAAOC,UAAU/C,IAAUf,SAASe,GAAO+B,aAAe/B,EAAM+B,UAC3E,CAMAiB,UAAAA,CAAWhD,GAEP,IACI,MAAoC,iBAAtBiD,KAAKC,MAAMlD,EAC7B,CAAE,MAAOmD,GACL,QACJ,CACJ,CAMAC,SAAAA,CAAUpD,EAAOqD,GAEb,OAAOC,WAAWtD,IAAUqD,CAChC,CAMAE,SAAAA,CAAUvD,EAAOqD,GAEb,OAAOC,WAAWtD,IAAUqD,CAChC,CAMAG,eAAAA,CAAgBxD,EAAOqD,GAEnB,MAAwB,iBAAVrD,GAAqBA,EAAME,QAAUmD,CACvD,CAMAI,eAAAA,CAAgBzD,EAAOqD,GAEnB,MAAwB,iBAAVrD,GAAqBA,EAAME,QAAUmD,CACvD,CAMAK,WAAAA,CAAY1D,EAAO6C,GAEf,OAASjH,KAAKgH,SAAS5C,EAAO6C,EAClC,CAMAc,aAAAA,CAAc3D,GAEV,OAASiC,MAAMqB,WAAWtD,KAAW4D,SAAS5D,EAClD,CAMAG,cAAAA,CAAeH,GAEX,MAAO,CAAC,UAAMlE,EAAW,IAAIiD,SAASiB,EAC1C,CAMA6D,gBAAAA,CAAiB7D,EAAO8D,GAEpB,OAAW,IAAAvB,OAAOuB,GAAYtB,KAAKC,OAAOzC,GAC9C,CAMA+D,cAAAA,CAAe/D,GAEX,OAASpE,KAAKuE,eAAeH,EACjC,CAMAgE,UAAAA,CAAWhE,EAAOzC,GAEd,OAAOyC,GAASzC,CACpB,CAMA0G,gBAAAA,CAAiBjE,EAAOoC,GAEpB,YAAYC,aAAarC,IAAUA,EAAMxC,WAAW4E,EACxD,CAMAC,YAAAA,CAAarC,GAET,MAAwB,iBAAVA,CAClB,CAMAkE,YAAAA,CAAalE,GAET,MAAO,CAAC,EAAG,KAAK,EAAM,QAAQjB,SAASiB,EAC3C,CAMAmE,SAAAA,CAAUnE,GAIN,OAAO,IAAIuC,OAFC,yKAEaC,KAAKC,OAAOzC,GAAO0C,cAChD,CAMA0B,UAAAA,CAAWpE,GAIP,OAAW,IAAAuC,OAFC,6EAEaC,KAAKC,OAAOzC,GAAO0C,cAChD,CAMApE,IAAAA,CAAK+F,EAAMC,GAEP5I,EAAOoG,UAAW,SAAQlG,KAAK0E,OAAO+D,MAAWC,CACrD,CAMAC,gBAAAA,CAAiBxI,GAEbH,KAAKG,SAAWA,CACpB,CAMAyI,eAAAA,CAAgB5F,EAAKa,GAEjB7D,KAAKG,SAAS6C,GAAOa,CACzB,CAMAgF,SAAAA,CAAU5I,GAEND,KAAKC,OAASA,CAClB,CAMA6I,mBAAAA,CAAoBC,GAEhB/I,KAAK+D,mBAAqBgF,CAC9B,EAOkB,oBAAXC,SACPA,OAAOlJ,OAAS,IAAIA"} -------------------------------------------------------------------------------- /dist/iodine.min.esm.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"iodine.min.esm.js","sources":["../src/iodine.js"],"sourcesContent":["/*\n|--------------------------------------------------------------------------\n| Iodine - JavaScript Library\n|--------------------------------------------------------------------------\n|\n| This library contains a collection of useful validation rules that can\n| be used to quickly verify whether items meet certain conditions.\n|\n*/\nexport default class Iodine\n{\n /**\n * Constructor.\n *\n */\n constructor()\n {\n this.locale = undefined;\n\n this.messages = {\n after : \"The date must be after: '[PARAM]'\",\n afterOrEqual : \"The date must be after or equal to: '[PARAM]'\",\n array : \"[FIELD] must be an array\",\n before : \"The date must be before: '[PARAM]'\",\n beforeOrEqual : \"The date must be before or equal to: '[PARAM]'\",\n boolean : \"[FIELD] must be true or false\",\n date : \"[FIELD] must be a date\",\n different : \"[FIELD] must be different to '[PARAM]'\",\n endsWith : \"[FIELD] must end with '[PARAM]'\",\n email : \"[FIELD] must be a valid email address\",\n falsy : \"[FIELD] must be a falsy value (false, 'false', 0 or '0')\",\n in : \"[FIELD] must be one of the following options: [PARAM]\",\n integer : \"[FIELD] must be an integer\",\n json : \"[FIELD] must be a parsable JSON object string\",\n max : \"[FIELD] must be less than or equal to [PARAM]\",\n min : \"[FIELD] must be greater than or equal to [PARAM]\",\n maxLength : \"[FIELD] must not be greater than '[PARAM]' in character length\",\n minLength : \"[FIELD] must not be less than '[PARAM]' character length\",\n notIn : \"[FIELD] must not be one of the following options: [PARAM]\",\n numeric : \"[FIELD] must be numeric\",\n optional : \"[FIELD] is optional\",\n regexMatch : \"[FIELD] must satisify the regular expression: [PARAM]\",\n required : \"[FIELD] must be present\",\n same : \"[FIELD] must be '[PARAM]'\",\n startsWith : \"[FIELD] must start with '[PARAM]'\",\n string : \"[FIELD] must be a string\",\n truthy : \"[FIELD] must be a truthy value (true, 'true', 1 or '1')\",\n url : \"[FIELD] must be a valid url\",\n uuid : \"[FIELD] must be a valid UUID\",\n };\n }\n\n /**\n * @internal.\n *\n */\n _compare(first, second, type, equals = false)\n {\n if (! this.assertDate(first)) return false;\n\n if (! this.assertDate(second) && ! this.assertInteger(second)) return false;\n\n second = typeof second === 'number' ? second : second.getTime();\n\n if (type === 'less' && equals) return first.getTime() <= second;\n if (type === 'less' && ! equals) return first.getTime() < second;\n if (type === 'more' && equals) return first.getTime() >= second;\n if (type === 'more' && ! equals) return first.getTime() > second;\n }\n\n /**\n * @internal.\n *\n */\n _error(rule, args = undefined)\n {\n let { param, field } = typeof args === 'object' ? args : { param : args, field : undefined };\n\n const chunks = rule.split(':');\n\n let key = chunks.shift();\n\n param = param || chunks.join(':');\n\n if (['after', 'afterOrEqual', 'before', 'beforeOrEqual'].includes(key)) {\n param = new Date(parseInt(param)).toLocaleTimeString(this.locale, {\n year : 'numeric',\n month : 'short',\n day : 'numeric',\n hour : '2-digit',\n minute : 'numeric',\n hour12 : false,\n });\n }\n\n let message = [null, undefined, ''].includes(param)\n ? this.messages[key]\n : this.messages[key].replace('[PARAM]', param);\n\n return [null, undefined, ''].includes(field)\n ? message.replace('[FIELD]', this.default_field_name || 'Value')\n : message.replace('[FIELD]', field);\n }\n\n /**\n * @internal.\n *\n */\n _missing()\n {\n return {\n valid : false,\n rule : 'None',\n error : 'Rules exist, but no value was provided to check',\n };\n }\n\n /**\n * @internal.\n *\n */\n _prepare(value, rules = [])\n {\n if (! rules.length) return [];\n\n if (rules[0] === 'optional' && this.assertOptional(value)) return [];\n\n return rules.filter(rule => rule !== 'optional').map(rule =>\n typeof(rule) === 'string'\n ? [rule, this._title(rule.split(':').shift()), rule.split(':').slice(1).join(':')]\n : [`${ rule.rule }:${ rule.param }`, this._title(rule.rule), rule.param]\n );\n }\n\n /**\n * @internal.\n *\n */\n _title(value)\n {\n return `${value[0].toUpperCase()}${value.slice(1)}`;\n }\n\n /**\n * @internal.\n *\n */\n _validate(value, rules, errors = null)\n {\n for (let index in rules = this._prepare(value, rules)) {\n if (! this[`assert${rules[index][1]}`].apply(this, [value, rules[index][2]])) {\n return {\n valid : false,\n rule : rules[index][0],\n error : errors \n ? errors[rules[index][0]]\n : this._error(rules[index][0]),\n };\n }\n }\n\n return {\n valid : true,\n rule : '',\n error : '',\n };\n }\n\n /**\n * Determine if the given content matches the given schema.\n *\n */\n assert(values, schema, errors = null)\n {\n if (Array.isArray(schema)) {\n return this._validate(values, schema, errors);\n }\n\n let keys = Object.keys(schema);\n\n let result = { valid : true, fields : { } };\n\n for (let i = 0; i < keys.length; i++) {\n result.fields[keys[i]] = values.hasOwnProperty(keys[i])\n ? this._validate(values[keys[i]], schema[keys[i]], errors != null ? errors[keys[i]] : null)\n : this._missing();\n\n if (! result.fields[keys[i]].valid) {\n result.valid = false;\n }\n }\n\n return result;\n }\n\n /**\n * Determine if the given date is after another given date.\n *\n */\n assertAfter(value, after)\n {\n return this._compare(value, after, 'more', false);\n }\n\n /**\n * Determine if the given date is after or equal to another given date.\n *\n */\n assertAfterOrEqual(value, after)\n {\n return this._compare(value, after, 'more', true);\n }\n\n /**\n * Determine if the given value is an array.\n *\n */\n assertArray(value)\n {\n return Array.isArray(value);\n }\n\n /**\n * Determine if the given date is before another given date.\n *\n */\n assertBefore(value, before)\n {\n return this._compare(value, before, 'less', false);\n }\n\n /**\n * Determine if the given date is before or equal to another given date.\n *\n */\n assertBeforeOrEqual(value, before)\n {\n return this._compare(value, before, 'less', true);\n }\n\n /**\n * Determine if the given value is a boolean.\n *\n */\n assertBoolean(value)\n {\n return [true, false].includes(value);\n }\n\n /**\n * Determine if the given value is a date object.\n *\n */\n assertDate(value)\n {\n return (value && Object.prototype.toString.call(value) === '[object Date]' && ! isNaN(value));\n }\n\n /**\n * Determine if the given value is different to another given value.\n *\n */\n assertDifferent(value, different)\n {\n return value != different;\n }\n\n /**\n * Determine if the given value ends with another given value.\n *\n */\n assertEndsWith(value, sub)\n {\n return this.assertString(value) && value.endsWith(sub);\n }\n\n /**\n * Determine if the given value is a valid email address.\n *\n */\n assertEmail(value)\n {\n let regex = \"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is falsy.\n *\n */\n assertFalsy(value)\n {\n return [0, '0', false, 'false'].includes(value);\n }\n\n /**\n * Determine if the given value is within the given array of options.\n *\n */\n assertIn(value, options)\n {\n return (typeof options === 'string' ? options.split(',') : options).includes(value);\n }\n\n /**\n * Determine if the given value is an integer.\n *\n */\n assertInteger(value)\n {\n return Number.isInteger(value) && parseInt(value).toString() === value.toString();\n }\n\n /**\n * Determine if the given value is a JSON string.\n *\n */\n assertJson(value)\n {\n try {\n return typeof JSON.parse(value) === 'object';\n } catch (e) {\n return false;\n }\n }\n\n /**\n * Determine if the given number is less than or equal to the maximum limit.\n *\n */\n assertMax(value, limit)\n {\n return parseFloat(value) <= limit;\n }\n\n /**\n * Determine if the given number is greater than or equal to the minimum limit.\n *\n */\n assertMin(value, limit)\n {\n return parseFloat(value) >= limit;\n }\n\n /**\n * Determine if the given value string length is less than or equal to the maximum limit.\n *\n */\n assertMaxLength(value, limit)\n {\n return typeof value === 'string' ? value.length <= limit : false;\n }\n\n /**\n * Determine if the given value string length is greater than or equal to the minimum limit.\n *\n */\n assertMinLength(value, limit)\n {\n return typeof value === 'string' ? value.length >= limit : false;\n }\n\n /**\n * Determine if the given value is not within the given array of options.\n *\n */\n assertNotIn(value, options)\n {\n return ! this.assertIn(value, options);\n }\n\n /**\n * Determine if the given value is numeric (an integer or a float).\n *\n */\n assertNumeric(value)\n {\n return ! isNaN(parseFloat(value)) && isFinite(value);\n }\n\n /**\n * Determine if the given value is optional.\n *\n */\n assertOptional(value)\n {\n return [null, undefined, ''].includes(value);\n }\n\n /**\n * Determine if the given value satisifies the given regular expression.\n *\n */\n assertRegexMatch(value, expression)\n {\n return new RegExp(expression).test(String(value));\n }\n\n /**\n * Determine if the given value is present.\n *\n */\n assertRequired(value)\n {\n return ! this.assertOptional(value);\n }\n\n /**\n * Determine if the given value is the same as another given value.\n *\n */\n assertSame(value, same)\n {\n return value == same;\n }\n\n /**\n * Determine if the given value starts with another given value.\n *\n */\n assertStartsWith(value, sub)\n {\n return this.assertString(value) && value.startsWith(sub);\n }\n\n /**\n * Determine if the given value is a string.\n *\n */\n assertString(value)\n {\n return typeof value === 'string';\n }\n\n /**\n * Determine if the given value is truthy.\n *\n */\n assertTruthy(value)\n {\n return [1, '1', true, 'true'].includes(value);\n }\n\n /**\n * Determine if the given value is a valid URL.\n *\n */\n assertUrl(value)\n {\n let regex = \"^(https?:\\\\/\\\\/)?((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|((\\\\d{1,3}\\\\.){3}\\\\d{1,3}))(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*(\\\\?[;&a-z\\\\d%_.~+=-]*)?(\\\\#[-a-z\\\\d_]*)?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is a valid UUID.\n *\n */\n assertUuid(value)\n {\n let regex = \"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Attach a custom validation rule to the library.\n *\n */\n rule(name, closure)\n {\n Iodine.prototype[`assert${this._title(name)}`] = closure;\n }\n\n /**\n * Replace the default error messages with a new set.\n *\n */\n setErrorMessages(messages)\n {\n this.messages = messages;\n }\n\n /**\n * Add or replace an error message.\n *\n */\n setErrorMessage(key, message)\n {\n this.messages[key] = message;\n }\n\n /**\n * Replace the default locale with a new value.\n *\n */\n setLocale(locale)\n {\n this.locale = locale;\n }\n\n /**\n * Replace the default field name with a new value.\n *\n */\n setDefaultFieldName(fieldName)\n {\n this.default_field_name = fieldName;\n }\n}\n\n/**\n * Create an instance of the library.\n *\n */\nif (typeof window !== 'undefined') {\n window.Iodine = new Iodine();\n}\n"],"names":["Iodine","constructor","this","locale","undefined","messages","after","afterOrEqual","array","before","beforeOrEqual","boolean","date","different","endsWith","email","falsy","in","integer","json","max","min","maxLength","minLength","notIn","numeric","optional","regexMatch","required","same","startsWith","string","truthy","url","uuid","_compare","first","second","type","equals","assertDate","assertInteger","getTime","_error","rule","args","param","field","chunks","split","key","shift","join","includes","Date","parseInt","toLocaleTimeString","year","month","day","hour","minute","hour12","message","replace","default_field_name","_missing","valid","error","_prepare","value","rules","length","assertOptional","filter","map","_title","slice","toUpperCase","_validate","errors","index","apply","assert","values","schema","Array","isArray","keys","Object","result","fields","i","hasOwnProperty","assertAfter","assertAfterOrEqual","assertArray","assertBefore","assertBeforeOrEqual","assertBoolean","prototype","toString","call","isNaN","assertDifferent","assertEndsWith","sub","assertString","assertEmail","RegExp","test","String","toLowerCase","assertFalsy","assertIn","options","Number","isInteger","assertJson","JSON","parse","e","assertMax","limit","parseFloat","assertMin","assertMaxLength","assertMinLength","assertNotIn","assertNumeric","isFinite","assertRegexMatch","expression","assertRequired","assertSame","assertStartsWith","assertTruthy","assertUrl","assertUuid","name","closure","setErrorMessages","setErrorMessage","setLocale","setDefaultFieldName","fieldName","window"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/iodine.min.umd.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"iodine.min.umd.js","sources":["../src/iodine.js"],"sourcesContent":["/*\n|--------------------------------------------------------------------------\n| Iodine - JavaScript Library\n|--------------------------------------------------------------------------\n|\n| This library contains a collection of useful validation rules that can\n| be used to quickly verify whether items meet certain conditions.\n|\n*/\nexport default class Iodine\n{\n /**\n * Constructor.\n *\n */\n constructor()\n {\n this.locale = undefined;\n\n this.messages = {\n after : \"The date must be after: '[PARAM]'\",\n afterOrEqual : \"The date must be after or equal to: '[PARAM]'\",\n array : \"[FIELD] must be an array\",\n before : \"The date must be before: '[PARAM]'\",\n beforeOrEqual : \"The date must be before or equal to: '[PARAM]'\",\n boolean : \"[FIELD] must be true or false\",\n date : \"[FIELD] must be a date\",\n different : \"[FIELD] must be different to '[PARAM]'\",\n endsWith : \"[FIELD] must end with '[PARAM]'\",\n email : \"[FIELD] must be a valid email address\",\n falsy : \"[FIELD] must be a falsy value (false, 'false', 0 or '0')\",\n in : \"[FIELD] must be one of the following options: [PARAM]\",\n integer : \"[FIELD] must be an integer\",\n json : \"[FIELD] must be a parsable JSON object string\",\n max : \"[FIELD] must be less than or equal to [PARAM]\",\n min : \"[FIELD] must be greater than or equal to [PARAM]\",\n maxLength : \"[FIELD] must not be greater than '[PARAM]' in character length\",\n minLength : \"[FIELD] must not be less than '[PARAM]' character length\",\n notIn : \"[FIELD] must not be one of the following options: [PARAM]\",\n numeric : \"[FIELD] must be numeric\",\n optional : \"[FIELD] is optional\",\n regexMatch : \"[FIELD] must satisify the regular expression: [PARAM]\",\n required : \"[FIELD] must be present\",\n same : \"[FIELD] must be '[PARAM]'\",\n startsWith : \"[FIELD] must start with '[PARAM]'\",\n string : \"[FIELD] must be a string\",\n truthy : \"[FIELD] must be a truthy value (true, 'true', 1 or '1')\",\n url : \"[FIELD] must be a valid url\",\n uuid : \"[FIELD] must be a valid UUID\",\n };\n }\n\n /**\n * @internal.\n *\n */\n _compare(first, second, type, equals = false)\n {\n if (! this.assertDate(first)) return false;\n\n if (! this.assertDate(second) && ! this.assertInteger(second)) return false;\n\n second = typeof second === 'number' ? second : second.getTime();\n\n if (type === 'less' && equals) return first.getTime() <= second;\n if (type === 'less' && ! equals) return first.getTime() < second;\n if (type === 'more' && equals) return first.getTime() >= second;\n if (type === 'more' && ! equals) return first.getTime() > second;\n }\n\n /**\n * @internal.\n *\n */\n _error(rule, args = undefined)\n {\n let { param, field } = typeof args === 'object' ? args : { param : args, field : undefined };\n\n const chunks = rule.split(':');\n\n let key = chunks.shift();\n\n param = param || chunks.join(':');\n\n if (['after', 'afterOrEqual', 'before', 'beforeOrEqual'].includes(key)) {\n param = new Date(parseInt(param)).toLocaleTimeString(this.locale, {\n year : 'numeric',\n month : 'short',\n day : 'numeric',\n hour : '2-digit',\n minute : 'numeric',\n hour12 : false,\n });\n }\n\n let message = [null, undefined, ''].includes(param)\n ? this.messages[key]\n : this.messages[key].replace('[PARAM]', param);\n\n return [null, undefined, ''].includes(field)\n ? message.replace('[FIELD]', this.default_field_name || 'Value')\n : message.replace('[FIELD]', field);\n }\n\n /**\n * @internal.\n *\n */\n _missing()\n {\n return {\n valid : false,\n rule : 'None',\n error : 'Rules exist, but no value was provided to check',\n };\n }\n\n /**\n * @internal.\n *\n */\n _prepare(value, rules = [])\n {\n if (! rules.length) return [];\n\n if (rules[0] === 'optional' && this.assertOptional(value)) return [];\n\n return rules.filter(rule => rule !== 'optional').map(rule =>\n typeof(rule) === 'string'\n ? [rule, this._title(rule.split(':').shift()), rule.split(':').slice(1).join(':')]\n : [`${ rule.rule }:${ rule.param }`, this._title(rule.rule), rule.param]\n );\n }\n\n /**\n * @internal.\n *\n */\n _title(value)\n {\n return `${value[0].toUpperCase()}${value.slice(1)}`;\n }\n\n /**\n * @internal.\n *\n */\n _validate(value, rules, errors = null)\n {\n for (let index in rules = this._prepare(value, rules)) {\n if (! this[`assert${rules[index][1]}`].apply(this, [value, rules[index][2]])) {\n return {\n valid : false,\n rule : rules[index][0],\n error : errors \n ? errors[rules[index][0]]\n : this._error(rules[index][0]),\n };\n }\n }\n\n return {\n valid : true,\n rule : '',\n error : '',\n };\n }\n\n /**\n * Determine if the given content matches the given schema.\n *\n */\n assert(values, schema, errors = null)\n {\n if (Array.isArray(schema)) {\n return this._validate(values, schema, errors);\n }\n\n let keys = Object.keys(schema);\n\n let result = { valid : true, fields : { } };\n\n for (let i = 0; i < keys.length; i++) {\n result.fields[keys[i]] = values.hasOwnProperty(keys[i])\n ? this._validate(values[keys[i]], schema[keys[i]], errors != null ? errors[keys[i]] : null)\n : this._missing();\n\n if (! result.fields[keys[i]].valid) {\n result.valid = false;\n }\n }\n\n return result;\n }\n\n /**\n * Determine if the given date is after another given date.\n *\n */\n assertAfter(value, after)\n {\n return this._compare(value, after, 'more', false);\n }\n\n /**\n * Determine if the given date is after or equal to another given date.\n *\n */\n assertAfterOrEqual(value, after)\n {\n return this._compare(value, after, 'more', true);\n }\n\n /**\n * Determine if the given value is an array.\n *\n */\n assertArray(value)\n {\n return Array.isArray(value);\n }\n\n /**\n * Determine if the given date is before another given date.\n *\n */\n assertBefore(value, before)\n {\n return this._compare(value, before, 'less', false);\n }\n\n /**\n * Determine if the given date is before or equal to another given date.\n *\n */\n assertBeforeOrEqual(value, before)\n {\n return this._compare(value, before, 'less', true);\n }\n\n /**\n * Determine if the given value is a boolean.\n *\n */\n assertBoolean(value)\n {\n return [true, false].includes(value);\n }\n\n /**\n * Determine if the given value is a date object.\n *\n */\n assertDate(value)\n {\n return (value && Object.prototype.toString.call(value) === '[object Date]' && ! isNaN(value));\n }\n\n /**\n * Determine if the given value is different to another given value.\n *\n */\n assertDifferent(value, different)\n {\n return value != different;\n }\n\n /**\n * Determine if the given value ends with another given value.\n *\n */\n assertEndsWith(value, sub)\n {\n return this.assertString(value) && value.endsWith(sub);\n }\n\n /**\n * Determine if the given value is a valid email address.\n *\n */\n assertEmail(value)\n {\n let regex = \"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is falsy.\n *\n */\n assertFalsy(value)\n {\n return [0, '0', false, 'false'].includes(value);\n }\n\n /**\n * Determine if the given value is within the given array of options.\n *\n */\n assertIn(value, options)\n {\n return (typeof options === 'string' ? options.split(',') : options).includes(value);\n }\n\n /**\n * Determine if the given value is an integer.\n *\n */\n assertInteger(value)\n {\n return Number.isInteger(value) && parseInt(value).toString() === value.toString();\n }\n\n /**\n * Determine if the given value is a JSON string.\n *\n */\n assertJson(value)\n {\n try {\n return typeof JSON.parse(value) === 'object';\n } catch (e) {\n return false;\n }\n }\n\n /**\n * Determine if the given number is less than or equal to the maximum limit.\n *\n */\n assertMax(value, limit)\n {\n return parseFloat(value) <= limit;\n }\n\n /**\n * Determine if the given number is greater than or equal to the minimum limit.\n *\n */\n assertMin(value, limit)\n {\n return parseFloat(value) >= limit;\n }\n\n /**\n * Determine if the given value string length is less than or equal to the maximum limit.\n *\n */\n assertMaxLength(value, limit)\n {\n return typeof value === 'string' ? value.length <= limit : false;\n }\n\n /**\n * Determine if the given value string length is greater than or equal to the minimum limit.\n *\n */\n assertMinLength(value, limit)\n {\n return typeof value === 'string' ? value.length >= limit : false;\n }\n\n /**\n * Determine if the given value is not within the given array of options.\n *\n */\n assertNotIn(value, options)\n {\n return ! this.assertIn(value, options);\n }\n\n /**\n * Determine if the given value is numeric (an integer or a float).\n *\n */\n assertNumeric(value)\n {\n return ! isNaN(parseFloat(value)) && isFinite(value);\n }\n\n /**\n * Determine if the given value is optional.\n *\n */\n assertOptional(value)\n {\n return [null, undefined, ''].includes(value);\n }\n\n /**\n * Determine if the given value satisifies the given regular expression.\n *\n */\n assertRegexMatch(value, expression)\n {\n return new RegExp(expression).test(String(value));\n }\n\n /**\n * Determine if the given value is present.\n *\n */\n assertRequired(value)\n {\n return ! this.assertOptional(value);\n }\n\n /**\n * Determine if the given value is the same as another given value.\n *\n */\n assertSame(value, same)\n {\n return value == same;\n }\n\n /**\n * Determine if the given value starts with another given value.\n *\n */\n assertStartsWith(value, sub)\n {\n return this.assertString(value) && value.startsWith(sub);\n }\n\n /**\n * Determine if the given value is a string.\n *\n */\n assertString(value)\n {\n return typeof value === 'string';\n }\n\n /**\n * Determine if the given value is truthy.\n *\n */\n assertTruthy(value)\n {\n return [1, '1', true, 'true'].includes(value);\n }\n\n /**\n * Determine if the given value is a valid URL.\n *\n */\n assertUrl(value)\n {\n let regex = \"^(https?:\\\\/\\\\/)?((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|((\\\\d{1,3}\\\\.){3}\\\\d{1,3}))(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*(\\\\?[;&a-z\\\\d%_.~+=-]*)?(\\\\#[-a-z\\\\d_]*)?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is a valid UUID.\n *\n */\n assertUuid(value)\n {\n let regex = \"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Attach a custom validation rule to the library.\n *\n */\n rule(name, closure)\n {\n Iodine.prototype[`assert${this._title(name)}`] = closure;\n }\n\n /**\n * Replace the default error messages with a new set.\n *\n */\n setErrorMessages(messages)\n {\n this.messages = messages;\n }\n\n /**\n * Add or replace an error message.\n *\n */\n setErrorMessage(key, message)\n {\n this.messages[key] = message;\n }\n\n /**\n * Replace the default locale with a new value.\n *\n */\n setLocale(locale)\n {\n this.locale = locale;\n }\n\n /**\n * Replace the default field name with a new value.\n *\n */\n setDefaultFieldName(fieldName)\n {\n this.default_field_name = fieldName;\n }\n}\n\n/**\n * Create an instance of the library.\n *\n */\nif (typeof window !== 'undefined') {\n window.Iodine = new Iodine();\n}\n"],"names":["Iodine","constructor","this","locale","undefined","messages","after","afterOrEqual","array","before","beforeOrEqual","boolean","date","different","endsWith","email","falsy","in","integer","json","max","min","maxLength","minLength","notIn","numeric","optional","regexMatch","required","same","startsWith","string","truthy","url","uuid","_compare","first","second","type","equals","assertDate","assertInteger","getTime","_error","rule","args","param","field","chunks","split","key","shift","join","includes","Date","parseInt","toLocaleTimeString","year","month","day","hour","minute","hour12","message","replace","default_field_name","_missing","valid","error","_prepare","value","rules","length","assertOptional","filter","map","_title","slice","toUpperCase","_validate","errors","index","apply","assert","values","schema","Array","isArray","keys","Object","result","fields","i","hasOwnProperty","assertAfter","assertAfterOrEqual","assertArray","assertBefore","assertBeforeOrEqual","assertBoolean","prototype","toString","call","isNaN","assertDifferent","assertEndsWith","sub","assertString","assertEmail","RegExp","test","String","toLowerCase","assertFalsy","assertIn","options","Number","isInteger","assertJson","JSON","parse","e","assertMax","limit","parseFloat","assertMin","assertMaxLength","assertMinLength","assertNotIn","assertNumeric","isFinite","assertRegexMatch","expression","assertRequired","assertSame","assertStartsWith","assertTruthy","assertUrl","assertUuid","name","closure","setErrorMessages","setErrorMessage","setLocale","setDefaultFieldName","fieldName","window"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/iodine.min.modern.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"iodine.min.modern.js","sources":["../src/iodine.js"],"sourcesContent":["/*\n|--------------------------------------------------------------------------\n| Iodine - JavaScript Library\n|--------------------------------------------------------------------------\n|\n| This library contains a collection of useful validation rules that can\n| be used to quickly verify whether items meet certain conditions.\n|\n*/\nexport default class Iodine\n{\n /**\n * Constructor.\n *\n */\n constructor()\n {\n this.locale = undefined;\n\n this.messages = {\n after : \"The date must be after: '[PARAM]'\",\n afterOrEqual : \"The date must be after or equal to: '[PARAM]'\",\n array : \"[FIELD] must be an array\",\n before : \"The date must be before: '[PARAM]'\",\n beforeOrEqual : \"The date must be before or equal to: '[PARAM]'\",\n boolean : \"[FIELD] must be true or false\",\n date : \"[FIELD] must be a date\",\n different : \"[FIELD] must be different to '[PARAM]'\",\n endsWith : \"[FIELD] must end with '[PARAM]'\",\n email : \"[FIELD] must be a valid email address\",\n falsy : \"[FIELD] must be a falsy value (false, 'false', 0 or '0')\",\n in : \"[FIELD] must be one of the following options: [PARAM]\",\n integer : \"[FIELD] must be an integer\",\n json : \"[FIELD] must be a parsable JSON object string\",\n max : \"[FIELD] must be less than or equal to [PARAM]\",\n min : \"[FIELD] must be greater than or equal to [PARAM]\",\n maxLength : \"[FIELD] must not be greater than '[PARAM]' in character length\",\n minLength : \"[FIELD] must not be less than '[PARAM]' character length\",\n notIn : \"[FIELD] must not be one of the following options: [PARAM]\",\n numeric : \"[FIELD] must be numeric\",\n optional : \"[FIELD] is optional\",\n regexMatch : \"[FIELD] must satisify the regular expression: [PARAM]\",\n required : \"[FIELD] must be present\",\n same : \"[FIELD] must be '[PARAM]'\",\n startsWith : \"[FIELD] must start with '[PARAM]'\",\n string : \"[FIELD] must be a string\",\n truthy : \"[FIELD] must be a truthy value (true, 'true', 1 or '1')\",\n url : \"[FIELD] must be a valid url\",\n uuid : \"[FIELD] must be a valid UUID\",\n };\n }\n\n /**\n * @internal.\n *\n */\n _compare(first, second, type, equals = false)\n {\n if (! this.assertDate(first)) return false;\n\n if (! this.assertDate(second) && ! this.assertInteger(second)) return false;\n\n second = typeof second === 'number' ? second : second.getTime();\n\n if (type === 'less' && equals) return first.getTime() <= second;\n if (type === 'less' && ! equals) return first.getTime() < second;\n if (type === 'more' && equals) return first.getTime() >= second;\n if (type === 'more' && ! equals) return first.getTime() > second;\n }\n\n /**\n * @internal.\n *\n */\n _error(rule, args = undefined)\n {\n let { param, field } = typeof args === 'object' ? args : { param : args, field : undefined };\n\n const chunks = rule.split(':');\n\n let key = chunks.shift();\n\n param = param || chunks.join(':');\n\n if (['after', 'afterOrEqual', 'before', 'beforeOrEqual'].includes(key)) {\n param = new Date(parseInt(param)).toLocaleTimeString(this.locale, {\n year : 'numeric',\n month : 'short',\n day : 'numeric',\n hour : '2-digit',\n minute : 'numeric',\n hour12 : false,\n });\n }\n\n let message = [null, undefined, ''].includes(param)\n ? this.messages[key]\n : this.messages[key].replace('[PARAM]', param);\n\n return [null, undefined, ''].includes(field)\n ? message.replace('[FIELD]', this.default_field_name || 'Value')\n : message.replace('[FIELD]', field);\n }\n\n /**\n * @internal.\n *\n */\n _missing()\n {\n return {\n valid : false,\n rule : 'None',\n error : 'Rules exist, but no value was provided to check',\n };\n }\n\n /**\n * @internal.\n *\n */\n _prepare(value, rules = [])\n {\n if (! rules.length) return [];\n\n if (rules[0] === 'optional' && this.assertOptional(value)) return [];\n\n return rules.filter(rule => rule !== 'optional').map(rule =>\n typeof(rule) === 'string'\n ? [rule, this._title(rule.split(':').shift()), rule.split(':').slice(1).join(':')]\n : [`${ rule.rule }:${ rule.param }`, this._title(rule.rule), rule.param]\n );\n }\n\n /**\n * @internal.\n *\n */\n _title(value)\n {\n return `${value[0].toUpperCase()}${value.slice(1)}`;\n }\n\n /**\n * @internal.\n *\n */\n _validate(value, rules, errors = null)\n {\n for (let index in rules = this._prepare(value, rules)) {\n if (! this[`assert${rules[index][1]}`].apply(this, [value, rules[index][2]])) {\n return {\n valid : false,\n rule : rules[index][0],\n error : errors \n ? errors[rules[index][0]]\n : this._error(rules[index][0]),\n };\n }\n }\n\n return {\n valid : true,\n rule : '',\n error : '',\n };\n }\n\n /**\n * Determine if the given content matches the given schema.\n *\n */\n assert(values, schema, errors = null)\n {\n if (Array.isArray(schema)) {\n return this._validate(values, schema, errors);\n }\n\n let keys = Object.keys(schema);\n\n let result = { valid : true, fields : { } };\n\n for (let i = 0; i < keys.length; i++) {\n result.fields[keys[i]] = values.hasOwnProperty(keys[i])\n ? this._validate(values[keys[i]], schema[keys[i]], errors != null ? errors[keys[i]] : null)\n : this._missing();\n\n if (! result.fields[keys[i]].valid) {\n result.valid = false;\n }\n }\n\n return result;\n }\n\n /**\n * Determine if the given date is after another given date.\n *\n */\n assertAfter(value, after)\n {\n return this._compare(value, after, 'more', false);\n }\n\n /**\n * Determine if the given date is after or equal to another given date.\n *\n */\n assertAfterOrEqual(value, after)\n {\n return this._compare(value, after, 'more', true);\n }\n\n /**\n * Determine if the given value is an array.\n *\n */\n assertArray(value)\n {\n return Array.isArray(value);\n }\n\n /**\n * Determine if the given date is before another given date.\n *\n */\n assertBefore(value, before)\n {\n return this._compare(value, before, 'less', false);\n }\n\n /**\n * Determine if the given date is before or equal to another given date.\n *\n */\n assertBeforeOrEqual(value, before)\n {\n return this._compare(value, before, 'less', true);\n }\n\n /**\n * Determine if the given value is a boolean.\n *\n */\n assertBoolean(value)\n {\n return [true, false].includes(value);\n }\n\n /**\n * Determine if the given value is a date object.\n *\n */\n assertDate(value)\n {\n return (value && Object.prototype.toString.call(value) === '[object Date]' && ! isNaN(value));\n }\n\n /**\n * Determine if the given value is different to another given value.\n *\n */\n assertDifferent(value, different)\n {\n return value != different;\n }\n\n /**\n * Determine if the given value ends with another given value.\n *\n */\n assertEndsWith(value, sub)\n {\n return this.assertString(value) && value.endsWith(sub);\n }\n\n /**\n * Determine if the given value is a valid email address.\n *\n */\n assertEmail(value)\n {\n let regex = \"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is falsy.\n *\n */\n assertFalsy(value)\n {\n return [0, '0', false, 'false'].includes(value);\n }\n\n /**\n * Determine if the given value is within the given array of options.\n *\n */\n assertIn(value, options)\n {\n return (typeof options === 'string' ? options.split(',') : options).includes(value);\n }\n\n /**\n * Determine if the given value is an integer.\n *\n */\n assertInteger(value)\n {\n return Number.isInteger(value) && parseInt(value).toString() === value.toString();\n }\n\n /**\n * Determine if the given value is a JSON string.\n *\n */\n assertJson(value)\n {\n try {\n return typeof JSON.parse(value) === 'object';\n } catch (e) {\n return false;\n }\n }\n\n /**\n * Determine if the given number is less than or equal to the maximum limit.\n *\n */\n assertMax(value, limit)\n {\n return parseFloat(value) <= limit;\n }\n\n /**\n * Determine if the given number is greater than or equal to the minimum limit.\n *\n */\n assertMin(value, limit)\n {\n return parseFloat(value) >= limit;\n }\n\n /**\n * Determine if the given value string length is less than or equal to the maximum limit.\n *\n */\n assertMaxLength(value, limit)\n {\n return typeof value === 'string' ? value.length <= limit : false;\n }\n\n /**\n * Determine if the given value string length is greater than or equal to the minimum limit.\n *\n */\n assertMinLength(value, limit)\n {\n return typeof value === 'string' ? value.length >= limit : false;\n }\n\n /**\n * Determine if the given value is not within the given array of options.\n *\n */\n assertNotIn(value, options)\n {\n return ! this.assertIn(value, options);\n }\n\n /**\n * Determine if the given value is numeric (an integer or a float).\n *\n */\n assertNumeric(value)\n {\n return ! isNaN(parseFloat(value)) && isFinite(value);\n }\n\n /**\n * Determine if the given value is optional.\n *\n */\n assertOptional(value)\n {\n return [null, undefined, ''].includes(value);\n }\n\n /**\n * Determine if the given value satisifies the given regular expression.\n *\n */\n assertRegexMatch(value, expression)\n {\n return new RegExp(expression).test(String(value));\n }\n\n /**\n * Determine if the given value is present.\n *\n */\n assertRequired(value)\n {\n return ! this.assertOptional(value);\n }\n\n /**\n * Determine if the given value is the same as another given value.\n *\n */\n assertSame(value, same)\n {\n return value == same;\n }\n\n /**\n * Determine if the given value starts with another given value.\n *\n */\n assertStartsWith(value, sub)\n {\n return this.assertString(value) && value.startsWith(sub);\n }\n\n /**\n * Determine if the given value is a string.\n *\n */\n assertString(value)\n {\n return typeof value === 'string';\n }\n\n /**\n * Determine if the given value is truthy.\n *\n */\n assertTruthy(value)\n {\n return [1, '1', true, 'true'].includes(value);\n }\n\n /**\n * Determine if the given value is a valid URL.\n *\n */\n assertUrl(value)\n {\n let regex = \"^(https?:\\\\/\\\\/)?((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|((\\\\d{1,3}\\\\.){3}\\\\d{1,3}))(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*(\\\\?[;&a-z\\\\d%_.~+=-]*)?(\\\\#[-a-z\\\\d_]*)?$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Determine if the given value is a valid UUID.\n *\n */\n assertUuid(value)\n {\n let regex = \"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$\";\n\n return new RegExp(regex).test(String(value).toLowerCase());\n }\n\n /**\n * Attach a custom validation rule to the library.\n *\n */\n rule(name, closure)\n {\n Iodine.prototype[`assert${this._title(name)}`] = closure;\n }\n\n /**\n * Replace the default error messages with a new set.\n *\n */\n setErrorMessages(messages)\n {\n this.messages = messages;\n }\n\n /**\n * Add or replace an error message.\n *\n */\n setErrorMessage(key, message)\n {\n this.messages[key] = message;\n }\n\n /**\n * Replace the default locale with a new value.\n *\n */\n setLocale(locale)\n {\n this.locale = locale;\n }\n\n /**\n * Replace the default field name with a new value.\n *\n */\n setDefaultFieldName(fieldName)\n {\n this.default_field_name = fieldName;\n }\n}\n\n/**\n * Create an instance of the library.\n *\n */\nif (typeof window !== 'undefined') {\n window.Iodine = new Iodine();\n}\n"],"names":["Iodine","constructor","this","locale","undefined","messages","after","afterOrEqual","array","before","beforeOrEqual","boolean","date","different","endsWith","email","falsy","in","integer","json","max","min","maxLength","minLength","notIn","numeric","optional","regexMatch","required","same","startsWith","string","truthy","url","uuid","_compare","first","second","type","equals","assertDate","assertInteger","getTime","_error","rule","args","param","field","chunks","split","key","shift","join","includes","Date","parseInt","toLocaleTimeString","year","month","day","hour","minute","hour12","message","replace","default_field_name","_missing","valid","error","_prepare","value","rules","length","assertOptional","filter","map","_title","slice","toUpperCase","_validate","errors","index","apply","assert","values","schema","Array","isArray","keys","Object","result","fields","i","hasOwnProperty","assertAfter","assertAfterOrEqual","assertArray","assertBefore","assertBeforeOrEqual","assertBoolean","prototype","toString","call","isNaN","assertDifferent","assertEndsWith","sub","assertString","assertEmail","RegExp","test","String","toLowerCase","assertFalsy","assertIn","options","Number","isInteger","assertJson","JSON","parse","e","assertMax","limit","parseFloat","assertMin","assertMaxLength","assertMinLength","assertNotIn","assertNumeric","isFinite","assertRegexMatch","expression","assertRequired","assertSame","assertStartsWith","assertTruthy","assertUrl","assertUuid","name","closure","setErrorMessages","setErrorMessage","setLocale","setDefaultFieldName","fieldName","window"],"mappings":"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"} -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | * 4 | */ 5 | import Iodine from '../src/iodine'; 6 | 7 | /** 8 | * Reset the library after each test. 9 | * 10 | */ 11 | afterEach(() => 12 | { 13 | window.Iodine = new Iodine(); 14 | }); 15 | 16 | /** 17 | * Confirm that the 'assertAfter' method works correctly. 18 | * 19 | */ 20 | test('after date values', () => 21 | { 22 | let year = new Date().getFullYear(); 23 | 24 | expect(window.Iodine.assertAfter(new Date(year + 1, 12, 18), new Date(year, 12, 18))).toBe(true); 25 | expect(window.Iodine.assertAfter(new Date(year + 1, 12, 17), Date.now())).toBe(true); 26 | expect(window.Iodine.assertAfter(new Date(`December 18, ${year + 1} 03:24:00`), new Date(year, 12, 18))).toBe(true); 27 | expect(window.Iodine.assertAfter(new Date(year - 1, 12, 17), Date.now())).toBe(false); 28 | expect(window.Iodine.assertAfter(new Date(year - 1, 12, 17), 'now')).toBe(false); 29 | expect(window.Iodine.assertAfter('now', new Date(year - 1, 12, 17))).toBe(false); 30 | expect(window.Iodine.assertAfter('now', 'now')).toBe(false); 31 | }); 32 | 33 | /** 34 | * Confirm that the 'assertAfterOrEqual' method works correctly. 35 | * 36 | */ 37 | test('after or equal date values', () => 38 | { 39 | let year = new Date().getFullYear(); 40 | 41 | expect(window.Iodine.assertAfterOrEqual(new Date(year + 1, 12, 18), new Date(year, 12, 18))).toBe(true); 42 | expect(window.Iodine.assertAfterOrEqual(new Date(year + 1, 12, 17), Date.now())).toBe(true); 43 | expect(window.Iodine.assertAfterOrEqual(new Date(`December 18, ${year + 1} 03:24:00`), new Date(year, 12, 18))).toBe(true); 44 | expect(window.Iodine.assertAfterOrEqual(new Date(year, 12, 18), new Date(year, 12, 18))).toBe(true); 45 | expect(window.Iodine.assertAfterOrEqual(new Date(year - 1, 12, 17), Date.now())).toBe(false); 46 | expect(window.Iodine.assertAfterOrEqual(new Date(year - 1, 12, 17), 'now')).toBe(false); 47 | expect(window.Iodine.assertAfterOrEqual('now', new Date(year - 1, 12, 17))).toBe(false); 48 | expect(window.Iodine.assertAfterOrEqual('now', 'now')).toBe(false); 49 | }); 50 | 51 | /** 52 | * Confirm that the 'assertBefore' method works correctly. 53 | * 54 | */ 55 | test('before date values', () => 56 | { 57 | let year = new Date().getFullYear(); 58 | 59 | expect(window.Iodine.assertBefore(new Date(year - 1, 12, 18), new Date(year, 12, 18))).toBe(true); 60 | expect(window.Iodine.assertBefore(new Date(year - 1, 12, 17), Date.now())).toBe(true); 61 | expect(window.Iodine.assertBefore(new Date(`December 18, ${year - 1} 03:24:00`), new Date(year, 12, 18))).toBe(true); 62 | expect(window.Iodine.assertBefore(new Date(year + 1, 12, 17), Date.now())).toBe(false); 63 | expect(window.Iodine.assertBefore(new Date(year + 1, 12, 17), 'now')).toBe(false); 64 | expect(window.Iodine.assertBefore('now', new Date(year + 1, 12, 17))).toBe(false); 65 | expect(window.Iodine.assertBefore('now', 'now')).toBe(false); 66 | }); 67 | 68 | /** 69 | * Confirm that the 'assertBeforeOrEqual' method works correctly. 70 | * 71 | */ 72 | test('before or equal date values', () => 73 | { 74 | let year = new Date().getFullYear(); 75 | 76 | expect(window.Iodine.assertBeforeOrEqual(new Date(year - 1, 12, 18), new Date(year, 12, 18))).toBe(true); 77 | expect(window.Iodine.assertBeforeOrEqual(new Date(year - 1, 12, 17), Date.now())).toBe(true); 78 | expect(window.Iodine.assertBeforeOrEqual(new Date(`December 18, ${year - 1} 03:24:00`), new Date(year, 12, 18))).toBe(true); 79 | expect(window.Iodine.assertBeforeOrEqual(new Date(year, 12, 18), new Date(year, 12, 18))).toBe(true); 80 | expect(window.Iodine.assertBeforeOrEqual(new Date(year + 1, 12, 17), Date.now())).toBe(false); 81 | expect(window.Iodine.assertBeforeOrEqual(new Date(year + 1, 12, 17), 'now')).toBe(false); 82 | expect(window.Iodine.assertBeforeOrEqual('now', new Date(year + 1, 12, 17))).toBe(false); 83 | expect(window.Iodine.assertBeforeOrEqual('now', 'now')).toBe(false); 84 | }); 85 | 86 | /** 87 | * Confirm that the 'assertArray' method works correctly. 88 | * 89 | */ 90 | test('array values', () => 91 | { 92 | expect(window.Iodine.assertArray([1, 2, 3])).toBe(true); 93 | expect(window.Iodine.assertArray(['1', '2', '3'])).toBe(true); 94 | expect(window.Iodine.assertArray(1)).toBe(false); 95 | expect(window.Iodine.assertArray('1')).toBe(false); 96 | }); 97 | 98 | /** 99 | * Confirm that the 'assertBoolean' method works correctly. 100 | * 101 | */ 102 | test('boolean values', () => 103 | { 104 | expect(window.Iodine.assertBoolean(true)).toBe(true); 105 | expect(window.Iodine.assertBoolean(false)).toBe(true); 106 | expect(window.Iodine.assertBoolean(1)).toBe(false); 107 | expect(window.Iodine.assertBoolean('1')).toBe(false); 108 | }); 109 | 110 | /** 111 | * Confirm that the 'assertDate' method works correctly. 112 | * 113 | */ 114 | test('date values', () => 115 | { 116 | expect(window.Iodine.assertDate(new Date(1995, 12, 17))).toBe(true); 117 | expect(window.Iodine.assertDate(new Date('December 17, 1995 03:24:00'))).toBe(true); 118 | expect(window.Iodine.assertDate(1)).toBe(false); 119 | expect(window.Iodine.assertDate('1')).toBe(false); 120 | }); 121 | 122 | /** 123 | * Confirm that the 'assertDifferent' method works correctly. 124 | * 125 | */ 126 | test('different values', () => 127 | { 128 | expect(window.Iodine.assertDifferent(1, 2)).toBe(true); 129 | expect(window.Iodine.assertDifferent('x', 'y')).toBe(true); 130 | expect(window.Iodine.assertDifferent(1, 1)).toBe(false); 131 | expect(window.Iodine.assertDifferent('x', 'x')).toBe(false); 132 | }); 133 | 134 | /** 135 | * Confirm that the 'assertEndsWith' method works correctly. 136 | * 137 | */ 138 | test('a value ends with another value', () => 139 | { 140 | expect(window.Iodine.assertEndsWith('hello world', 'world')).toBe(true); 141 | expect(window.Iodine.assertEndsWith('hello universe', 'world')).toBe(false); 142 | }); 143 | 144 | /** 145 | * Confirm that the 'assertEmail' method works correctly. 146 | * 147 | */ 148 | test('email values', () => 149 | { 150 | expect(window.Iodine.assertEmail('john@example.com')).toBe(true); 151 | expect(window.Iodine.assertEmail('m@i.com')).toBe(true); 152 | expect(window.Iodine.assertEmail('m@i.de')).toBe(true); 153 | expect(window.Iodine.assertEmail('m@i.co.uk')).toBe(true); 154 | expect(window.Iodine.assertEmail('😃@i.com')).toBe(false); 155 | expect(window.Iodine.assertEmail('')).toBe(false); 156 | expect(window.Iodine.assertEmail('john@example.com ')).toBe(false); 157 | expect(window.Iodine.assertEmail('john@example.com extra')).toBe(false); 158 | expect(window.Iodine.assertEmail('45454.com')).toBe(false); 159 | expect(window.Iodine.assertEmail('sdfsf@')).toBe(false); 160 | }); 161 | 162 | /** 163 | * Confirm that the 'assertFalsy' method works correctly. 164 | * 165 | */ 166 | test('falsy values', () => 167 | { 168 | expect(window.Iodine.assertFalsy(false)).toBe(true); 169 | expect(window.Iodine.assertFalsy('false')).toBe(true); 170 | expect(window.Iodine.assertFalsy(0)).toBe(true); 171 | expect(window.Iodine.assertFalsy('0')).toBe(true); 172 | expect(window.Iodine.assertFalsy(true)).toBe(false); 173 | expect(window.Iodine.assertFalsy('true')).toBe(false); 174 | expect(window.Iodine.assertFalsy(1)).toBe(false); 175 | expect(window.Iodine.assertFalsy('1')).toBe(false); 176 | }); 177 | 178 | /** 179 | * Confirm that the 'assertIn' method works correctly. 180 | * 181 | */ 182 | test('in list values', () => 183 | { 184 | expect(window.Iodine.assertIn('a', 'a,b,c')).toBe(true); 185 | expect(window.Iodine.assertIn('a', ['a', 'b', 'c'])).toBe(true); 186 | expect(window.Iodine.assertIn('d', 'a,b,c')).toBe(false); 187 | expect(window.Iodine.assertIn('d', ['a', 'b', 'c'])).toBe(false); 188 | }); 189 | 190 | /** 191 | * Confirm that the 'assertInteger' method works correctly. 192 | * 193 | */ 194 | test('integer values', () => 195 | { 196 | expect(window.Iodine.assertInteger(1)).toBe(true); 197 | expect(window.Iodine.assertInteger(1.5)).toBe(false); 198 | expect(window.Iodine.assertInteger('1')).toBe(false); 199 | }); 200 | 201 | /** 202 | * Confirm that the 'assertJson' method works correctly. 203 | * 204 | */ 205 | test('json values', () => 206 | { 207 | expect(window.Iodine.assertJson('{}')).toBe(true); 208 | expect(window.Iodine.assertJson('{"a" : 3}')).toBe(true); 209 | expect(window.Iodine.assertJson('1')).toBe(false); 210 | expect(window.Iodine.assertJson('')).toBe(false); 211 | }); 212 | 213 | /** 214 | * Confirm that the 'assertMax' method works correctly. 215 | * 216 | */ 217 | test('maximum numeric values', () => 218 | { 219 | expect(window.Iodine.assertMax(1, 5)).toBe(true); 220 | expect(window.Iodine.assertMax(5, 5)).toBe(true); 221 | expect(window.Iodine.assertMax(6, 5)).toBe(false); 222 | expect(window.Iodine.assertMax('1', 5)).toBe(true); 223 | expect(window.Iodine.assertMax('5', 5)).toBe(true); 224 | expect(window.Iodine.assertMax('6', 5)).toBe(false); 225 | }); 226 | 227 | /** 228 | * Confirm that the 'assertMin' method works correctly. 229 | * 230 | */ 231 | test('minimum numeric values', () => 232 | { 233 | expect(window.Iodine.assertMin(6, 5)).toBe(true); 234 | expect(window.Iodine.assertMin(5, 5)).toBe(true); 235 | expect(window.Iodine.assertMin(4, 5)).toBe(false); 236 | expect(window.Iodine.assertMin('6', 5)).toBe(true); 237 | expect(window.Iodine.assertMin('5', 5)).toBe(true); 238 | expect(window.Iodine.assertMin('4', 5)).toBe(false); 239 | }); 240 | 241 | /** 242 | * Confirm that the 'assertMaxLength' method works correctly. 243 | * 244 | */ 245 | test('maximum string length', () => 246 | { 247 | expect(window.Iodine.assertMaxLength(1, 5)).toBe(false); 248 | expect(window.Iodine.assertMaxLength(5, 5)).toBe(false); 249 | expect(window.Iodine.assertMaxLength(6, 5)).toBe(false); 250 | expect(window.Iodine.assertMaxLength('1', 5)).toBe(true); 251 | expect(window.Iodine.assertMaxLength('12345', 5)).toBe(true); 252 | expect(window.Iodine.assertMaxLength('123456', 5)).toBe(false); 253 | }); 254 | 255 | /** 256 | * Confirm that the 'assertMinLength' method works correctly. 257 | * 258 | */ 259 | test('minimum string length', () => 260 | { 261 | expect(window.Iodine.assertMinLength(6, 5)).toBe(false); 262 | expect(window.Iodine.assertMinLength(5, 5)).toBe(false); 263 | expect(window.Iodine.assertMinLength(4, 5)).toBe(false); 264 | expect(window.Iodine.assertMinLength('123456', 5)).toBe(true); 265 | expect(window.Iodine.assertMinLength('12345', 5)).toBe(true); 266 | expect(window.Iodine.assertMinLength('1234', 5)).toBe(false); 267 | }); 268 | 269 | /** 270 | * Confirm that the 'assertNotIn' method works correctly. 271 | * 272 | */ 273 | test('not in list values', () => 274 | { 275 | expect(window.Iodine.assertNotIn('d', 'a,b,c')).toBe(true); 276 | expect(window.Iodine.assertNotIn('d', ['a', 'b', 'c'])).toBe(true); 277 | expect(window.Iodine.assertNotIn('a', 'a,b,c')).toBe(false); 278 | expect(window.Iodine.assertNotIn('a', ['a', 'b', 'c'])).toBe(false); 279 | }); 280 | 281 | /** 282 | * Confirm that the 'assertNumeric' method works correctly. 283 | * 284 | */ 285 | test('numeric values', () => 286 | { 287 | expect(window.Iodine.assertNumeric(1)).toBe(true); 288 | expect(window.Iodine.assertNumeric(2.5)).toBe(true); 289 | expect(window.Iodine.assertNumeric(3.45)).toBe(true); 290 | expect(window.Iodine.assertNumeric('13')).toBe(true); 291 | expect(window.Iodine.assertNumeric('14.55')).toBe(true); 292 | expect(window.Iodine.assertNumeric('17.0')).toBe(true); 293 | expect(window.Iodine.assertNumeric('abc')).toBe(false); 294 | }); 295 | 296 | /** 297 | * Confirm that the 'assertOptional' method works correctly. 298 | * 299 | */ 300 | test('optional values', () => 301 | { 302 | expect(window.Iodine.assertOptional(1)).toBe(false); 303 | expect(window.Iodine.assertOptional('1')).toBe(false); 304 | expect(window.Iodine.assertOptional('')).toBe(true); 305 | expect(window.Iodine.assertOptional(null)).toBe(true); 306 | expect(window.Iodine.assertOptional(undefined)).toBe(true); 307 | }); 308 | 309 | /** 310 | * Confirm that the 'assertRegexMatch' method works correctly. 311 | * 312 | */ 313 | test('regular expression values', () => 314 | { 315 | expect(window.Iodine.assertRegexMatch('P54655465', "^P\\d{3,}$")).toBe(true); 316 | expect(window.Iodine.assertRegexMatch('1234', "^\\S+@\\S+[\\.][0-9a-z]+$")).toBe(false); 317 | expect(window.Iodine.assertRegexMatch('john@example.com', "^\\S+@\\S+[\\.][0-9a-z]+$")).toBe(true); 318 | }); 319 | 320 | /** 321 | * Confirm that the 'assertRequired' method works correctly. 322 | * 323 | */ 324 | test('required values', () => 325 | { 326 | expect(window.Iodine.assertRequired(1)).toBe(true); 327 | expect(window.Iodine.assertRequired('1')).toBe(true); 328 | expect(window.Iodine.assertRequired('')).toBe(false); 329 | expect(window.Iodine.assertRequired(null)).toBe(false); 330 | expect(window.Iodine.assertRequired(undefined)).toBe(false); 331 | }); 332 | 333 | /** 334 | * Confirm that the 'assertSame' method works correctly. 335 | * 336 | */ 337 | test('same values', () => 338 | { 339 | expect(window.Iodine.assertSame(1, 1)).toBe(true); 340 | expect(window.Iodine.assertSame('x', 'x')).toBe(true); 341 | expect(window.Iodine.assertSame(1, 2)).toBe(false); 342 | expect(window.Iodine.assertSame('x', 'y')).toBe(false); 343 | }); 344 | 345 | /** 346 | * Confirm that the 'assertStartsWith' method works correctly. 347 | * 348 | */ 349 | test('a value starts with another value', () => 350 | { 351 | expect(window.Iodine.assertStartsWith('bye world', 'hello')).toBe(false); 352 | expect(window.Iodine.assertStartsWith('hello world', 'hello')).toBe(true); 353 | }); 354 | 355 | /** 356 | * Confirm that the 'assertString' method works correctly. 357 | * 358 | */ 359 | test('string values', () => 360 | { 361 | expect(window.Iodine.assertString(1)).toBe(false); 362 | expect(window.Iodine.assertString('1')).toBe(true); 363 | }); 364 | 365 | /** 366 | * Confirm that the 'assertTruthy' method works correctly. 367 | * 368 | */ 369 | test('truthy values', () => 370 | { 371 | expect(window.Iodine.assertTruthy(true)).toBe(true); 372 | expect(window.Iodine.assertTruthy('true')).toBe(true); 373 | expect(window.Iodine.assertTruthy(1)).toBe(true); 374 | expect(window.Iodine.assertTruthy('1')).toBe(true); 375 | expect(window.Iodine.assertTruthy(false)).toBe(false); 376 | expect(window.Iodine.assertTruthy('false')).toBe(false); 377 | expect(window.Iodine.assertTruthy(0)).toBe(false); 378 | expect(window.Iodine.assertTruthy('0')).toBe(false); 379 | }); 380 | 381 | /** 382 | * Confirm that the 'assertUrl' method works correctly. 383 | * 384 | */ 385 | test('url values', () => 386 | { 387 | expect(window.Iodine.assertUrl('1234')).toBe(false); 388 | expect(window.Iodine.assertUrl('http://www.google.com')).toBe(true); 389 | }); 390 | 391 | /** 392 | * Confirm that the 'assertUuid' method works correctly. 393 | * 394 | */ 395 | test('UUID values', () => 396 | { 397 | expect(window.Iodine.assertUuid('9034dfa4-49d9-4e3f-9c6d-bc6a0e2233d1')).toBe(true); 398 | expect(window.Iodine.assertUuid('XAZLYYZeNu75xkicYcPoBWhAW0AX2HRlbqbK')).toBe(false); 399 | }); 400 | 401 | /** 402 | * Confirm that the '_error' method works correctly. 403 | * 404 | */ 405 | test('it retrieves formatted error messages for rules', () => 406 | { 407 | let time = Date.UTC(2020, 4, 2, 10, 17, 0); 408 | 409 | window.Iodine.setLocale('en-US'); 410 | 411 | let hour = new Date(parseInt(time)).getHours().toString().padStart(2, '0'); 412 | 413 | expect(window.Iodine._error('array')).toBe('Value must be an array'); 414 | expect(window.Iodine._error('endsWith')).toBe(`Value must end with '[PARAM]'`); 415 | expect(window.Iodine._error('endsWith:world')).toBe(`Value must end with 'world'`); 416 | expect(window.Iodine._error('endsWith', 'world')).toBe(`Value must end with 'world'`); 417 | expect(window.Iodine._error('endsWith', { field : 'Song title' })).toBe(`Song title must end with '[PARAM]'`); 418 | expect(window.Iodine._error('endsWith:world', { field : 'Song title' })).toBe(`Song title must end with 'world'`); 419 | expect(window.Iodine._error('endsWith', { field : 'Song title', param : 'world'})).toBe(`Song title must end with 'world'`); 420 | expect(window.Iodine._error('endsWith', { param : 'world' })).toBe(`Value must end with 'world'`); 421 | expect(window.Iodine._error(`after:${time}`)).toBe(`The date must be after: 'May 2, 2020, ${hour}:17'`); 422 | expect(window.Iodine._error(`after`, time)).toBe(`The date must be after: 'May 2, 2020, ${hour}:17'`); 423 | }); 424 | 425 | /** 426 | * Confirm that the default error messages can be replaced. 427 | * 428 | */ 429 | test('it can replace the default error messages', () => 430 | { 431 | window.Iodine.setErrorMessages({ 432 | array : 'Hello world', 433 | endsWith : 'Hello, [PARAM]', 434 | startsWith : "[FIELD]: [PARAM] says, 'hello'", 435 | }); 436 | 437 | expect(window.Iodine._error('array')).toBe('Hello world'); 438 | expect(window.Iodine._error('endsWith:John')).toBe('Hello, John'); 439 | expect(window.Iodine._error('endsWith', 'John')).toBe('Hello, John'); 440 | expect(window.Iodine._error('endsWith', { param : 'John' })).toBe('Hello, John'); 441 | expect(window.Iodine._error('endsWith', 'John')).toBe('Hello, John'); 442 | expect(window.Iodine._error('startsWith:Paul')).toBe(`Value: Paul says, 'hello'`); 443 | expect(window.Iodine._error('startsWith', 'Paul')).toBe(`Value: Paul says, 'hello'`); 444 | expect(window.Iodine._error('startsWith', { param : 'Paul' })).toBe(`Value: Paul says, 'hello'`); 445 | expect(window.Iodine._error('startsWith:Paul', { field : 'Name' })).toBe(`Name: Paul says, 'hello'`); 446 | expect(window.Iodine._error('startsWith', { field : 'Name', param : 'Paul' })).toBe(`Name: Paul says, 'hello'`); 447 | }); 448 | 449 | /** 450 | * Confirm the defualt field name can be replaced. 451 | * 452 | */ 453 | test('it can replace the default field name', () => 454 | { 455 | window.Iodine.setDefaultFieldName('Input'); 456 | 457 | expect(window.Iodine._error('array')).toBe('Input must be an array'); 458 | expect(window.Iodine._error('endsWith')).toBe(`Input must end with '[PARAM]'`); 459 | expect(window.Iodine._error('endsWith:world')).toBe(`Input must end with 'world'`); 460 | expect(window.Iodine._error('endsWith', { param : 'world' })).toBe(`Input must end with 'world'`); 461 | }); 462 | 463 | /** 464 | * Confirm that a single error message can be replaced. 465 | * 466 | */ 467 | test('it can replace a default error message', () => 468 | { 469 | const total = Object.keys(window.Iodine.messages).length; 470 | 471 | window.Iodine.setErrorMessage('email', 'Does not look like a valid email'); 472 | 473 | expect(window.Iodine._error('email')).toBe('Does not look like a valid email'); 474 | expect(Object.keys(window.Iodine.messages).length).toEqual(total); 475 | expect(window.Iodine._error('date')).toBe('Value must be a date'); 476 | }); 477 | 478 | /** 479 | * Confirm that a single error message can be added to the set. 480 | * 481 | */ 482 | test('it can add an error message to the set', () => 483 | { 484 | const total = Object.keys(window.Iodine.messages).length; 485 | 486 | window.Iodine.setErrorMessage('passwordConfirmation', 'Password confirmation needs to match'); 487 | 488 | expect(window.Iodine._error('passwordConfirmation')).toBe('Password confirmation needs to match'); 489 | expect(Object.keys(window.Iodine.messages).length).toEqual(total + 1); 490 | }); 491 | 492 | /** 493 | * Confirm that the 'assert' method works correctly. 494 | * 495 | */ 496 | test('it can validate a single item against multiple rules', () => 497 | { 498 | let pass = { 499 | valid : true, 500 | rule : '', 501 | error : '', 502 | }; 503 | 504 | let fail = { 505 | valid : false, 506 | rule : 'min:7', 507 | error : 'Value must be greater than or equal to 7', 508 | }; 509 | 510 | expect(window.Iodine.assert('5', ['required', 'string', 'min:1', 'max:5'])).toStrictEqual(pass); 511 | expect(window.Iodine.assert(5, ['required', 'integer', 'min:7', 'max:10'])).toStrictEqual(fail); 512 | expect(window.Iodine.assert(5, ['optional', 'integer', 'min:7', 'max:10'])).toStrictEqual(fail); 513 | expect(window.Iodine.assert('', ['optional', 'integer', 'min:7', 'max:10'])).toStrictEqual(pass); 514 | expect(window.Iodine.assert(null, ['optional', 'integer', 'min:7', 'max:10'])).toStrictEqual(pass); 515 | expect(window.Iodine.assert(undefined, ['optional', 'integer', 'min:7', 'max:10'])).toStrictEqual(pass); 516 | }); 517 | 518 | /** 519 | * Confirm that the 'assert' method works correctly. 520 | * 521 | */ 522 | test('it can validate multiple items against multiple rules', () => 523 | { 524 | let result_1 = { 525 | valid : false, 526 | fields : { 527 | website : { 528 | valid : false, 529 | rule : 'None', 530 | error : 'Rules exist, but no value was provided to check', 531 | }, 532 | ping : { 533 | valid : false, 534 | rule : 'None', 535 | error : 'Rules exist, but no value was provided to check', 536 | }, 537 | } 538 | }; 539 | 540 | let result_2 = { 541 | valid : true, 542 | fields : { 543 | email : { 544 | valid : true, 545 | rule : '', 546 | error : '', 547 | }, 548 | password : { 549 | valid : true, 550 | rule : '', 551 | error : '', 552 | }, 553 | name : { 554 | valid : true, 555 | rule : '', 556 | error : '', 557 | }, 558 | } 559 | }; 560 | 561 | let result_3 = { 562 | valid : true, 563 | fields : { 564 | website : { 565 | valid : true, 566 | rule : '', 567 | error : '', 568 | }, 569 | } 570 | }; 571 | 572 | let result_4 = { 573 | valid : false, 574 | fields : { 575 | website : { 576 | valid : true, 577 | rule : '', 578 | error : '', 579 | }, 580 | ping : { 581 | valid : false, 582 | rule : 'integer', 583 | error : 'Value must be an integer', 584 | }, 585 | } 586 | }; 587 | 588 | expect(window.Iodine.assert({ }, { website : ['required', 'url'], ping : ['required' , 'integer'] })).toStrictEqual(result_1); 589 | expect(window.Iodine.assert({ email : 'welcome@to.iodine', password : 'abcdefgh', name : 'John Doe' }, { email : ['required', 'email'], password : ['required', 'minLength:6'], name : ['required', 'minLength:3'] })).toStrictEqual(result_2); 590 | expect(window.Iodine.assert({ website : 'https://iodine.is', ping : 'ninety' }, { website : ['required', 'url'] })).toStrictEqual(result_3); 591 | expect(window.Iodine.assert({ website : 'https://iodine.io', ping : 'ninety' }, { website : ['required', 'url'], ping : ['required', 'integer'] })).toStrictEqual(result_4); 592 | }); 593 | 594 | /** 595 | * Confirm that the 'assert' method can handle rules that contain semicolons. 596 | * 597 | */ 598 | test('parameter that contains semicolon(":")', () => 599 | { 600 | let pass = { 601 | valid : true, 602 | rule : '', 603 | error : '', 604 | }; 605 | 606 | let fail = { 607 | valid : false, 608 | rule : "regexMatch:^:\\w$", 609 | error : "Value must satisify the regular expression: ^:\\w$", 610 | }; 611 | 612 | expect(window.Iodine.assert(':b', ['required', "regexMatch:^:\\w$"])).toStrictEqual(pass); 613 | expect(window.Iodine.assert('a:b', ['required', "regexMatch:^:\\w$"])).toStrictEqual(fail); 614 | expect(window.Iodine.assert(':b', ['required', "regexMatch:^:\\w$"])).toStrictEqual(pass); 615 | expect(window.Iodine.assert('a:b', ['required', "regexMatch:^:\\w$"])).toStrictEqual(fail); 616 | }); 617 | 618 | /** 619 | * Confirm that the 'assert' method can accept rules as objects. 620 | * 621 | */ 622 | test('it can accept rules as objects', () => 623 | { 624 | let pass = { 625 | valid : true, 626 | rule : '', 627 | error : '', 628 | }; 629 | 630 | let fail_1 = { 631 | valid : false, 632 | rule : 'min:7', 633 | error : 'Value must be greater than or equal to 7' 634 | }; 635 | 636 | let fail_2 = { 637 | valid : false, 638 | rule : 'in:7,8,9', 639 | error : 'Value must be one of the following options: 7,8,9' 640 | }; 641 | 642 | let fail_3 = { 643 | valid : false, 644 | fields : { 645 | hello : { error : '', rule : '', valid : true }, 646 | not_valid : { error : 'Value must be greater than or equal to 12', rule : 'min:12', valid : false }, 647 | }, 648 | }; 649 | 650 | let rules = { 651 | hello : ['required', { rule : 'string' }], 652 | not_valid : ['required', { rule : 'min', param : 12 }] 653 | }; 654 | 655 | expect(window.Iodine.assert(8, ['required', { rule : 'min', param : 7 }, 'max:10'])).toStrictEqual(pass); 656 | expect(window.Iodine.assert(2, ['required', { rule : 'min', param : 7 }, 'max:10'])).toStrictEqual(fail_1); 657 | expect(window.Iodine.assert(5, ['required', { rule : 'in', param : [ 7, 8, 9 ] }, 'max:10'])).toStrictEqual(fail_2); 658 | expect(window.Iodine.assert(8, ['required', { rule : 'in', param : [ 7, 8, 9 ] }, 'max:10'])).toStrictEqual(pass); 659 | expect(window.Iodine.assert({ hello : 'string', not_valid : 10 }, rules)).toStrictEqual(fail_3); 660 | }); 661 | 662 | /** 663 | * Confirm that the 'rule' method works correctly for simple rules. 664 | * 665 | */ 666 | test('it can add simple custom rules', () => 667 | { 668 | window.Iodine.rule('lowerCase', (value) => value === value.toLowerCase()); 669 | 670 | window.Iodine.setErrorMessages({ lowerCase : 'Value must be in lower case' }); 671 | 672 | let pass = { 673 | valid : true, 674 | rule : '', 675 | error : '', 676 | }; 677 | 678 | let fail = { 679 | valid : false, 680 | rule : 'lowerCase', 681 | error : 'Value must be in lower case', 682 | }; 683 | 684 | expect(window.Iodine.assertLowerCase('hello')).toBe(true); 685 | expect(window.Iodine.assertLowerCase('Hello')).toBe(false); 686 | expect(window.Iodine.assertLowerCase('HELLO')).toBe(false); 687 | expect(window.Iodine.assert('hello', ['required', 'lowerCase'])).toStrictEqual(pass); 688 | expect(window.Iodine.assert('Hello', ['required', 'lowerCase'])).toStrictEqual(fail); 689 | expect(window.Iodine.assert('HELLO', ['required', 'lowerCase'])).toStrictEqual(fail); 690 | expect(window.Iodine._error('lowerCase')).toBe('Value must be in lower case'); 691 | }); 692 | 693 | /** 694 | * Confirm that the 'rule' method works correctly for advanced rules. 695 | * 696 | */ 697 | test('it can add advanced custom rules', () => 698 | { 699 | window.Iodine.rule('equals', (value, param) => value == param); 700 | 701 | window.Iodine.setErrorMessages({ equals : "Value must be equal to '[PARAM]'" }); 702 | 703 | let pass = { 704 | valid : true, 705 | rule : '', 706 | error : '', 707 | }; 708 | 709 | let fail_1 = { 710 | valid : false, 711 | rule : 'equals:2', 712 | error : "Value must be equal to '2'", 713 | }; 714 | 715 | let fail_2 = { 716 | valid : false, 717 | rule : 'equals:3', 718 | error : "Value must be equal to '3'", 719 | }; 720 | 721 | expect(window.Iodine.assertEquals(1, 1)).toBe(true); 722 | expect(window.Iodine.assertEquals(1, 2)).toBe(false); 723 | expect(window.Iodine.assertEquals(1, 3)).toBe(false); 724 | expect(window.Iodine.assert(1, ['required', 'equals:1'])).toStrictEqual(pass); 725 | expect(window.Iodine.assert(1, ['required', 'equals:2'])).toStrictEqual(fail_1); 726 | expect(window.Iodine.assert(1, ['required', 'equals:3'])).toStrictEqual(fail_2); 727 | expect(window.Iodine._error('equals:2')).toBe("Value must be equal to '2'"); 728 | expect(window.Iodine._error('equals', 2)).toBe("Value must be equal to '2'"); 729 | }); 730 | 731 | /** 732 | * Confirm that the 'assert' method works correctly with custom errors by field. 733 | * 734 | */ 735 | test('it supports custom errors by field', () => 736 | { 737 | let fail = { 738 | valid : false, 739 | rule : 'required', 740 | error : 'The "Hello" field is required.', 741 | }; 742 | 743 | let fields = { 744 | name: '', 745 | }; 746 | 747 | let rules = { 748 | name: ['required'] 749 | }; 750 | 751 | let errors = { 752 | name: { 753 | required: 'The "Name" field must be present.' 754 | } 755 | }; 756 | 757 | let failMultiple = { 758 | valid : false, 759 | fields : { 760 | name : { 761 | valid : false, 762 | rule : 'required', 763 | error : 'The "Name" field must be present.', 764 | }, 765 | }, 766 | }; 767 | 768 | expect(window.Iodine.assert('', ['required'], { 'required': 'The "Hello" field is required.' })).toStrictEqual(fail); 769 | expect(window.Iodine.assert(fields, rules, errors)).toStrictEqual(failMultiple); 770 | }); 771 | --------------------------------------------------------------------------------