├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── blueprint ├── base │ ├── cls.js │ └── embedCls.js ├── functions │ ├── deserialize.js │ ├── normalize.js │ ├── serialize.js │ └── utils.js ├── index.js └── utils.js ├── faker └── index.js ├── index.d.ts ├── index.js ├── logos ├── logo.png ├── logo_200.png ├── logo_300.png └── logo_400.png ├── package-lock.json ├── package.json ├── spec ├── blueprint.js └── spec.md ├── test ├── able_to_process_class_instance.test.js ├── allow_unknown_properties_extend.test.js ├── any_serializer_deserializer.test.js ├── any_type.test.js ├── boolean_serializer_deserializer.test.js ├── boolean_type.test.js ├── boolean_validation.test.js ├── conditions.test.js ├── conditions_serializer_deserializer.test.js ├── datetime_serializer_deserializer.test.js ├── datetime_type.test.js ├── datetime_validation.test.js ├── execute_once_for_transform_in_serialization.test.js ├── extend.test.js ├── extend_on_array.test.js ├── float_serializer_deserializer.test.js ├── float_type.test.js ├── float_validation.test.js ├── handler.test.js ├── integer_serializer_deserializer.test.js ├── integer_type.test.js ├── integer_validation.test.js ├── number_serializer_deserializer.test.js ├── number_type.test.js ├── number_validation.test.js ├── options.test.js ├── options_validation.test.js ├── string_serializer_deserializer.test.js ├── string_type.test.js ├── string_validation.test.js ├── transform_serializer_deserializer.test.js └── transform_type.test.js ├── types ├── any.js ├── boolean.js ├── conditions.js ├── datetime.js ├── detector.js ├── float.js ├── index.js ├── integer.js ├── number.js ├── options.js ├── string.js ├── transform.js └── utils.js └── validator ├── errors ├── PodengError.js └── index.js ├── index.js └── utils.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "jest": true 5 | }, 6 | "extends": "standard", 7 | "rules": { 8 | "semi": [ 9 | "error", 10 | "always" 11 | ], 12 | "quotes": [ 13 | "error", 14 | "single" 15 | ], 16 | "max-len": [ 17 | "error", 18 | { 19 | "code": 120 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Created by https://www.gitignore.io/api/node,webstorm,visualstudiocode 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (http://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Typescript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | 66 | ### VisualStudioCode ### 67 | .vscode/* 68 | !.vscode/settings.json 69 | !.vscode/tasks.json 70 | !.vscode/launch.json 71 | !.vscode/extensions.json 72 | .history 73 | 74 | ### WebStorm ### 75 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 76 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 77 | 78 | # User-specific stuff: 79 | .idea/**/workspace.xml 80 | .idea/**/tasks.xml 81 | .idea/dictionaries 82 | 83 | # Sensitive or high-churn files: 84 | .idea/**/dataSources/ 85 | .idea/**/dataSources.ids 86 | .idea/**/dataSources.xml 87 | .idea/**/dataSources.local.xml 88 | .idea/**/sqlDataSources.xml 89 | .idea/**/dynamic.xml 90 | .idea/**/uiDesigner.xml 91 | 92 | # Gradle: 93 | .idea/**/gradle.xml 94 | .idea/**/libraries 95 | 96 | # CMake 97 | cmake-build-debug/ 98 | 99 | # Mongo Explorer plugin: 100 | .idea/**/mongoSettings.xml 101 | 102 | ## File-based project format: 103 | *.iws 104 | 105 | ## Plugin-specific files: 106 | 107 | # IntelliJ 108 | /out/ 109 | 110 | # mpeltonen/sbt-idea plugin 111 | .idea_modules/ 112 | 113 | # JIRA plugin 114 | atlassian-ide-plugin.xml 115 | 116 | # Cursive Clojure plugin 117 | .idea/replstate.xml 118 | 119 | # Ruby plugin and RubyMine 120 | /.rakeTasks 121 | 122 | # Crashlytics plugin (for Android Studio and IntelliJ) 123 | com_crashlytics_export_strings.xml 124 | crashlytics.properties 125 | crashlytics-build.properties 126 | fabric.properties 127 | 128 | ### WebStorm Patch ### 129 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 130 | 131 | # *.iml 132 | # modules.xml 133 | # .idea/misc.xml 134 | # *.ipr 135 | 136 | # Sonarlint plugin 137 | .idea/sonarlint 138 | 139 | 140 | # End of https://www.gitignore.io/api/node,webstorm,visualstudiocode 141 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "eslint.enable": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Aditya Kresna 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PodengJS 2 | 3 | Simple JSON value normalization to make everything gone right. 4 | 5 | This small library helps you to make JSON property value to follow the rules, no matter how strange the value is, you could still normalize it into right type(s). 6 | 7 | **Update:** I have a limited amount of time to writing documentation on this module, for faster understanding (and example) please take a look at test files under [**test**](https://github.com/slaveofcode/podeng/tree/master/test) folder, it has a lot of code examples to get you understand the idea of this module. 8 | 9 | 10 | 11 | ## Terms 12 | 1. **Normalization**, convert json value to follow rules without changing any key/property name 13 | 2. **Serialization**, normalization-like but with different key name 14 | 3. **Deserialization**, reversed-like-serialization but with the different supplied key/property name 15 | 4. **Validation**, validate json value if not matched with rules(also could work with serialization-deserialization) 16 | 17 | ## Example Cases 18 | 19 | 1. Parser-Serializer for your RESTful API 20 | 2. JSON value validator 21 | 3. JSON value normalization 22 | 4. Minimalist JSON Schema 23 | 5. Other... 24 | 25 | ## Installation 26 | 27 | Podeng requires a minimal node version >= 6.x 28 | 29 | > NPM 30 | ``` 31 | npm i podeng 32 | ``` 33 | 34 | > Yarn 35 | ``` 36 | yarn add podeng 37 | ``` 38 | 39 | ## Known Dependencies 40 | - lodash 41 | - moment 42 | - moment-timezone (for running test) 43 | - jest (for running test) 44 | 45 | ## Running the Test 46 | 47 | ``` 48 | >> npm i 49 | >> npm run test 50 | ``` 51 | 52 | ## Documentation 53 | 54 | ### Simple Example 55 | 56 | ``` 57 | const { blueprint, types } = require('podeng') 58 | 59 | const PersonSchema = blueprint.object({ 60 | name: types.string, 61 | age: types.integer, 62 | birthday: types.datetime({ dateOnly: true }) // ISO 8601 63 | }) 64 | 65 | const givenData = { 66 | name: 'Aditya Kresna', 67 | age: '27', 68 | birthday: '1991-06-18 00:05:00', 69 | address: 'Indonesia', 70 | hobby: ['Sleeping', 'Coding'] 71 | } 72 | 73 | const result = PersonSchema(givenData) 74 | // { 75 | // name: 'Aditya Kresna', 76 | // age: 27, 77 | // birthday: '1991-06-18' 78 | // } 79 | ``` 80 | 81 | ## Blueprint Object Options 82 | 83 | - **frozen**: Freeze the JSON result, so it cannot be modified 84 | - **giveWarning**: Enable warning when parsing (normalize-serialize-deserialize) got invalid value 85 | - **onError**: Object to determine action when error caught on parsing, this has 2 option handler, `onKey` and `onAll` 86 | - **onKey**: A function to execute while some key detected has an invalid value, this function will 2 supplied by parameter key of type and error details 87 | - **onAll**: A function to execute when all key detected has an invalid value, this function just supplied with one parameter, the error details 88 | - **throwOnError**: Throwing an exception while the error parse detected 89 | - **allowUnknownProperties**: This option will allow unknown properties on parsing 90 | 91 | > Blueprint Object Example 92 | 93 | ``` 94 | const Parser = blueprint.object( 95 | { 96 | key: types.bool, 97 | key2: types.bool(['Yes', 'True', 'Yup']) 98 | }, 99 | { 100 | throwOnError: true, 101 | giveWarning: true 102 | } 103 | ); 104 | 105 | const Parser2 = blueprint.object( 106 | { 107 | value: types.integer 108 | }, 109 | { onError: TypeError('The Invalid onError value') } 110 | ) 111 | 112 | const Parser3 = blueprint.array( 113 | { 114 | value: types.integer 115 | }, 116 | { 117 | onError: { 118 | onKey: (key, err) => { 119 | throw new TypeError('Error coming from ', key) 120 | }, 121 | }, 122 | } 123 | ) 124 | 125 | // Create array object from JSON 126 | const ListParser = blueprint.array({ 127 | name: types.string, 128 | age: types.integer 129 | }) 130 | 131 | // Create array object from existing blueprint object 132 | const Person = blueprint.object({ 133 | name: types.string 134 | }); 135 | 136 | const People = blueprint.array(Person); 137 | ``` 138 | 139 | ## Blueprint Extend 140 | 141 | Once you make an Object of blueprint, it can be extended into some new object with different types. This method helps you easier to re-use an existing object or defining several object with same basic types. 142 | 143 | > Example Of Blueprint Extend 144 | 145 | ``` 146 | const { blueprint, types, BlueprintClass } = require('podeng'); 147 | 148 | const Car = blueprint.object({ 149 | color: types.string, 150 | wheels: types.string({ normalize: ['trimmed', 'upper_first'] }) 151 | }); 152 | 153 | const Bus = blueprint.extend(Car, { 154 | brand: types.string({ normalize: 'upper_first_word' }), 155 | length: types.transform(val => `${val} meters`) 156 | }); 157 | 158 | const Buses = blueprint.array(Bus); 159 | 160 | // extend from an array object 161 | const FlyingBuses = blueprint.extend(Buses, { 162 | wingsCount: types.integer 163 | }); 164 | 165 | Bus({ 166 | color: 'Blue', 167 | wheels: 'bridgestone', 168 | brand: 'mercedes benz', 169 | length: 20 170 | }) 171 | 172 | //{ 173 | // color: 'Blue', 174 | // wheels: 'Bridgestone', 175 | // brand: 'Mercedes Benz', 176 | // length: '20 meters' 177 | //} 178 | 179 | FlyingBuses([{ 180 | color: 'Blue', 181 | wheels: 'bridgestone', 182 | brand: 'mercedes benz', 183 | length: 20, 184 | wingsCount: 20 185 | }]) 186 | 187 | //[{ 188 | // color: 'Blue', 189 | // wheels: 'Bridgestone', 190 | // brand: 'Mercedes Benz', 191 | // length: '20 meters' 192 | // wingsCount: 20 193 | //}] 194 | ``` 195 | 196 | Even you could attach some options like a normal blueprint object 197 | 198 | ``` 199 | const Man = blueprint.object({ 200 | name: types.string, 201 | age: types.integer, 202 | birthday: types.datetime({ dateOnly: true }) 203 | }); 204 | 205 | const Child = blueprint.extend( 206 | Man, 207 | { 208 | toy: types.string 209 | }, 210 | { 211 | onError: new TypeError('Ooops you\'ve got an wrong value!') 212 | } 213 | ); 214 | ``` 215 | 216 | Or you could remove properties that not needed on extended object by giving them an extra argument 217 | 218 | ``` 219 | const Man = blueprint.object({ 220 | name: types.string, 221 | age: types.integer, 222 | birthday: types.datetime({ dateOnly: true }) 223 | }); 224 | 225 | const Alien = blueprint.extend(Man, { 226 | planet: types.string 227 | }, { 228 | deleteProperties: ['birthday'] 229 | }) 230 | 231 | ``` 232 | 233 | ## Embedding Blueprint Object 234 | 235 | In Podeng attaching an existing blueprint object to property is possible, it known as embedding object. On embed object, all the rules of blueprint object will be assigned to the related property in master object. 236 | 237 | >> Example Embedded Object 238 | 239 | ``` 240 | const Obj1 = blueprint.object({ 241 | myValue: types.integer 242 | }) 243 | 244 | const Obj2 = blueprint.object({ 245 | foo: types.string, 246 | bar: Obj1 247 | }) 248 | 249 | const Obj3 = blueprint.object({ 250 | foo: types.string, 251 | bar: Obj1.embed({ default: 'empty value' }) 252 | }) 253 | 254 | Obj2({foo: 'bar', bar: { myValue: 'foo' }}) // { "foo": "bar", "bar": { "myValue": "foo" }} 255 | 256 | Obj3({ foo: 'something', bar: { myValue: 'interesting' }}) // { "foo": "something", "bar": { "myValue": "interesting" }} 257 | 258 | Obj3({ foo: 'something' }) // { "foo": "something", "bar": "empty value"} 259 | 260 | ``` 261 | 262 | ## Types options 263 | 264 | > Default Options (available for all types) 265 | 266 | - **hideOnFail**: Hide the value when normalization failed, default: `false` 267 | - **default**: The default value to assign when normalization fails, default: `null` 268 | - **validate**: Custom validation function, should return boolean, `true` for valid, `false` for invalid, default is `null` 269 | - **serialize.to**: Serialization options to rename property name on serializing, default: `null` 270 | - **serialize.display**: Serialization options to hide property name on serializing, default: `true` 271 | - **deserialize.from**: Deserialization options to accept property name on deserializing, default: `null` 272 | - **deserialize.display**: Deserialization options to hide property name on deserializing, default: `true` 273 | 274 | | Type | Default Options | Description | 275 | |--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 276 | | `string` | **stringify**: every value will be stringified, default: `true`
**min**: minimum digits to accept, default: `null`
**max**: maximum digits to accept, default: `null`
**normalize**: normalize the string value, the valid options are:
| String data types | 277 | | `number` | **min**: minimum number value to accept, default: `null`
**max**: maximum number value to accept, default: `null`
**minDigits**: minimal digit number value to accept, not include the length behind of comma separated number, default: `null`
**maxDigits**: maximum digit number value to accept, not include the length behind of comma separated number, default: `null` | Number data types including float and integer | 278 | | `integer` | **min**: minimum number value to accept, default: `null`
**max**: maximum number value to accept, default: `null`
**minDigits**: minimal digit value to accept, default: `null`
**maxDigits**: maximum digit value to accept, default: `null` | Accept integer data type | 279 | | `float` | **min**: minimum number value to accept, default: `null`
**max**: maximum number value to accept, default: `null`
**minDigits**: minimal digit value to accept, not include the length after comma separated, default: `null`
**maxDigits**: maximum digit value to accept, not include the length after comma separated, default: `null` | Accept float data type | 280 | | `bool` | **validList**: array list of valid values, default: `null`
**invalidList**: array list of invalid values, default: `null`
**caseSensitive**: case sensitive status to match with **validList** or **invalidList**, this works if any string value exist on these options, default: `null`
**normalizeNil**: change every non `undefined` or null value to normalize as a `true` value, default: `null` | In boolean data type, you should only choose either `validList` or `invalidList` to set on the options, they can't be used together (if both are setup, `validList` will be used). If you set `validList`, every value listed on `validList` array will make the result value become `true`, otherwise for every value described on `invalidList` array, every value that not listed on `invalidList` would become `true`. Please take a note for *normalizeNil* option doesn't effect if has `validList` or `invalidList` option | 281 | | `datetime` | **parseFormat**: string format to parse or accept incoming value, default: `null`
**returnFormat**: string format to parse value on before its returned, default: `null`
**timezoneAware**: boolean that indicates to parse with current timezone or not, default: `true`
**asMoment**: boolean that indicates to returning as a `moment` object, default: `false`
**dateOnly**: only accept and return date format as `YYYY-MM-DD`, default: `false`
**timeOnly**: only accept and return time format as `HH:mm:ss`, default: `false` | Datetime type could be used for date or time implementation, they can be resulted as a moment object automatically | 282 | | `any` | no options except on default options | All values will be accepted | 283 | | `options` | **list**: array of valid values, or can be set directly as a type argument | Only value inside this list will evaluated as a valid value | 284 | | `transform` | no options except on default options | transform accept function to evaluates the incoming value as a valid or invalid value, the function should return `boolean` type of status | 285 | | `conditions` | **evaluates**: a function that evaluates the input value, must return `boolean` type indicating the success status
**onOk**: a function to run if the evaulation status is true
**onFail**: a function to run if the evaulation status is false | Condition could be helpful if we want to accept value based on several conditions
**shorthand**: use first argument as **evaluates** function, second argument as **onOk** and third as **onFail** function. | 286 | 287 | 288 | ## Types with Simple Example 289 | 290 | These are various types that you can use to normalize JSON values, please take a note the example just a simple schema that may not covering all of the options described above, but you can explore more deeper inspecting the test files or by opening an issue here. 291 | 292 | ### String 293 | 294 | ``` 295 | const Parser = blueprint.object({ 296 | name: types.string({ normalize: ['trimmed', 'upper_first_word']), 297 | address: types.string({ normalize: 'trimmed'), 298 | }) 299 | 300 | Parser({ 301 | name: ' aditya kresna permana ', 302 | address: ' Bekasi Indonesia ' 303 | }) 304 | 305 | // produce { "name": 'Aditya Kresna Permana', address: 'Bekasi Indonesia' } 306 | ``` 307 | 308 | 309 | ### Integer 310 | 311 | ``` 312 | const Parser = blueprint.object({ 313 | num: types.integer({ min: 10 }) 314 | }) 315 | 316 | Parser({ num: 10 }) // { "num": 10 } 317 | Parser({ num: 7 }) // { "num": null } 318 | 319 | ``` 320 | 321 | ### Float 322 | 323 | ``` 324 | const Parser = blueprint.object({ 325 | num: types.float({ min: 10 }) 326 | }) 327 | 328 | Parser({ num: 10.10 }) // { "num": 10.10 } 329 | Parser({ num: 7.12 }) // { "num": null } 330 | 331 | ``` 332 | 333 | ### Number 334 | 335 | ``` 336 | const Parser = blueprint.object({ 337 | num: types.number, 338 | num2: types.number 339 | }) 340 | 341 | Parser({ num: 11.10, num2: 20 }) // { "num": 11.10, "num2": 20 } 342 | Parser({ num: 7, num2: 18.5 }) // { "num": 7, "num2": 18.5 } 343 | 344 | ``` 345 | 346 | ### Boolean 347 | 348 | ``` 349 | const Parser = blueprint.object({ 350 | val1: types.bool, 351 | val2: types.bool(['Yes', 'Yeah']), 352 | }) 353 | 354 | Parser({ val1: true, val2: 'Yes' })) // { "val1": true, "val2": true } 355 | Parser({ val1: true, val2: 'No' })) // { "val1": true, "val2": false } 356 | ``` 357 | 358 | ### DateTime 359 | 360 | ``` 361 | const Parser = blueprint.object({ 362 | val1: types.datetime, 363 | val2: types.datetime('DD-MM-YYYY'), 364 | }) 365 | 366 | 367 | Parser({ val1: '2018-06-18', val2: '18-06-1991' }) // { "val1": "2018-06-18T00:00:00+07:00", "val2": "1991-06-18T00:00:00+07:00" } 368 | ``` 369 | 370 | ### Options 371 | 372 | ``` 373 | const Car = blueprint.object({ 374 | brand: types.options(['Honda', 'Toyota', 'Mitsubishi']), 375 | color: types.options({ list: ['Red', 'Green', 'Blue'], default: 'None' }), 376 | }) 377 | 378 | Car({ brand: 'Yamaha', color: 'Green' }) // { "brand": null, "color": "Green" } 379 | Car({ brand: 'Yamaha', color: 'Black' }) // { "brand": null, "color": "None" } 380 | ``` 381 | 382 | ### Transform 383 | 384 | ``` 385 | const Parser = blueprint.object({ 386 | val1: types.transform(val => val * 2), 387 | val2: types.transform(1818), 388 | }) 389 | 390 | Parser({ val1: 10, val2: 20 }) // { "val1": 20, "val2": 1818 } 391 | ``` 392 | 393 | ### Conditions 394 | 395 | ``` 396 | const Exercise = blueprint.object({ 397 | age: types.conditions(value => value >= 17, 'Adult', 'Child'), 398 | grade: types.conditions({ 399 | evaluates: grade => grade >= 7, 400 | onOk: 'Congratulation!', 401 | onFail: 'Sorry You Fail', 402 | }), 403 | }) 404 | 405 | Exercise({ age: 17, grade: 8 }) // { "age": "Adult", "grade": "Congratulation!" } 406 | Exercise({ age: 10, grade: 5 }) // { "age": "Child", "grade": "Sorry You Fail" } 407 | ``` 408 | 409 | ### Any 410 | 411 | ``` 412 | const Parser = blueprint.object({ 413 | val1: types.any, 414 | val2: types.any(), 415 | val3: types.any({ allowUndefined: true }) 416 | }) 417 | 418 | Parser({ val1: 123, val2: "Foo", val3: null }) // { "val1": 123, "val2": "Foo", "val3": null } 419 | Parser({ val1: "Foo", val2: "Bar" }) // { "val1": 123, "val2": "Foo", "val3": undefined } 420 | Parser({ val1: "Foo"}) // { "val1": 123, "val2": null, "val3": undefined } 421 | ``` 422 | 423 | ## Validator 424 | 425 | Podeng has capability to validate your json, the validation refers to your type schema and throwing exception (or not) depending on what the options you set. If you find more example to implement validation, you could refers to test files as well. 426 | 427 | ### Example Validation 428 | 429 | ``` 430 | const { blueprint, types, validator } = require('podeng'); 431 | 432 | const Human = blueprint.object({ 433 | eyeColor: types.string, 434 | hairColor: types.string 435 | }); 436 | 437 | const HumanValidator = validator(Human); 438 | 439 | HumanValidator.validate({ eyeColor: 'Blue', hairColor: () => {} }); // will throw an error 440 | 441 | const [errorStatus, errorDetails] = HumanValidator.check({ 442 | eyeColor: 'Blue', 443 | hairColor: () => {} 444 | }); 445 | 446 | // errorStatus: true 447 | // errorDetails: { hairColor: ['failed to parse "hairColor" with its type'] } 448 | ``` 449 | 450 | ## License 451 | 452 | MIT License 453 | 454 | Copyright 2018 Aditya Kresna Permana 455 | 456 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 457 | 458 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 459 | 460 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 461 | -------------------------------------------------------------------------------- /blueprint/base/cls.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const normalize = require('../functions/normalize'); 4 | const serialize = require('../functions/serialize'); 5 | const deserialize = require('../functions/deserialize'); 6 | 7 | const cls = function (schema, options = {}, { isArray = false }) { 8 | this.isArray = isArray; 9 | this.schema = schema; 10 | this.options = options; 11 | 12 | this.normalize = normalize; 13 | this.serialize = serialize; 14 | this.deserialize = deserialize; 15 | }; 16 | 17 | module.exports = cls; 18 | -------------------------------------------------------------------------------- /blueprint/base/embedCls.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isString, isNumber, isBoolean } = require('../../types/detector'); 4 | const { combineEmbedDefaultOptions } = require('../utils'); 5 | 6 | const EmbedCls = function (instanceCls, options = {}) { 7 | this.options = combineEmbedDefaultOptions(options); 8 | this.instance = instanceCls; 9 | 10 | this.getOptions = function () { 11 | return this.options; 12 | }; 13 | this.getObject = function () { 14 | return this.instance; 15 | }; 16 | this.getSerializeName = function () { 17 | return (isString(this.options.serialize.to) || isNumber(this.options.serialize.to) 18 | ? this.options.serialize.to 19 | : null); 20 | }; 21 | 22 | this.getDeserializeName = function () { 23 | return (isString(this.options.deserialize.from) || isNumber(this.options.deserialize.from) 24 | ? this.options.deserialize.from 25 | : null); 26 | }; 27 | 28 | this.isHideOnSerialization = function () { 29 | return !(isBoolean(this.options.serialize.display) ? this.options.serialize.display : true); 30 | }; 31 | 32 | this.isHideOnDeserialization = function () { 33 | return !(isBoolean(this.options.deserialize.display) 34 | ? this.options.deserialize.display 35 | : true); 36 | }; 37 | 38 | this.isHideOnFail = function () { return this.options.hideOnFail; }; 39 | }; 40 | 41 | module.exports = EmbedCls; 42 | -------------------------------------------------------------------------------- /blueprint/functions/deserialize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { keys, forEach, omit } = require('lodash'); 4 | const { isArray } = require('../../types/detector'); 5 | const { isTypeObject } = require('../utils'); 6 | const { resolveEmbededObj, parseEmbedValue, initiateTypeHandler, handleUnknownProperties } = require('./utils'); 7 | 8 | const deserializeValue = function (valuesToDeserialize, onValidation = false) { 9 | if (this.isArray && !isArray(valuesToDeserialize)) { 10 | throw new TypeError('Wrong value type, you must supply array values!'); 11 | } 12 | 13 | const getDeserializedKeys = (objValue, schema) => { 14 | const deserializedNames = []; 15 | forEach(schema, (typeHandler, key) => { 16 | const type = initiateTypeHandler(typeHandler); 17 | const deserializeFrom = type.getDeserializeName() === null 18 | ? type.getSerializeName() === null ? key : type.getSerializeName() 19 | : type.getDeserializeName(); 20 | deserializedNames.push(deserializeFrom); 21 | }); 22 | return deserializedNames; 23 | }; 24 | 25 | const deserialize = (objValue, schema, config = { doValidation: false }) => { 26 | const deserialized = {}; 27 | const errorResults = {}; 28 | 29 | forEach(schema, (typeHandler, key) => { 30 | const errorList = []; 31 | let deserializeFrom = key; 32 | 33 | const embedObj = resolveEmbededObj(typeHandler); 34 | if (embedObj !== null) { 35 | deserializeFrom = embedObj.getDeserializeName() === null 36 | ? (embedObj.getSerializeName() === null ? key : embedObj.getSerializeName()) 37 | : embedObj.getDeserializeName(); 38 | const deserializedResult = parseEmbedValue('deserialize', embedObj, objValue[deserializeFrom]); 39 | if (!embedObj.isHideOnDeserialization()) { 40 | deserialized[key] = deserializedResult; 41 | } 42 | } else { 43 | const type = initiateTypeHandler(typeHandler); 44 | deserializeFrom = type.getDeserializeName() === null 45 | ? (type.getSerializeName() === null ? key : type.getSerializeName()) 46 | : type.getDeserializeName(); 47 | 48 | let [fail, normalizedValue] = type.parse( 49 | deserializeFrom, 50 | objValue ? objValue[deserializeFrom] : undefined, 51 | { 52 | operationType: 'deserialize', 53 | data: objValue || {} 54 | } 55 | ); 56 | 57 | // Handle multilevel types normalization 58 | // for example conditions type 59 | while (isTypeObject(normalizedValue)) { 60 | const result = normalizedValue.parse( 61 | deserializeFrom, 62 | objValue ? objValue[deserializeFrom] : undefined, 63 | { 64 | operationType: 'deserialize', 65 | data: objValue || {} 66 | } 67 | ); 68 | fail = result[0]; 69 | normalizedValue = result[1]; 70 | } 71 | 72 | if (config.doValidation) { 73 | const [errorDetails, valid] = type.validate( 74 | deserializeFrom, 75 | objValue ? objValue[deserializeFrom] : undefined, 76 | type.getOptions() 77 | ); 78 | 79 | if (!valid) { 80 | fail = true; 81 | forEach(errorDetails, err => errorList.push(err)); 82 | } 83 | } 84 | 85 | if (!fail || (fail && !type.isHideOnFail())) { 86 | if (!type.isHideOnDeserialization()) { 87 | deserialized[key] = normalizedValue; 88 | } 89 | } 90 | 91 | // default error message 92 | if (fail && errorList.length === 0) { 93 | errorList.push( 94 | `failed to deserialize from "${deserializeFrom}" to "${key}" with its type` 95 | ); 96 | } 97 | 98 | if (errorList.length > 0) { 99 | errorResults[deserializeFrom] = Object.assign([], errorList); 100 | } 101 | } 102 | }); 103 | 104 | return [errorResults, deserialized]; 105 | }; 106 | 107 | const isAllowUnknownProperties = this.options.allowUnknownProperties; 108 | 109 | if (!this.isArray) { 110 | const [errors, deserializedResult] = deserialize( 111 | valuesToDeserialize, 112 | this.schema, 113 | { doValidation: onValidation } 114 | ); 115 | 116 | if (isAllowUnknownProperties) { 117 | const deserializedKeys = getDeserializedKeys( 118 | valuesToDeserialize, 119 | this.schema 120 | ); 121 | Object.assign( 122 | deserializedResult, 123 | omit( 124 | handleUnknownProperties(valuesToDeserialize, this.schema), 125 | deserializedKeys 126 | ) 127 | ); 128 | } 129 | 130 | return [keys(errors).length > 0, errors, deserializedResult]; 131 | } else { 132 | const results = valuesToDeserialize.map(v => { 133 | const [errorDetails, deserializedResult] = deserialize(v, this.schema, { 134 | doValidation: onValidation 135 | }); 136 | if (isAllowUnknownProperties) { 137 | const deserializedKeys = getDeserializedKeys(v, this.schema); 138 | Object.assign( 139 | deserializedResult, 140 | omit(handleUnknownProperties(v, this.schema), deserializedKeys) 141 | ); 142 | } 143 | return [errorDetails, deserializedResult]; 144 | }); 145 | const allErrors = []; 146 | const normalizedResults = []; 147 | 148 | forEach(results, ([errors, normalized]) => { 149 | if (errors.length > 0) allErrors.push(errors); 150 | normalizedResults.push(normalized); 151 | }); 152 | 153 | return [allErrors.length > 0, allErrors, normalizedResults]; 154 | } 155 | }; 156 | 157 | module.exports = deserializeValue; 158 | -------------------------------------------------------------------------------- /blueprint/functions/normalize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { keys, forEach } = require('lodash'); 4 | const { isArray } = require('../../types/detector'); 5 | const { isTypeObject } = require('../utils'); 6 | const { 7 | resolveEmbededObj, 8 | parseEmbedValue, 9 | initiateTypeHandler, 10 | handleUnknownProperties 11 | } = require('./utils'); 12 | 13 | const normalizeValue = function (valuesToNormalize, onValidation = false) { 14 | if (this.isArray && !isArray(valuesToNormalize)) { 15 | throw new TypeError('Wrong value type, you must supply array values!'); 16 | } 17 | 18 | const normalize = (objValue, schema, config = { doValidation: false }) => { 19 | const normalized = {}; 20 | const errorResults = {}; 21 | 22 | forEach(schema, (typeHandler, key) => { 23 | // checking handler is an embedded class 24 | // so we do recursive operation 25 | const embedObj = resolveEmbededObj(typeHandler); 26 | if (embedObj !== null) { 27 | const embedValue = objValue[key]; 28 | 29 | const result = parseEmbedValue('normalize', embedObj, embedValue); 30 | 31 | if (result !== null) normalized[key] = result; 32 | } else { 33 | const errorList = []; 34 | 35 | const type = initiateTypeHandler(typeHandler); 36 | 37 | let [fail, normalizedValue] = type.parse( 38 | key, 39 | objValue ? objValue[key] : undefined, 40 | { 41 | operationType: 'serialize', 42 | data: objValue || {} 43 | } 44 | ); 45 | 46 | // Handle multilevel types normalization 47 | // for example conditions type 48 | while (isTypeObject(normalizedValue)) { 49 | const result = normalizedValue.parse( 50 | key, 51 | objValue ? objValue[key] : undefined, 52 | { 53 | operationType: 'serialize', 54 | data: objValue || {} 55 | } 56 | ); 57 | fail = result[0]; 58 | normalizedValue = result[1]; 59 | } 60 | 61 | // only execute if for validation purpose 62 | if (config.doValidation) { 63 | const [errorDetails, valid] = type.validate( 64 | key, 65 | objValue ? objValue[key] : undefined, 66 | type.getOptions() 67 | ); 68 | 69 | if (!valid) { 70 | fail = true; 71 | forEach(errorDetails, err => errorList.push(err)); 72 | } 73 | } 74 | 75 | if (!fail || (fail && !type.isHideOnFail())) { 76 | normalized[key] = normalizedValue; 77 | } 78 | 79 | // default error message 80 | if (fail && errorList.length === 0) { 81 | errorList.push(`failed to parse "${key}" with its type`); 82 | } 83 | 84 | if (errorList.length > 0) { 85 | errorResults[key] = Object.assign([], errorList); 86 | } 87 | } 88 | }); 89 | 90 | return [errorResults, normalized]; 91 | }; 92 | 93 | const isAllowUnknownProperties = this.options.allowUnknownProperties; 94 | 95 | if (!this.isArray) { 96 | const [errors, normalizedResult] = normalize( 97 | valuesToNormalize, 98 | this.schema, 99 | { doValidation: onValidation } 100 | ); 101 | 102 | if (isAllowUnknownProperties) { 103 | Object.assign( 104 | normalizedResult, 105 | handleUnknownProperties(valuesToNormalize, this.schema) 106 | ); 107 | } 108 | 109 | return [keys(errors).length > 0, errors, normalizedResult]; 110 | } else { 111 | const results = valuesToNormalize.map(v => { 112 | const [errorDetails, normalizedResult] = normalize(v, this.schema, { 113 | doValidation: onValidation 114 | }); 115 | if (isAllowUnknownProperties) { 116 | Object.assign( 117 | normalizedResult, 118 | handleUnknownProperties(v, this.schema) 119 | ); 120 | } 121 | return [errorDetails, normalizedResult]; 122 | }); 123 | const allErrors = []; 124 | const normalizedResults = []; 125 | 126 | forEach(results, ([errors, normalized]) => { 127 | if (errors.length > 0) allErrors.push(errors); 128 | normalizedResults.push(normalized); 129 | }); 130 | 131 | return [allErrors.length > 0, allErrors, normalizedResults]; 132 | } 133 | }; 134 | 135 | module.exports = normalizeValue; 136 | -------------------------------------------------------------------------------- /blueprint/functions/serialize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { includes, forEach } = require('lodash'); 4 | const { 5 | resolveEmbededObj, 6 | parseEmbedValue, 7 | initiateTypeHandler, 8 | handleUnknownProperties 9 | } = require('./utils'); 10 | 11 | const serializeValue = function ( 12 | valuesToSerialize, 13 | onValidation = false, 14 | opts = { isSelfCall: false } 15 | ) { 16 | let serialized = this.isArray ? [] : {}; 17 | 18 | let isError = false; 19 | let errorDetails = []; 20 | let normalizedValues = {}; 21 | if (!opts.isSelfCall) { 22 | [isError, errorDetails, normalizedValues] = this.normalize( 23 | valuesToSerialize, 24 | { doValidation: onValidation } 25 | ); 26 | } else { 27 | normalizedValues = valuesToSerialize; 28 | } 29 | 30 | const isAllowUnknownProperties = this.options.allowUnknownProperties; 31 | 32 | const serialize = (normalizedValues, schema) => { 33 | const serialized = {}; 34 | const normalizedKeys = Object.keys(normalizedValues); 35 | 36 | forEach(schema, (typeHandler, key) => { 37 | const embedObj = resolveEmbededObj(typeHandler); 38 | if (embedObj !== null) { 39 | if (!embedObj.isHideOnSerialization()) { 40 | const serializedResult = parseEmbedValue( 41 | 'serialize', 42 | embedObj, 43 | normalizedValues[key] 44 | ); 45 | const serializeTo = embedObj.getSerializeName(); 46 | if (serializeTo !== null) { 47 | serialized[serializeTo] = serializedResult; 48 | } else { 49 | serialized[key] = serializedResult; 50 | } 51 | } 52 | } else { 53 | const type = initiateTypeHandler(typeHandler); 54 | if (includes(normalizedKeys, key) && !type.isHideOnSerialization()) { 55 | const serializeTo = type.getSerializeName(); 56 | if (serializeTo !== null) { 57 | serialized[serializeTo] = normalizedValues[key]; 58 | } else { 59 | serialized[key] = normalizedValues[key]; 60 | } 61 | } 62 | } 63 | }); 64 | 65 | return serialized; 66 | }; 67 | 68 | if (this.isArray) { 69 | forEach(normalizedValues, v => { 70 | const serializeResult = serialize(v, this.schema); 71 | 72 | if (isAllowUnknownProperties) { 73 | Object.assign(serializeResult, handleUnknownProperties(v, this.schema)); 74 | } 75 | 76 | serialized.push(serializeResult); 77 | }); 78 | } else { 79 | serialized = serialize(normalizedValues, this.schema); 80 | if (isAllowUnknownProperties) { 81 | Object.assign( 82 | serialized, 83 | handleUnknownProperties(normalizedValues, this.schema) 84 | ); 85 | } 86 | } 87 | 88 | return [isError, errorDetails, serialized]; 89 | }; 90 | 91 | module.exports = serializeValue; 92 | -------------------------------------------------------------------------------- /blueprint/functions/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BlueprintEmbedClass = require('../base/embedCls'); 4 | const { includes, keys, difference, pick } = require('lodash'); 5 | const { isArray, isFunction, isUndefined } = require('../../types/detector'); 6 | 7 | /** 8 | * Resolving type handler, if user didn't execute the function 9 | * it will be auto initialized 10 | * @param {*} typehandler 11 | */ 12 | const initiateTypeHandler = typehandler => { 13 | if (includes(keys(typehandler), 'parse')) { 14 | return typehandler; 15 | } else { 16 | return typehandler(); 17 | } 18 | }; 19 | 20 | /** 21 | * Detect & returning embedded object 22 | * @param {embedCls} embeddedObject 23 | */ 24 | const resolveEmbededObj = obj => 25 | isFunction(obj.embed) && obj.embed() instanceof BlueprintEmbedClass 26 | ? obj.embed() 27 | : obj instanceof BlueprintEmbedClass 28 | ? obj 29 | : null; 30 | 31 | const handleUnknownProperties = (params, objToExclude) => { 32 | const registeredKeys = keys(objToExclude); 33 | const paramKeys = keys(params); 34 | const unknownProperties = difference(paramKeys, registeredKeys); 35 | return pick(params, unknownProperties); 36 | }; 37 | 38 | /** 39 | * Normalizing embedded object 40 | * @param {*} embedObj 41 | * @param {*} valueToParse 42 | */ 43 | const parseEmbedValue = (clsMethodName, embedObj, valueToParse) => { 44 | const embedInstance = embedObj.getObject(); 45 | const embedOptions = embedObj.getOptions(); 46 | let result = null; 47 | 48 | // resolving empty value based on embed options 49 | const resolveEmptyValue = () => { 50 | const defaultValue = embedOptions.default; 51 | if (isUndefined(defaultValue)) { 52 | return embedInstance.isArray ? [] : null; 53 | } else { 54 | return defaultValue; 55 | } 56 | }; 57 | 58 | if (valueToParse) { 59 | // treat different action if value is not valid for array blueprint 60 | // because executing wrong value type (not array) on array object will cause exception 61 | if (embedInstance.isArray && !isArray(valueToParse)) { 62 | result = resolveEmptyValue(); 63 | } else { 64 | // calling normalize/serialize/deserialize function on parent blueprint obj 65 | const [fail, , parsedValues] = embedInstance[clsMethodName]( 66 | valueToParse, 67 | false, 68 | { isSelfCall: true } 69 | ); 70 | 71 | // applying embed object options 72 | if (!fail || (fail && !embedOptions.hideOnFail)) { 73 | result = parsedValues; 74 | } 75 | } 76 | } else { 77 | if (!embedOptions.hideOnFail) { 78 | result = resolveEmptyValue(); 79 | } 80 | } 81 | 82 | return result; 83 | }; 84 | 85 | module.exports = { 86 | initiateTypeHandler, 87 | resolveEmbededObj, 88 | handleUnknownProperties, 89 | parseEmbedValue 90 | }; 91 | -------------------------------------------------------------------------------- /blueprint/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { includes, keys, forEach } = require('lodash'); 4 | const BlueprintClass = require('./base/cls'); 5 | const BlueprintEmbedClass = require('./base/embedCls'); 6 | const types = require('../types'); 7 | const { 8 | combineObjDefaultOptions, 9 | combineExtDefaultOptions 10 | } = require('./utils'); 11 | const { errorInitializer, warningInitializer } = require('../validator/errors'); 12 | const { isFunction, isArray } = require('../types/detector'); 13 | 14 | /** 15 | * Creating new instance and return as handler function 16 | * @param {Object} schema 17 | * @param {Object} options 18 | * @param {boolean} isArrayType 19 | */ 20 | const createHandler = (schema, options, isArrayType = false) => { 21 | const inst = new BlueprintClass(schema, options, { isArray: isArrayType }); 22 | 23 | const handlerFunc = () => { }; 24 | handlerFunc.getInstance = () => inst; 25 | handlerFunc.getClass = () => BlueprintClass; 26 | 27 | return handlerFunc; 28 | }; 29 | 30 | /** 31 | * Deep freezing object recursively 32 | * @param {Object} obj 33 | */ 34 | const freezeObject = obj => { 35 | forEach(keys(obj), name => { 36 | const prop = obj[name]; 37 | if (typeof prop === 'object' && prop !== null) freezeObject(prop); 38 | }); 39 | 40 | return Object.freeze(obj); 41 | }; 42 | 43 | const componentCreator = isArrayComponent => { 44 | return (params, options = {}) => { 45 | let combinedDefaultOptions = combineObjDefaultOptions(options); 46 | 47 | /** 48 | * Detect params passed as a component instead of a json 49 | */ 50 | let handler; 51 | if ( 52 | includes(keys(params), 'getHandler') && 53 | isFunction(params.getHandler) && 54 | includes(keys(params), 'getInstance') && 55 | isFunction(params.getInstance) 56 | ) { 57 | if (!(params.getInstance() instanceof BlueprintClass)) { 58 | throw new TypeError( 59 | 'Invalid parameter, not an instance of blueprint object' 60 | ); 61 | } 62 | // Re-creating handler from existing components 63 | // overriding the default options 64 | 65 | combinedDefaultOptions = isArrayComponent 66 | ? Object.assign(combinedDefaultOptions, params.getOptions(), options) 67 | : combinedDefaultOptions; 68 | 69 | handler = createHandler( 70 | params.getParams(), 71 | combinedDefaultOptions, 72 | isArrayComponent 73 | ); 74 | } else { 75 | handler = createHandler(params, combinedDefaultOptions, isArrayComponent); 76 | } 77 | 78 | const { onError, throwOnError, giveWarning } = combinedDefaultOptions; 79 | const errorHandler = errorInitializer({ 80 | onError, 81 | throwOnError 82 | }); 83 | const warningHandler = warningInitializer({ 84 | giveWarning 85 | }); 86 | 87 | /** 88 | * Normalize function 89 | * @param {Object} values 90 | * @returns {Object} Normalized values 91 | */ 92 | const component = function (values) { 93 | const [ 94 | err, 95 | errorDetails, 96 | normalizedValues 97 | ] = handler.getInstance().normalize(values); 98 | 99 | if (err) { 100 | warningHandler(errorDetails); 101 | errorHandler(errorDetails); 102 | } 103 | 104 | return combinedDefaultOptions.frozen 105 | ? freezeObject(normalizedValues) 106 | : normalizedValues; 107 | }; 108 | 109 | /** 110 | * Serialize function 111 | * @param {Object} values 112 | * @returns {Object} Serialized values 113 | */ 114 | component.serialize = values => { 115 | const [ 116 | err, 117 | errorDetails, 118 | serializedValues 119 | ] = handler.getInstance().serialize(values); 120 | if (err) { 121 | warningHandler(errorDetails); 122 | errorHandler(errorDetails); 123 | } 124 | 125 | return combinedDefaultOptions.frozen 126 | ? freezeObject(serializedValues) 127 | : serializedValues; 128 | }; 129 | 130 | /** 131 | * Deserialize function 132 | * @param {Object} values 133 | * @returns {Object} Deserialized values 134 | */ 135 | component.deserialize = values => { 136 | const [ 137 | err, 138 | errorDetails, 139 | deserializedValues 140 | ] = handler.getInstance().deserialize(values); 141 | 142 | if (err) { 143 | warningHandler(errorDetails); 144 | errorHandler(errorDetails); 145 | } 146 | 147 | return combinedDefaultOptions.frozen 148 | ? freezeObject(deserializedValues) 149 | : deserializedValues; 150 | }; 151 | 152 | component.embed = options => 153 | new BlueprintEmbedClass(handler.getInstance(), options); 154 | 155 | /** 156 | * Return handler from this component 157 | * @returns Object 158 | */ 159 | component.getHandler = () => handler; 160 | 161 | /** 162 | * Return instance from original class instance 163 | * @returns Object 164 | */ 165 | component.getInstance = () => handler.getInstance(); 166 | 167 | /** 168 | * Get Schema from class instance 169 | * @returns Object 170 | */ 171 | component.getSchema = () => handler.getInstance().schema; 172 | 173 | component.getParams = () => Object.assign({}, params); 174 | component.getOptions = () => Object.assign({}, combinedDefaultOptions); 175 | 176 | return component; 177 | }; 178 | }; 179 | 180 | const extensibleComponent = ( 181 | component, 182 | params, 183 | options = {}, 184 | extendOptions = {} 185 | ) => { 186 | if (isArray(component)) { 187 | throw new TypeError( 188 | 'To extend you need to pass blueprint object not an array!' 189 | ); 190 | } 191 | 192 | const hasInstanceFunc = includes(keys(component), 'getInstance'); 193 | 194 | if (!hasInstanceFunc) { 195 | throw new TypeError('To extend you must pass blueprint object!'); 196 | } 197 | 198 | if (!(component.getInstance() instanceof BlueprintClass)) { 199 | throw new TypeError('To extend you must pass blueprint object!'); 200 | } 201 | 202 | const extOptions = combineExtDefaultOptions(extendOptions); 203 | 204 | const originalParams = component.getParams(); 205 | const originalOptions = component.getOptions(); 206 | 207 | options = combineObjDefaultOptions(Object.assign({}, originalOptions, options)); 208 | 209 | const deleteProperties = (params, listPropsToDelete) => { 210 | forEach(listPropsToDelete, propName => { 211 | if (originalParams[propName]) { 212 | delete params[propName]; 213 | } 214 | }); 215 | }; 216 | 217 | if (extOptions.deleteProperties.length > 0) { 218 | deleteProperties(originalParams, extOptions.deleteProperties); 219 | } 220 | 221 | const finalParams = Object.assign({}, originalParams, params); 222 | 223 | return componentCreator(component.getInstance().isArray)(finalParams, options); 224 | }; 225 | 226 | module.exports = { 227 | object: componentCreator(false), 228 | array: componentCreator(true), 229 | extend: extensibleComponent, 230 | types, 231 | BlueprintClass, 232 | BlueprintEmbedClass 233 | }; 234 | -------------------------------------------------------------------------------- /blueprint/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { includes, keys } = require('lodash'); 4 | 5 | const DEFAULT_OPTIONS = { 6 | frozen: false, 7 | giveWarning: false, 8 | onError: {}, 9 | throwOnError: false, 10 | allowUnknownProperties: false 11 | }; 12 | 13 | const EXTEND_OPTIONS = { 14 | deleteProperties: [] 15 | }; 16 | 17 | const EMBED_OPTIONS = { 18 | default: undefined, 19 | hideOnFail: false, 20 | serialize: { 21 | to: null, 22 | display: true 23 | }, 24 | deserialize: { 25 | from: null, 26 | display: true 27 | } 28 | }; 29 | 30 | const combineObjDefaultOptions = options => 31 | Object.assign({}, DEFAULT_OPTIONS, options); 32 | 33 | const combineExtDefaultOptions = options => 34 | Object.assign({}, EXTEND_OPTIONS, options); 35 | 36 | const combineEmbedDefaultOptions = options => 37 | Object.assign({}, EMBED_OPTIONS, options); 38 | 39 | const isTypeObject = obj => { 40 | const incMethod = name => { 41 | return includes(keys(obj), name); 42 | }; 43 | return ( 44 | incMethod('parse') && 45 | incMethod('validate') && 46 | incMethod('getSerializeName') && 47 | incMethod('getDeserializeName') && 48 | incMethod('isHideOnSerialization') && 49 | incMethod('isHideOnDeserialization') && 50 | incMethod('isHideOnFail') && 51 | incMethod('getOptions') 52 | ); 53 | }; 54 | 55 | module.exports = { 56 | combineObjDefaultOptions, 57 | combineExtDefaultOptions, 58 | combineEmbedDefaultOptions, 59 | isTypeObject 60 | }; 61 | -------------------------------------------------------------------------------- /faker/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveofcode/podeng/2a51724889d5217dc33ab9a9b7cd8c8195d9dcf4/faker/index.js -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** Declaration file generated by dts-gen */ 2 | 3 | export function BlueprintClass(schema: any, options?: any, { isArray = false }?: any): void; 4 | 5 | export function BlueprintEmbedClass(instanceCls: any, options?: any): any; 6 | 7 | export function validator(component: any, options?: any): any; 8 | 9 | export namespace blueprint { 10 | function BlueprintClass(schema: any, options?: any, { isArray = false }?: any): void; 11 | 12 | function BlueprintEmbedClass(instanceCls: any, options?: any): any; 13 | 14 | function array(params: any, options?: any): any; 15 | 16 | function extend(component: any, params: any, options?: any, extendOptions?: any): any; 17 | 18 | function object(params: any, options?: any): any; 19 | 20 | namespace types { 21 | function any(paramsOrOptions: any): any; 22 | 23 | function bool(paramsOrOptions: any): any; 24 | 25 | function conditions(paramsOrOptions: any): any; 26 | 27 | function datetime(paramsOrOptions: any): any; 28 | 29 | function integer(paramsOrOptions: any): any; 30 | 31 | function number(paramsOrOptions: any): any; 32 | 33 | function options(paramsOrOptions: any): any; 34 | 35 | function string(paramsOrOptions: any): any; 36 | 37 | function transform(paramsOrOptions: any): any; 38 | 39 | } 40 | 41 | } 42 | 43 | export namespace types { 44 | function any(paramsOrOptions: any): any; 45 | 46 | function bool(paramsOrOptions: any): any; 47 | 48 | function conditions(paramsOrOptions: any): any; 49 | 50 | function datetime(paramsOrOptions: any): any; 51 | 52 | function integer(paramsOrOptions: any): any; 53 | 54 | function number(paramsOrOptions: any): any; 55 | 56 | function options(paramsOrOptions: any): any; 57 | 58 | function string(paramsOrOptions: any): any; 59 | 60 | function transform(paramsOrOptions: any): any; 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('./blueprint'); 4 | const types = require('./types'); 5 | const validator = require('./validator'); 6 | 7 | module.exports = { 8 | blueprint, 9 | types, 10 | validator, 11 | BlueprintClass: blueprint.BlueprintClass, 12 | BlueprintEmbedClass: blueprint.BlueprintEmbedClass 13 | }; 14 | -------------------------------------------------------------------------------- /logos/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveofcode/podeng/2a51724889d5217dc33ab9a9b7cd8c8195d9dcf4/logos/logo.png -------------------------------------------------------------------------------- /logos/logo_200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveofcode/podeng/2a51724889d5217dc33ab9a9b7cd8c8195d9dcf4/logos/logo_200.png -------------------------------------------------------------------------------- /logos/logo_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveofcode/podeng/2a51724889d5217dc33ab9a9b7cd8c8195d9dcf4/logos/logo_300.png -------------------------------------------------------------------------------- /logos/logo_400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slaveofcode/podeng/2a51724889d5217dc33ab9a9b7cd8c8195d9dcf4/logos/logo_400.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "podeng", 3 | "version": "1.5.5", 4 | "description": "Simple JSON value normalization to make everything gone right.", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "lint-staged": { 13 | "*.js": [ 14 | "eslint --fix", 15 | "git add" 16 | ] 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/slaveofcode/podeng.git" 21 | }, 22 | "keywords": [ 23 | "json", 24 | "json-validator", 25 | "json-handler", 26 | "parser", 27 | "validator" 28 | ], 29 | "author": "Aditya Kresna Permana", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/slaveofcode/podeng/issues" 33 | }, 34 | "homepage": "https://github.com/slaveofcode/podeng#readme", 35 | "devDependencies": { 36 | "eslint": "^4.19.1", 37 | "eslint-config-standard": "^11.0.0", 38 | "eslint-plugin-import": "^2.12.0", 39 | "eslint-plugin-node": "^6.0.1", 40 | "eslint-plugin-promise": "^3.7.0", 41 | "eslint-plugin-standard": "^3.1.0", 42 | "jest": "^24.8.0", 43 | "moment-timezone": "^0.5.21" 44 | }, 45 | "dependencies": { 46 | "lodash": "^4.17.20", 47 | "moment": "^2.22.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/blueprint.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file intended to create a blueprint or feature plan in this project 4 | * this also could be used for code documentation about how to use the lib. 5 | */ 6 | 7 | // Parser 8 | const { blueprint, types, faker, validator } = require('podeng') 9 | 10 | // Type properties 11 | // - String: min, max, normalize: [uppercased, lowercased, upper_first, upper_first_word, custom func] 12 | 13 | // Creating blueprint of object 14 | const Item = blueprint.object( 15 | { 16 | categoryName: types.string, 17 | categoryId: types.number, 18 | someSecretKey: types.string, 19 | }, 20 | { 21 | onError: { 22 | onKey: (key, value) => { 23 | throw new Error(`Error on key ${key} with value ${value}`) 24 | }, 25 | onAll: allErrorParams => { 26 | throw new Error( 27 | `Error on key ${key} with value ${value}and all params ${allParams}` 28 | ) 29 | }, 30 | }, 31 | } 32 | ) 33 | 34 | // Creating serializer from 35 | const parsedItem = Item({ 36 | categoryName: 'cars', 37 | categoryId: '1', 38 | }) 39 | 40 | // Creating blueprint of array of object 41 | const Items = blueprint.array(Item) 42 | 43 | // Creating blueprint of array of plain object 44 | const Color = blueprint.object({ name: types.string }) 45 | const Cars = blueprint.array({ 46 | type: types.string, 47 | brand: types.options({ list: ['Honda', 'Toyota', 'Ford'] }), // options could be a primitive types, blueprint object (not array), with single or multiple (array) values 48 | variant: types.options([Color, Item]), 49 | color: types.options(Color), 50 | }) 51 | 52 | const parsedItems = Items([ 53 | { 54 | categoryName: 'cars', 55 | categoryId: '1', 56 | }, 57 | { 58 | categoryName: 'colors', 59 | categoryId: '2', 60 | }, 61 | ]) 62 | 63 | // Creating more complex blueprint object 64 | const Person = blueprint.object( 65 | { 66 | id: types.number, 67 | name: types.string({ 68 | min: 4, 69 | max: 50, 70 | normalize: 'upper_first_word', 71 | default: 'No Names', 72 | }), 73 | phone: types.ext.phone, 74 | credit_card: types.ext.credit_card, 75 | hobby: types.array(types.string), 76 | someComplexArray: types.array({ 77 | id: types.number, 78 | name: types.string, 79 | default: null, 80 | }), 81 | arrayItemOfObj: types.array(Item, { default: [] }), 82 | arrayItems: Items, 83 | }, 84 | { 85 | frozen: true, // Freeze the returned object 86 | giveWarning: true, // warning on wrong value given 87 | throwOnError: true, // throw error on wrong value given 88 | allowUnknownProperties: false, // no unknow properties given will exist if false 89 | } 90 | ) 91 | 92 | // Condition types 93 | const spicyEvaluator = food => { 94 | const foodEvaluator = { 95 | 'gado-gado': true, 96 | pizza: true, 97 | steak: false, 98 | tomyum: true, 99 | } 100 | 101 | return foodEvaluator[food] 102 | } 103 | 104 | // Boolean types 105 | const Food = blueprint.object({ 106 | isSpicy: types.bool, 107 | isMealFood: types.bool(['Yes', 'Ya', 'Sure']), 108 | isDinnerFood: types.bool(['Yes', 'Sure'], ['No', 'Not']), 109 | isAsianFood: types.bool({ 110 | validList: ['Yes', 'Ya', 'Sure'], 111 | invalidList: ['No', 'Nope', 'Not'], 112 | }), 113 | }) 114 | 115 | const asianFoodEvaluator = (food, evaluatedValue) => { 116 | const asianFoodEvaluator = { 117 | 'gado-gado': true, 118 | pizza: false, 119 | steak: false, 120 | tomyum: true, 121 | } 122 | 123 | return evaluatedValue && asianFoodEvaluator[food] 124 | } 125 | 126 | const Food = blueprint.object({ 127 | name: types.string({ hideOnFail: true }), 128 | isSpicy: types.conditions({ 129 | evaluates: foodEvaluator, 130 | onOk: 'Yes', 131 | onFail: 'No', 132 | }), 133 | isSpicyShorthand: types.conditions(foodEvaluator, 'Yes', 'No'), 134 | isSpicyAndFromAsian: types.conditions({ 135 | evaluates: [foodEvaluator, asianFoodEvaluator], 136 | onOk: 'Yes', 137 | onFail: 'No', 138 | }), 139 | isFoodIsSpicyAndFromAsian: types.conditions({ 140 | evaluates: foodEvaluator, 141 | onOk: types.conditions({ 142 | evaluates: asianFoodEvaluator, 143 | onOk: 'True Asian Spicy Food', 144 | onFail: 'No', 145 | }), 146 | onFail: 'No', 147 | }), 148 | }) 149 | 150 | // Creating fake data 151 | const fakePerson = faker.faking(Person) 152 | const fakeItems = faker.faking(Items) 153 | 154 | // Extending Blueprint object, same property on extend will override parent property 155 | const Mutant = blueprint.extend( 156 | Person, 157 | { 158 | breathOnWater: types.bool, 159 | ability: types.options(['Fly', 'Run Faster', 'Jump High']), 160 | }, 161 | { 162 | giveWarning: false, 163 | throwOnError: false, 164 | }, 165 | { 166 | deleteProperties: ['id', 'hobby'], 167 | } 168 | ) 169 | 170 | // validating with existing blueprint object 171 | const [isError, errorDetails] = validator(Mutant, { 172 | allowUnknownProperties: true, 173 | }).check({ 174 | breathOnWater: 'Not valid value', 175 | }) // return status of validation, not throwing error 176 | validator(Mutant, { allowUnknownProperties: true }).validate({ 177 | breathOnWater: 'Not valid value', 178 | }) // throw an error 179 | 180 | // keyMutation example 181 | const FooBar = blueprint.object({ 182 | id: types.integer({ serialize: { display: false } }), 183 | thing: types.string({ serialize: { to: 'something' } }), 184 | }) 185 | 186 | const foo = FooBar({ id: '343', thing: 'boooo laaa' }) // { id: 343, thing: 'boooo laaa' } 187 | const fooMutated = FooBar.serialize({ id: '343', thing: 'boooo laaa' }) // { something: 'boooo laaa' } 188 | const fooFromMutated = FooBar.deserialize({ something: 'boooo laaa' }) // { thing: 'boooo laaa' } 189 | -------------------------------------------------------------------------------- /spec/spec.md: -------------------------------------------------------------------------------- 1 | # Oh My JS 2 | 3 | ## DataTypes 4 | 5 | * follow jkt 6 | * function for custom / predefined value 7 | 8 | ## Components 9 | 10 | * JS Component (Object & Array) 11 | * Enum 12 | 13 | ## Features 14 | 15 | * Define parser component 16 | * Various Types 17 | * Key-Value Map 18 | * Extended 19 | * Extended with Modify parent 20 | * Check instance of component 21 | * Multi-level parser 22 | * Valid value based on ENUM 23 | * Configurable component validator (default is inactive) 24 | * immutable results 25 | * custom value mapping 26 | * Options to allow unknown keys to be shown -------------------------------------------------------------------------------- /test/able_to_process_class_instance.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require("../blueprint"); 6 | const types = require("../types"); 7 | 8 | test("Should able to process a class instance", () => { 9 | const Obj = blueprint.object( 10 | { 11 | val1: types.any, 12 | val2: types.any() 13 | }, 14 | { allowUnknownProperties: true, frozen: true } 15 | ); 16 | 17 | const Obj2 = blueprint.extend( 18 | Obj, 19 | { 20 | val1: types.any({ allowUndefined: true }), 21 | val2: types.any() 22 | }, 23 | { frozen: true } 24 | ); 25 | 26 | const x = function() {}; 27 | x.prototype.val1 = 1; 28 | x.prototype.val2 = 2; 29 | x.prototype.val3 = 10; 30 | 31 | const nx = new x(); 32 | 33 | expect(Obj(nx)).toEqual({ 34 | val1: 1, 35 | val2: 2 36 | }); 37 | expect(Obj2({ val1: 1, val2: 2, val3: 10 })).toEqual({ 38 | val1: 1, 39 | val2: 2, 40 | val3: 10 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/allow_unknown_properties_extend.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | 8 | test('Should be able to allow unknown properties on extended object', () => { 9 | const Obj = blueprint.object({ 10 | val1: types.any, 11 | val2: types.any(), 12 | }, { allowUnknownProperties: true }); 13 | 14 | const Obj2 = blueprint.extend(Obj, { 15 | val1: types.any({ allowUndefined: true }), 16 | val2: types.any(), 17 | }) 18 | 19 | expect(Obj({ val1: 1, val2: 2, val3: 10 })).toEqual({ 20 | val1: 1, 21 | val2: 2, 22 | val3: 10 23 | }) 24 | expect(Obj2({ val1: 1, val2: 2, val3: 10 })).toEqual({ 25 | val1: 1, 26 | val2: 2, 27 | val3: 10 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/any_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | 6 | test('Object able to serialize and deserialize', () => { 7 | const Obj1 = blueprint.object({ 8 | good: types.any({ serialize: { to: 'good_status' } }) 9 | }); 10 | 11 | expect(Obj1.serialize({ good: true })).toEqual({ good_status: true }); 12 | expect(Obj1.deserialize({ good_status: 'Yes' })).toEqual({ good: 'Yes' }); 13 | expect(Obj1.deserialize({})).toEqual({ good: null }); 14 | }); 15 | 16 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 17 | const Obj1 = blueprint.object({ 18 | good: types.any({ 19 | serialize: { to: 'good_thing' }, 20 | deserialize: { from: 'from_a_good_thing' } 21 | }) 22 | }); 23 | 24 | expect(Obj1.serialize({ good: 'meh' })).toEqual({ good_thing: 'meh' }); 25 | expect(Obj1.serialize({ good: true })).toEqual({ good_thing: true }); 26 | expect(Obj1.deserialize({ good_thing: 'meh' })).toEqual({ good: null }); 27 | expect(Obj1.deserialize({ from_a_good_thing: 'meh' })).toEqual({ 28 | good: 'meh' 29 | }); 30 | expect(Obj1.deserialize({ from_a_good_thing: true })).toEqual({ good: true }); 31 | }); 32 | 33 | test('Object able to hide on serialize', () => { 34 | const Obj1 = blueprint.object({ 35 | good: types.any({ 36 | serialize: { to: 'good_thing' } 37 | }), 38 | best: types.any({ serialize: { display: false } }) 39 | }); 40 | 41 | expect(Obj1.serialize({ good: 'Foo-Bar', best: 'foo' })).toEqual({ 42 | good_thing: 'Foo-Bar' 43 | }); 44 | expect(Obj1.serialize({ good: true, best: 'bar' })).toEqual({ 45 | good_thing: true 46 | }); 47 | expect(Obj1({ good: 123, best: 'foo' })).toEqual({ 48 | good: 123, 49 | best: 'foo' 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/any_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | 8 | test('Should be able to use any type', () => { 9 | const Obj = blueprint.object({ 10 | val1: types.any, 11 | val2: types.any(), 12 | }) 13 | 14 | const Obj2 = blueprint.object({ 15 | val1: types.any({ allowUndefined: true }), 16 | val2: types.any(), 17 | }) 18 | 19 | expect(Obj({ val1: true, val2: null })).toEqual({ 20 | val1: true, 21 | val2: null, 22 | }) 23 | 24 | expect(Obj({ val1: undefined, val2: 'Foo' })).toEqual({ 25 | val1: null, 26 | val2: 'Foo', 27 | }) 28 | 29 | let undef 30 | expect(Obj2({ val1: undef, val2: 'Bar' })).toEqual({ 31 | val1: undefined, 32 | val2: 'Bar', 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/boolean_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | 6 | test('Object able to serialize and deserialize', () => { 7 | const Obj1 = blueprint.object({ 8 | good: types.bool({ serialize: { to: 'good_status' } }) 9 | }); 10 | 11 | expect(Obj1.serialize({ good: true })).toEqual({ good_status: true }); 12 | expect(Obj1.deserialize({ good_status: '20' })).toEqual({ good: null }); 13 | expect(Obj1.deserialize({ good_status: false })).toEqual({ good: false }); 14 | }); 15 | 16 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 17 | const Obj1 = blueprint.object({ 18 | good: types.bool({ 19 | serialize: { to: 'good_thing' }, 20 | deserialize: { from: 'from_a_good_thing' } 21 | }) 22 | }); 23 | 24 | const Obj2 = blueprint.object({ 25 | good: types.bool({ 26 | validList: ['Yes', 'Sure'], 27 | serialize: { to: 'best' } 28 | }) 29 | }); 30 | 31 | const Obj3 = blueprint.object({ 32 | good: types.bool({ 33 | validList: ['Yes', 'Sure'], 34 | serialize: { to: 'best' }, 35 | caseSensitive: false 36 | }) 37 | }); 38 | 39 | expect(Obj1.serialize({ good: 'meh' })).toEqual({ good_thing: null }); 40 | expect(Obj1.serialize({ good: true })).toEqual({ good_thing: true }); 41 | expect(Obj1.deserialize({ good_thing: 'meh' })).toEqual({ good: null }); 42 | expect(Obj1.deserialize({ from_a_good_thing: 'meh' })).toEqual({ 43 | good: null 44 | }); 45 | expect(Obj1.deserialize({ from_a_good_thing: true })).toEqual({ good: true }); 46 | 47 | expect(Obj2.serialize({ good: 'Yes' })).toEqual({ best: true }); 48 | expect(Obj2.serialize({ good: 'Yeah' })).toEqual({ best: false }); 49 | expect(Obj2.serialize({ good: 'yes' })).toEqual({ best: false }); 50 | 51 | expect(Obj2.deserialize({ best: 'Yes' })).toEqual({ good: true }); 52 | expect(Obj2.deserialize({ best: 'Yeah' })).toEqual({ good: false }); 53 | expect(Obj2.deserialize({ best: 'yes' })).toEqual({ good: false }); 54 | 55 | expect(Obj3.serialize({ good: true })).toEqual({ best: true }); 56 | expect(Obj3.serialize({ good: 'yes' })).toEqual({ best: true }); 57 | expect(Obj3.serialize({ good: 'Yes' })).toEqual({ best: true }); 58 | 59 | expect(Obj3.deserialize({ best: true })).toEqual({ good: true }); 60 | expect(Obj3.deserialize({ best: 'yes' })).toEqual({ good: true }); 61 | expect(Obj3.deserialize({ best: 'Yes' })).toEqual({ good: true }); 62 | }); 63 | 64 | test('Object able to hide on serialize', () => { 65 | const Obj1 = blueprint.object({ 66 | good: types.bool({ 67 | serialize: { to: 'good_thing' }, 68 | normalizeNil: true 69 | }), 70 | best: types.bool({ serialize: { display: false } }) 71 | }); 72 | 73 | expect(Obj1.serialize({ good: 'true', best: 'foo' })).toEqual({ 74 | good_thing: true 75 | }); 76 | expect(Obj1.serialize({ good: true, best: 'bar' })).toEqual({ 77 | good_thing: true 78 | }); 79 | expect(Obj1({ good: 123, best: 'foo' })).toEqual({ 80 | good: true, 81 | best: null 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/boolean_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | 8 | test('Should be able to use boolean type', () => { 9 | const Obj = blueprint.object({ 10 | val1: types.bool, 11 | val2: types.bool(['Yes', 'Yeah']), 12 | }) 13 | 14 | const Obj2 = blueprint.object({ 15 | val1: types.bool(['Yes', 'Yeah'], ['No', 'Not']), 16 | val2: types.bool({ 17 | invalidList: ['No', 'Nope'], 18 | caseSensitive: false, 19 | }), 20 | }) 21 | 22 | const Obj3 = blueprint.object({ 23 | val1: types.bool, 24 | val2: types.bool(['Yes', 'Yeah'], { normalizeNil: true }), 25 | val3: types.bool({ normalizeNil: true }), 26 | }) 27 | 28 | const throwError = () => { 29 | blueprint.object({ 30 | value: types.bool([]), 31 | }) 32 | } 33 | 34 | expect(Obj({ val1: true, val2: 'Yes' })).toEqual({ 35 | val1: true, 36 | val2: true, 37 | }) 38 | 39 | expect(Obj({ val1: false, val2: 'No' })).toEqual({ 40 | val1: false, 41 | val2: false, 42 | }) 43 | 44 | expect(Obj2({ val1: 'Yeah', val2: 'sure' })).toEqual({ 45 | val1: true, 46 | val2: true, 47 | }) 48 | 49 | expect(Obj3({ val1: 'not nil', val2: 'Yes', val3: 'not nil' })).toEqual({ 50 | val1: null, 51 | val2: true, 52 | val3: true, 53 | }) 54 | 55 | expect(Obj3({ val1: true, val2: 'Yeah', val3: 123 })).toEqual({ 56 | val1: true, 57 | val2: true, 58 | val3: true, 59 | }) 60 | 61 | expect(Obj3({ val1: false, val2: 'foo', val3: null })).toEqual({ 62 | val1: false, 63 | val2: false, 64 | val3: false, 65 | }) 66 | 67 | expect(Obj3({ val1: true, val2: 'bar' })).toEqual({ 68 | val1: true, 69 | val2: false, 70 | val3: false, 71 | }) 72 | 73 | expect(throwError).toThrow(TypeError('Invalid setup for "bool" type')) 74 | }) 75 | -------------------------------------------------------------------------------- /test/boolean_validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | const PodengError = require('../validator/errors/PodengError'); 6 | const Validator = require('../validator'); 7 | 8 | test('Throw error when throw options set via non Validator', () => { 9 | const Obj1 = blueprint.object( 10 | { 11 | key: types.bool, 12 | key2: types.bool(['Yes', 'True', 'Yup']) 13 | }, 14 | { 15 | throwOnError: true, 16 | giveWarning: true 17 | } 18 | ); 19 | 20 | const assignWrongValue = () => { 21 | Obj1({ key: 'Blue', key2: 123 }); 22 | }; 23 | 24 | expect(assignWrongValue).toThrow(PodengError); 25 | }); 26 | 27 | test('Throw error when validate params via Validator', () => { 28 | const Obj1 = blueprint.object({ 29 | key: types.bool, 30 | key2: types.bool 31 | }); 32 | 33 | const validator = Validator(Obj1); 34 | 35 | const validate = () => { 36 | validator.validate({ key: 'Blue', key2: 123 }); 37 | }; 38 | 39 | expect(validate).toThrow(PodengError); 40 | }); 41 | 42 | test('Returns error details when checking params via Validator', () => { 43 | const Obj1 = blueprint.object({ 44 | key: types.bool, 45 | key2: types.bool 46 | }); 47 | 48 | const validator = Validator(Obj1); 49 | 50 | const [err, errDetails] = validator.check({ 51 | key: '100.34', 52 | key2: () => {} 53 | }); 54 | 55 | expect(err).toBe(true); 56 | expect(errDetails).toEqual({ 57 | key: ['failed to parse "key" with its type'], 58 | key2: ['failed to parse "key2" with its type'] 59 | }); 60 | }); 61 | 62 | test('Able to validate using object serialize params', () => { 63 | const Obj1 = blueprint.object({ 64 | key: types.bool({ serialize: { to: 'key1' } }), 65 | key2: types.bool({ deserialize: { from: 'key_2' } }), 66 | key3: types.bool 67 | }); 68 | 69 | const validator = Validator(Obj1); 70 | const validator2 = Validator(Obj1, { deserialization: true }); 71 | 72 | const throwErr1 = () => { 73 | validator.validate({ 74 | key: 10, 75 | key2: 20, 76 | key3: 'stringss' 77 | }); 78 | }; 79 | 80 | const notThrowErr = () => { 81 | validator2.validate({ 82 | key1: true, 83 | key_2: false, 84 | key3: true 85 | }); 86 | }; 87 | 88 | const [err1, errDetails1] = validator.check({ 89 | key: 10, 90 | key2: true, 91 | key3: '30' 92 | }); 93 | 94 | const [err2, errDetails2] = validator2.check({ 95 | key1: 10, 96 | key_2: '2', 97 | key3: '30' 98 | }); 99 | 100 | expect(throwErr1).toThrow(PodengError); 101 | expect(notThrowErr).not.toThrow(PodengError); 102 | 103 | expect(err1).toBe(true); 104 | expect(errDetails1).toEqual({ 105 | key: ['failed to parse "key" with its type'], 106 | key3: ['failed to parse "key3" with its type'] 107 | }); 108 | 109 | expect(err2).toBe(true); 110 | expect(errDetails2).toEqual({ 111 | key1: ['failed to deserialize from "key1" to "key" with its type'], 112 | key_2: ['failed to deserialize from "key_2" to "key2" with its type'], 113 | key3: ['failed to deserialize from "key3" to "key3" with its type'] 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/conditions.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | 8 | test('Should be able to use condition type', () => { 9 | const Schema = blueprint.object({ 10 | summary: types.conditions(value => value >= 17, 'Adult', 'Child'), 11 | pass: types.conditions({ 12 | evaluates: birthYear => new Date().getFullYear() - birthYear > 17, 13 | onOk: 'Yes you pass', 14 | onFail: 'You Fail', 15 | }), 16 | }) 17 | 18 | const Schema2 = blueprint.object({ 19 | evaluate: types.conditions( 20 | value => { 21 | const v = 'wrong evaluator' 22 | }, 23 | 'Foo', 24 | 'Bar' 25 | ), 26 | }) 27 | 28 | const throwError = () => { 29 | blueprint.object({ 30 | value: types.conditions([]), 31 | }) 32 | } 33 | 34 | expect(Schema({ summary: 10, pass: 1991 })).toEqual({ 35 | summary: 'Child', 36 | pass: 'Yes you pass', 37 | }) 38 | 39 | expect(Schema({ summary: 27, pass: 2015 })).toEqual({ 40 | summary: 'Adult', 41 | pass: 'You Fail', 42 | }) 43 | 44 | expect(Schema2({ evaluate: 'sxx' })).toEqual({ evaluate: 'Bar' }) 45 | 46 | expect(throwError).toThrow(TypeError('Invalid setup for "conditions" type')) 47 | }) 48 | 49 | test('Should be able to use multiple condition level', () => { 50 | const Schema = blueprint.object({ 51 | age: types.conditions( 52 | age => age >= 17, 53 | types.conditions( 54 | age => age >= 30, 55 | 'You should find your love', 56 | 'Just having fun right now' 57 | ), 58 | 'Child' 59 | ), 60 | }) 61 | 62 | const SchemaClone1 = blueprint.object({ 63 | age: types.conditions({ 64 | evaluates: age => age >= 17, 65 | onOk: types.conditions({ 66 | evaluates: age => age >= 30, 67 | onOk: 'You should find your love', 68 | onFail: 'Just having fun right now', 69 | }), 70 | onFail: 'Child', 71 | }), 72 | }) 73 | 74 | const Schema2 = blueprint.object({ 75 | age: types.conditions( 76 | age => age >= 17, 77 | types.conditions( 78 | age => age >= 30, 79 | types.conditions( 80 | age => age >= 60, 81 | 'Take a rest', 82 | 'You should find your love' 83 | ), 84 | 'Just having fun right now' 85 | ), 86 | 'Child' 87 | ), 88 | }) 89 | 90 | const SchemaClone2 = blueprint.object({ 91 | age: types.conditions({ 92 | evaluates: age => age >= 17, 93 | onOk: types.conditions( 94 | age => age >= 30, 95 | types.conditions( 96 | age => age >= 60, 97 | 'Take a rest', 98 | 'You should find your love' 99 | ), 100 | 'Just having fun right now' 101 | ), 102 | onFail: 'Child', 103 | }), 104 | }) 105 | 106 | const Schema3 = blueprint.object({ 107 | age: types.conditions( 108 | age => age <= 17, 109 | 'Explore the world Child!', 110 | types.conditions( 111 | age => age >= 30, 112 | 'find your love', 113 | types.conditions( 114 | age => age === 27, 115 | 'Build a startup!', 116 | 'Do whatever you like' 117 | ) 118 | ) 119 | ), 120 | }) 121 | 122 | const SchemaClone3 = blueprint.object({ 123 | age: types.conditions( 124 | age => age <= 17, 125 | 'Explore the world Child!', 126 | types.conditions( 127 | age => age >= 30, 128 | 'find your love', 129 | types.conditions({ 130 | evaluates: age => age === 27, 131 | onOk: 'Build a startup!', 132 | onFail: 'Do whatever you like', 133 | }) 134 | ) 135 | ), 136 | }) 137 | 138 | expect(Schema({ age: 35 })).toEqual({ age: 'You should find your love' }) 139 | expect(Schema({ age: 20 })).toEqual({ age: 'Just having fun right now' }) 140 | expect(Schema({ age: 15 })).toEqual({ age: 'Child' }) 141 | expect(SchemaClone1({ age: 35 })).toEqual({ 142 | age: 'You should find your love', 143 | }) 144 | expect(SchemaClone1({ age: 20 })).toEqual({ 145 | age: 'Just having fun right now', 146 | }) 147 | expect(SchemaClone1({ age: 15 })).toEqual({ age: 'Child' }) 148 | 149 | expect(Schema2({ age: 35 })).toEqual({ age: 'You should find your love' }) 150 | expect(Schema2({ age: 65 })).toEqual({ age: 'Take a rest' }) 151 | expect(SchemaClone2({ age: 35 })).toEqual({ 152 | age: 'You should find your love', 153 | }) 154 | expect(SchemaClone2({ age: 65 })).toEqual({ age: 'Take a rest' }) 155 | 156 | expect(Schema3({ age: 17 })).toEqual({ age: 'Explore the world Child!' }) 157 | expect(Schema3({ age: 27 })).toEqual({ age: 'Build a startup!' }) 158 | expect(Schema3({ age: 42 })).toEqual({ age: 'find your love' }) 159 | expect(SchemaClone3({ age: 17 })).toEqual({ age: 'Explore the world Child!' }) 160 | expect(SchemaClone3({ age: 27 })).toEqual({ age: 'Build a startup!' }) 161 | expect(SchemaClone3({ age: 42 })).toEqual({ age: 'find your love' }) 162 | }) 163 | -------------------------------------------------------------------------------- /test/conditions_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | 8 | test('Should be able to use serialize with condition', () => { 9 | const Person = blueprint.object({ 10 | age: types.conditions({ 11 | evaluates: age => age >= 17, 12 | onOk: 'Adult', 13 | onFail: 'Child', 14 | serialize: { 15 | to: 'person_age', 16 | }, 17 | }), 18 | }) 19 | 20 | expect(Person({ age: 35 })).toEqual({ age: 'Adult' }) 21 | expect(Person.serialize({ age: 20 })).toEqual({ person_age: 'Adult' }) 22 | expect(Person.serialize({ age: 15 })).toEqual({ person_age: 'Child' }) 23 | }) 24 | 25 | test('Should be able to use deserialize with condition', () => { 26 | const Person = blueprint.object({ 27 | age: types.conditions({ 28 | evaluates: age => age >= 17, 29 | onOk: 'Adult', 30 | onFail: 'Child', 31 | deserialize: { 32 | from: 'the_age', 33 | }, 34 | }), 35 | }) 36 | 37 | expect(Person({ age: 35 })).toEqual({ age: 'Adult' }) 38 | expect(Person.serialize({ age: 20 })).toEqual({ age: 'Adult' }) 39 | expect(Person.serialize({ age: 15 })).toEqual({ age: 'Child' }) 40 | expect(Person.deserialize({ the_age: 15 })).toEqual({ age: 'Child' }) 41 | expect(Person.deserialize({ the_age: 20 })).toEqual({ age: 'Adult' }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/datetime_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const moment = require('moment-timezone'); 4 | const blueprint = require('../blueprint'); 5 | const types = require('../types'); 6 | 7 | moment.tz.setDefault('Asia/Jakarta'); 8 | 9 | test('Object able to serialize and deserialize', () => { 10 | const Obj1 = blueprint.object({ 11 | date: types.datetime({ serialize: { to: 'happy_date' } }) 12 | }); 13 | 14 | expect(Obj1.serialize({ date: '1991-06-18' })).toEqual({ 15 | happy_date: '1991-06-18T00:00:00+07:00' 16 | }); 17 | expect(Obj1.deserialize({ happy_date: '1991-06-18' })).toEqual({ 18 | date: '1991-06-18T00:00:00+07:00' 19 | }); 20 | expect(Obj1.deserialize({ happy_date: '1991-06-18s' })).toEqual({ 21 | date: null 22 | }); 23 | }); 24 | 25 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 26 | const Obj1 = blueprint.object({ 27 | date: types.datetime({ 28 | serialize: { to: 'happy_date' }, 29 | deserialize: { from: 'what_a_date' }, 30 | timezoneAware: false 31 | }) 32 | }); 33 | 34 | const Obj2 = blueprint.object({ 35 | date: types.datetime({ 36 | dateOnly: true, 37 | serialize: { to: 'good_day' } 38 | }) 39 | }); 40 | 41 | const Obj3 = blueprint.object({ 42 | date: types.datetime({ 43 | returnFormat: 'DD-MM-YYYY HH' 44 | }) 45 | }); 46 | 47 | expect(Obj1.serialize({ date: '1991-06-18' })).toEqual({ 48 | happy_date: '1991-06-18T00:00:00Z' 49 | }); 50 | expect(Obj1.serialize({ date: '1991-06-17s' })).toEqual({ happy_date: null }); 51 | expect(Obj1.deserialize({ what_a_date: '1991-06-07' })).toEqual({ 52 | date: '1991-06-07T00:00:00Z' 53 | }); 54 | expect(Obj1.deserialize({ what_a_date: 'meh' })).toEqual({ 55 | date: null 56 | }); 57 | expect(Obj1.deserialize({ what_a_date: '1991-06-18 18:08:08' })).toEqual({ 58 | date: '1991-06-18T18:08:08Z' 59 | }); 60 | 61 | expect(Obj2.serialize({ date: '1992-05-31 18:08:31' })).toEqual({ 62 | good_day: '1992-05-31' 63 | }); 64 | expect(Obj2.serialize({ date: 'meh' })).toEqual({ good_day: null }); 65 | expect(Obj2.serialize({ date: '1992-05-31' })).toEqual({ 66 | good_day: '1992-05-31' 67 | }); 68 | 69 | expect(Obj2.deserialize({ good_day: '1992-05-31' })).toEqual({ 70 | date: '1992-05-31' 71 | }); 72 | expect(Obj2.deserialize({ good_day: '1992-xx-05-31' })).toEqual({ 73 | date: null 74 | }); 75 | expect(Obj2.deserialize({ good_day: '1992-05-31 18:08:31' })).toEqual({ 76 | date: '1992-05-31' 77 | }); 78 | 79 | expect(Obj3.serialize({ date: '1991-06-18' })).toEqual({ 80 | date: '18-06-1991 00' 81 | }); 82 | expect(Obj3.serialize({ date: '1992-05-31 18:08:31' })).toEqual({ 83 | date: '31-05-1992 18' 84 | }); 85 | expect(Obj3.serialize({ date: '1992-05-3111' })).toEqual({ date: null }); 86 | 87 | expect(Obj3.deserialize({ date: '1991-06-18' })).toEqual({ 88 | date: '18-06-1991 00' 89 | }); 90 | expect(Obj3.deserialize({ date: '1992-05-31 18:08:31' })).toEqual({ 91 | date: '31-05-1992 18' 92 | }); 93 | expect(Obj3.deserialize({ date: '1992-05-3111' })).toEqual({ date: null }); 94 | }); 95 | 96 | test('Object able to hide on serialize', () => { 97 | const Obj1 = blueprint.object({ 98 | date: types.datetime({ 99 | serialize: { to: 'good_day' } 100 | }), 101 | date2: types.datetime({ serialize: { display: false } }) 102 | }); 103 | 104 | expect(Obj1.serialize({ date: '1991-06-18', date2: '1991-06-18' })).toEqual({ 105 | good_day: '1991-06-18T00:00:00+07:00' 106 | }); 107 | expect(Obj1.serialize({ date: '1991x-06-18', date2: '1991-06-18' })).toEqual({ 108 | good_day: null 109 | }); 110 | expect(Obj1({ date: '1991-06-18', date2: '1991-06-18' })).toEqual({ 111 | date: '1991-06-18T00:00:00+07:00', 112 | date2: '1991-06-18T00:00:00+07:00' 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/datetime_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const moment = require('moment-timezone') 6 | const blueprint = require('../blueprint') 7 | const types = require('../types') 8 | 9 | moment.tz.setDefault('Asia/Jakarta') 10 | 11 | test('Should be able to use datetime type', () => { 12 | const Obj = blueprint.object({ 13 | val1: types.datetime, 14 | val2: types.datetime('DD-MM-YYYY'), 15 | }) 16 | 17 | const Obj2 = blueprint.object({ 18 | val1: types.datetime({ parseFormat: 'DD-MM-YYYY' }), 19 | val2: types.datetime({ returnFormat: 'DD-MM-YYYY' }), 20 | }) 21 | 22 | const Obj3 = blueprint.object({ 23 | val1: types.datetime({ timezoneAware: false }), 24 | val2: types.datetime({ asMoment: true }), 25 | }) 26 | 27 | const Obj4 = blueprint.object({ 28 | val1: types.datetime({ dateOnly: true }), 29 | val2: types.datetime({ timeOnly: true }), 30 | }) 31 | 32 | const Obj5 = blueprint.object({ 33 | val1: types.datetime({ dateOnly: 'DD-MM-YYYY' }), 34 | val2: types.datetime({ timeOnly: 'H:m:s' }), 35 | }) 36 | 37 | const throwError = () => { 38 | blueprint.object({ 39 | value: types.datetime([]), 40 | }) 41 | } 42 | 43 | expect(Obj({ val1: true, val2: 'Yes' })).toEqual({ 44 | val1: null, 45 | val2: null, 46 | }) 47 | 48 | expect(Obj({ val1: '2018-06-18', val2: '18-06-1991' })).toEqual({ 49 | val1: '2018-06-18T00:00:00+07:00', 50 | val2: '1991-06-18T00:00:00+07:00', 51 | }) 52 | 53 | expect(Obj2({ val1: '18-06-1991', val2: '2018-06-18' })).toEqual({ 54 | val1: '1991-06-18T00:00:00+07:00', 55 | val2: '18-06-2018', 56 | }) 57 | 58 | const obj3Inst = Obj3({ val1: '1991-06-18', val2: '2018-06-18' }) 59 | 60 | expect(obj3Inst.val1).toEqual('1991-06-18T00:00:00Z') 61 | expect(obj3Inst.val2).toBeInstanceOf(moment) 62 | 63 | expect(Obj4({ val1: '1991-06-18', val2: '2018-06-18 18:18:18' })).toEqual({ 64 | val1: '1991-06-18', 65 | val2: '18:18:18', 66 | }) 67 | 68 | expect(Obj5({ val1: '1991-06-18', val2: '2018-06-18 09:30:05' })).toEqual({ 69 | val1: '18-06-1991', 70 | val2: '9:30:5', 71 | }) 72 | 73 | expect(throwError).toThrow(TypeError('Invalid setup for "date" type')) 74 | }) 75 | 76 | test('Ignore null value', () => { 77 | const Obj = blueprint.object({ 78 | value: types.datetime, 79 | }) 80 | 81 | expect(Obj({ value: null })).toEqual({ value: null }) 82 | }) -------------------------------------------------------------------------------- /test/datetime_validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | const PodengError = require('../validator/errors/PodengError'); 6 | const Validator = require('../validator'); 7 | 8 | test('Throw error when throw options set via non Validator', () => { 9 | const Obj1 = blueprint.object( 10 | { 11 | key: types.datetime, 12 | key2: types.datetime('DD-MM-YYYY') 13 | }, 14 | { 15 | throwOnError: true, 16 | giveWarning: true 17 | } 18 | ); 19 | 20 | const assignWrongValue = () => { 21 | Obj1({ key: '1991-06-18', key2: '1991-06-18' }); 22 | console.log(Obj1({ key: '1991-06-18', key2: '1991-06-18' })); 23 | }; 24 | 25 | expect(assignWrongValue).toThrow(PodengError); 26 | }); 27 | 28 | test('Throw error when validate params via Validator', () => { 29 | const Obj1 = blueprint.object({ 30 | key: types.datetime, 31 | key2: types.datetime 32 | }); 33 | 34 | const validator = Validator(Obj1); 35 | 36 | const validate = () => { 37 | validator.validate({ key: 123, key2: '2017-12-01' }); 38 | }; 39 | 40 | expect(validate).toThrow(PodengError); 41 | }); 42 | 43 | test('Returns error details when checking params via Validator', () => { 44 | const Obj1 = blueprint.object({ 45 | key: types.datetime, 46 | key2: types.datetime 47 | }); 48 | 49 | const validator = Validator(Obj1); 50 | 51 | const [err, errDetails] = validator.check({ 52 | key: 'foo', 53 | key2: 'bar' 54 | }); 55 | 56 | expect(err).toBe(true); 57 | expect(errDetails).toEqual({ 58 | key: ['Unable to parse "key" with value: foo'], 59 | key2: ['Unable to parse "key2" with value: bar'] 60 | }); 61 | }); 62 | 63 | test('Able to validate using object serialize params', () => { 64 | const Obj1 = blueprint.object({ 65 | key: types.datetime({ serialize: { to: 'key1' } }), 66 | key2: types.datetime({ 67 | parseFormat: 'DD-MM-YYYY', 68 | deserialize: { from: 'key_2' } 69 | }), 70 | key3: types.datetime 71 | }); 72 | 73 | const validator = Validator(Obj1); 74 | const validator2 = Validator(Obj1, { deserialization: true }); 75 | 76 | const throwErr1 = () => { 77 | validator.validate({ 78 | key: 123, 79 | key2: '1991-06-18', 80 | key3: 'foo' 81 | }); 82 | }; 83 | 84 | const notThrowErr = () => { 85 | validator2.validate({ 86 | key1: '1991-06-18', 87 | key_2: '18-06-1991', 88 | key3: '1991-06-18' 89 | }); 90 | }; 91 | 92 | const [err1, errDetails1] = validator.check({ 93 | key: 123, 94 | key2: '1991-06-18', 95 | key3: 'foo' 96 | }); 97 | 98 | const [err2, errDetails2] = validator2.check({ 99 | key1: '1991-06-18', 100 | key_2: '1991-06-18', 101 | key3: '1991-06-18' 102 | }); 103 | 104 | expect(throwErr1).toThrow(PodengError); 105 | expect(notThrowErr).not.toThrow(PodengError); 106 | 107 | expect(err1).toBe(true); 108 | expect(errDetails1).toEqual({ 109 | key: ['Unable to parse "key" with value: 123'], 110 | key2: ['Unable to parse "key2" with value: 1991-06-18'], 111 | key3: ['Unable to parse "key3" with value: foo'] 112 | }); 113 | 114 | expect(err2).toBe(true); 115 | expect(errDetails2).toEqual({ 116 | key_2: ['Unable to parse "key_2" with value: 1991-06-18'] 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /test/execute_once_for_transform_in_serialization.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require("../blueprint"); 6 | const types = require("../types"); 7 | 8 | test("Should be executed once for transform function in embed", () => { 9 | const expSchema = blueprint.object({ 10 | id: types.transform(id => { 11 | return "abc" + id; 12 | }) 13 | }); 14 | 15 | const expSchema2 = blueprint.object({ 16 | emb: expSchema 17 | }); 18 | 19 | const res = expSchema2({ 20 | emb: { 21 | id: 123 22 | } 23 | }); 24 | 25 | expect(res.emb.id).toEqual("abc123"); 26 | }); 27 | 28 | test("Should be executed once for transform function in embed in serialization", () => { 29 | const expSchema = blueprint.object({ 30 | id: types.transform(id => { 31 | return "abc" + id; 32 | }) 33 | }); 34 | 35 | const expSchema2 = blueprint.object({ 36 | emb: expSchema 37 | }); 38 | 39 | const expSchema3 = blueprint.array({ 40 | emb: expSchema 41 | }); 42 | 43 | const res = expSchema2.serialize({ 44 | emb: { 45 | id: 123 46 | } 47 | }); 48 | 49 | const res2 = expSchema3.serialize([ 50 | { 51 | emb: { 52 | id: 456 53 | } 54 | } 55 | ]); 56 | 57 | expect(res.emb.id).toEqual("abc123"); 58 | expect(res2[0].emb.id).toEqual("abc456"); 59 | }); 60 | 61 | test("Should be executed once for transform function in embed in deserialization", () => { 62 | const expSchema = blueprint.object({ 63 | id: types.transform( 64 | id => { 65 | return "abc" + id; 66 | }, 67 | { deserialize: { from: "number" } } 68 | ) 69 | }); 70 | 71 | const expSchema2 = blueprint.object({ 72 | emb: expSchema 73 | }); 74 | 75 | const expSchema3 = blueprint.array({ 76 | emb: expSchema 77 | }); 78 | 79 | const res = expSchema2.deserialize({ 80 | emb: { 81 | number: 123 82 | } 83 | }); 84 | 85 | const res2 = expSchema3.deserialize([ 86 | { 87 | emb: { 88 | number: 456 89 | } 90 | } 91 | ]); 92 | 93 | expect(res.emb.id).toEqual("abc123"); 94 | expect(res2[0].emb.id).toEqual("abc456"); 95 | }); 96 | -------------------------------------------------------------------------------- /test/extend.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { BlueprintClass: blueprintClass } = require('../blueprint'); 4 | const blueprint = require('../blueprint'); 5 | const types = require('../types'); 6 | const { keys } = require('lodash'); 7 | 8 | test('Create extensible blueprint object', () => { 9 | const Car = blueprint.object({ 10 | color: types.string, 11 | wheels: types.string 12 | }); 13 | 14 | const Bus = blueprint.extend(Car, { 15 | brand: types.string, 16 | length: types.string 17 | }); 18 | 19 | expect(Bus.getInstance() instanceof blueprintClass).toBe(true); 20 | expect(keys(Bus.getParams())).toEqual(['color', 'wheels', 'brand', 'length']); 21 | expect( 22 | Bus({ 23 | color: 'Blue', 24 | wheels: 'Bridgestone', 25 | brand: 'Mercedes Benz', 26 | length: '20 meters' 27 | }) 28 | ).toEqual({ 29 | color: 'Blue', 30 | wheels: 'Bridgestone', 31 | brand: 'Mercedes Benz', 32 | length: '20 meters' 33 | }); 34 | }); 35 | 36 | test('Create extensible blueprint object with deleted properties', () => { 37 | const Animal = blueprint.object({ 38 | skin: types.string, 39 | height: types.string, 40 | habitat: types.string 41 | }); 42 | 43 | const Human = blueprint.extend( 44 | Animal, 45 | { 46 | talking: types.string 47 | }, 48 | {}, 49 | { deleteProperties: ['habitat'] } 50 | ); 51 | 52 | expect(keys(Human.getParams())).toEqual(['skin', 'height', 'talking']); 53 | }); 54 | 55 | test('Create extensible blueprint array object', () => { 56 | const Animal = blueprint.object({ 57 | skin: types.string, 58 | height: types.string, 59 | habitat: types.string 60 | }); 61 | 62 | const Animals = blueprint.array(Animal); 63 | 64 | const notThrowError = () => { 65 | blueprint.extend(Animals, { 66 | talking: types.string 67 | }); 68 | }; 69 | 70 | expect(notThrowError).not.toThrowError( 71 | 'To extend you must pass blueprint object, not blueprint array!' 72 | ); 73 | }); 74 | -------------------------------------------------------------------------------- /test/extend_on_array.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | 8 | test('Should be able to extend array object', () => { 9 | const Obj = blueprint.array({ 10 | val1: types.integer, 11 | val2: types.integer, 12 | }, { allowUnknownProperties: true }); 13 | 14 | const Obj2 = blueprint.extend(Obj, { 15 | val1: types.float, 16 | val2: types.float, 17 | }) 18 | 19 | const throwError = () => { 20 | blueprint.extend(Obj, { 21 | val1: types.any, 22 | val2: types.any, 23 | }) 24 | } 25 | 26 | expect(Obj([{ val1: 1, val2: 2, val3: 10 }])).toEqual([{ 27 | val1: 1, 28 | val2: 2, 29 | val3: 10 30 | }]) 31 | expect(Obj2([{ val1: 1.03, val2: 2.34, val3: 10.08 }])).toEqual([{ 32 | val1: 1.03, 33 | val2: 2.34, 34 | val3: 10.08 35 | }]) 36 | expect(throwError).not.toThrow(TypeError('To extend you must pass blueprint object, not blueprint array!')) 37 | }) 38 | -------------------------------------------------------------------------------- /test/float_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | 6 | test('Object able to serialize and deserialize', () => { 7 | const Obj1 = blueprint.object({ 8 | num: types.float({ serialize: { to: 'number' } }) 9 | }); 10 | 11 | expect(Obj1.serialize({ num: '10.10' })).toEqual({ number: 10.10 }); 12 | expect(Obj1.deserialize({ number: '20' })).toEqual({ num: 20 }); 13 | expect(Obj1.deserialize({ number: '20.40' })).toEqual({ num: 20.40 }); 14 | }); 15 | 16 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 17 | const Obj1 = blueprint.object({ 18 | num: types.float({ 19 | serialize: { to: 'number' }, 20 | deserialize: { from: 'a_number' } 21 | }) 22 | }); 23 | 24 | const Obj2 = blueprint.object({ 25 | num: types.float({ 26 | min: 100, 27 | serialize: { to: 'number' } 28 | }) 29 | }); 30 | 31 | expect(Obj1.serialize({ num: '20' })).toEqual({ number: 20 }); 32 | expect(Obj1.serialize({ num: '20.50' })).toEqual({ number: 20.50 }); 33 | expect(Obj1.deserialize({ a_number: '50.32' })).toEqual({ num: 50.32 }); 34 | expect(Obj2.deserialize({ number: 200 })).toEqual({ num: 200 }); 35 | expect(Obj2.deserialize({ number: 200.55 })).toEqual({ num: 200.55 }); 36 | expect(Obj2.deserialize({ number: 10 })).toEqual({ num: null }); 37 | expect(Obj2.deserialize({ number: 99.99 })).toEqual({ num: null }); 38 | }); 39 | 40 | test('Object able to hide on serialize', () => { 41 | const Obj1 = blueprint.object({ 42 | num: types.float({ 43 | serialize: { to: 'a_number' } 44 | }), 45 | num2: types.float({ serialize: { display: false } }) 46 | }); 47 | 48 | expect(Obj1.serialize({ num: 30, num2: '100' })).toEqual({ 49 | a_number: 30 50 | }); 51 | expect(Obj1.serialize({ num: 50.30, num2: '100' })).toEqual({ 52 | a_number: 50.30 53 | }); 54 | expect(Obj1({ num: 30.22, num2: '100.123' })).toEqual({ 55 | num: 30.22, 56 | num2: 100.123 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/float_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | const PodengError = require('../validator/errors/PodengError') 8 | 9 | test('Object include float type', () => { 10 | const Obj = blueprint.object({ 11 | num: types.float, 12 | }) 13 | 14 | expect(typeof Obj).toEqual('function') 15 | }) 16 | 17 | test('Validate value on wrong type passed', () => { 18 | const Obj = blueprint.object({ 19 | num: types.float({ hideOnFail: true }), 20 | num2: types.float, 21 | }) 22 | 23 | const Obj2 = blueprint.object({ 24 | num: types.float({ min: 5.5 }), 25 | }) 26 | 27 | const Obj3 = blueprint.object({ 28 | num: types.float({ max: 100.050 }), 29 | }) 30 | 31 | const Obj4 = blueprint.object({ 32 | num: types.float({ min: 5.045, max: 17.5, default: 18.3 }), 33 | }) 34 | 35 | const Obj5 = blueprint.object({ 36 | number: types.float({ 37 | minDigits: 2, 38 | maxDigits: 3, 39 | }), 40 | }) 41 | 42 | expect(Obj({ num: {}, num2: '30.059' })).toEqual({ num2: 30.059 }) 43 | expect(Obj({ num: {}, num2: '30.000' })).toEqual({ num2: 30 }) 44 | expect(Obj({ num: {}, num2: 30.000 })).toEqual({ num2: 30 }) 45 | expect(Obj({ num: {}, num2: 0 })).toEqual({ num2: 0 }) 46 | expect(Obj2({ num: 20 })).toEqual({ num: 20 }) 47 | expect(Obj2({ num: 20.20 })).toEqual({ num: 20.20 }) 48 | expect(Obj2({ num: 'invalid' })).toEqual({ num: null }) 49 | expect(Obj3({ num: 0 })).toEqual({ num: 0 }) 50 | expect(Obj3({ num: '80.23' })).toEqual({ num: 80.23 }) 51 | expect(Obj3({ num: '180.23' })).toEqual({ num: null }) 52 | expect(Obj3({ num: '10.3235' })).toEqual({ num: 10.3235 }) 53 | expect(Obj4({ num: '5.045' })).toEqual({ num: 5.045 }) 54 | expect(Obj4({ num: '17.5' })).toEqual({ num: 17.5 }) 55 | expect(Obj4({ num: {} })).toEqual({ num: 18.3 }) 56 | expect(Obj4({ num: 5 })).toEqual({ num: 18.3 }) 57 | expect(Obj4({ num: 20 })).toEqual({ num: 18.3 }) 58 | expect(Obj5({ number: '27' })).toEqual({ number: 27 }) 59 | expect(Obj5({ number: '9' })).toEqual({ number: null }) 60 | expect(Obj5({ number: '9.5' })).toEqual({ number: null }) 61 | expect(Obj5({ number: '1000' })).toEqual({ number: null }) 62 | expect(Obj5({ number: '100.04' })).toEqual({ number: 100.04 }) 63 | }) 64 | 65 | test('Object array with float options', () => { 66 | const Obj = blueprint.object({ 67 | value1: types.float, 68 | value2: types.float({ min: 10.18 }), 69 | }) 70 | 71 | const Collections = blueprint.array(Obj) 72 | 73 | expect( 74 | Collections([ 75 | { value1: 33.2, value2: '33' }, 76 | { value1: '10.19', value2: 10.19 }, 77 | { value1: 11, value2: 5 }, 78 | { value1: '4.12', value2: '88.18' }, 79 | ]) 80 | ).toEqual([ 81 | { 82 | value1: 33.2, 83 | value2: 33, 84 | }, 85 | { value1: 10.19, value2: 10.19 }, 86 | { value1: 11, value2: null }, 87 | { value1: 4.12, value2: 88.18 }, 88 | ]) 89 | }) 90 | 91 | test('Object include float with validation', () => { 92 | const Obj1 = blueprint.object( 93 | { 94 | value: types.float, 95 | }, 96 | { throwOnError: true } 97 | ) 98 | 99 | const Obj2 = blueprint.object( 100 | { 101 | value: types.float, 102 | }, 103 | { throwOnError: new TypeError('The Value Error') } 104 | ) 105 | 106 | const Obj3 = blueprint.object( 107 | { 108 | value: types.float, 109 | }, 110 | { onError: TypeError('The Invalid onError value') } 111 | ) 112 | 113 | const Obj4 = blueprint.object( 114 | { 115 | value: types.float, 116 | }, 117 | { 118 | onError: { 119 | onKey: (key, err) => { 120 | throw new TypeError('Error coming from onKey') 121 | }, 122 | }, 123 | } 124 | ) 125 | 126 | const Obj5 = blueprint.object( 127 | { 128 | value: types.float, 129 | }, 130 | { 131 | onError: { 132 | onAll: errors => { 133 | throw new TypeError('Error coming from onAll') 134 | }, 135 | }, 136 | } 137 | ) 138 | 139 | const Obj6 = blueprint.object({ 140 | someKey: types.float({ min: 'abc' }), 141 | }) 142 | const Obj7 = blueprint.object({ 143 | someKey: types.float({ max: 'abc' }), 144 | }) 145 | const Obj8 = blueprint.object({ 146 | someKey: types.float({ minDigits: 'abc' }), 147 | }) 148 | const Obj9 = blueprint.object({ 149 | someKey: types.float({ maxDigits: 'abc' }), 150 | }) 151 | 152 | const willThrow = obj => { 153 | return () => { 154 | obj.call(null, { 155 | value: function () { }, 156 | }) 157 | } 158 | } 159 | 160 | expect(willThrow(Obj1)).toThrow(PodengError) 161 | expect(willThrow(Obj2)).toThrow(TypeError) 162 | expect(willThrow(Obj3)).not.toThrow() 163 | expect(willThrow(Obj4)).toThrow(TypeError('Error coming from onKey')) 164 | expect(willThrow(Obj5)).toThrow(TypeError('Error coming from onAll')) 165 | expect(() => Obj6({ someKey: '123.23' })).toThrow( 166 | TypeError( 167 | 'Float: Invalid "min" option value for someKey, it should be in numeric type!' 168 | ) 169 | ) 170 | expect(() => Obj7({ someKey: '123.23' })).toThrow( 171 | TypeError( 172 | 'Float: Invalid "max" option value for someKey, it should be in numeric type!' 173 | ) 174 | ) 175 | expect(() => Obj8({ someKey: '123.23' })).toThrow( 176 | TypeError( 177 | 'Float: Invalid "minDigits" option value for someKey, it should be in numeric type!' 178 | ) 179 | ) 180 | expect(() => Obj9({ someKey: '123.23' })).toThrow( 181 | TypeError( 182 | 'Float: Invalid "maxDigits" option value for someKey, it should be in numeric type!' 183 | ) 184 | ) 185 | }) 186 | 187 | test('Will validate using custom value', () => { 188 | const Obj = blueprint.object({ 189 | value: types.float({ 190 | validate: val => val > 100, 191 | }), 192 | }) 193 | 194 | const Obj2 = blueprint.object({ 195 | value: types.float({ 196 | validate: val => val !== 1818, 197 | default: () => 9999, 198 | }), 199 | }) 200 | 201 | expect(Obj({ value: '80.23' })).toEqual({ value: null }) 202 | expect(Obj({ value: '220.21' })).toEqual({ value: 220.21 }) 203 | expect(Obj2({ value: 'abc' })).toEqual({ value: 9999 }) 204 | expect(Obj2({ value: 1818 })).toEqual({ value: 9999 }) 205 | }) 206 | 207 | test('Ignore null value', () => { 208 | const Obj = blueprint.object({ 209 | value: types.float, 210 | }) 211 | 212 | expect(Obj({ value: null })).toEqual({ value: null }) 213 | }) -------------------------------------------------------------------------------- /test/float_validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | const PodengError = require('../validator/errors/PodengError'); 6 | const Validator = require('../validator'); 7 | 8 | test('Throw error when throw options set via non Validator', () => { 9 | const Obj1 = blueprint.object( 10 | { 11 | num: types.float, 12 | num2: types.float 13 | }, 14 | { 15 | throwOnError: true, 16 | giveWarning: true 17 | } 18 | ); 19 | 20 | const assignWrongValue = () => { 21 | Obj1({ num: 'Blue', num2: () => {} }); 22 | }; 23 | 24 | expect(assignWrongValue).toThrow(PodengError); 25 | }); 26 | 27 | test('Throw error when validate params via Validator', () => { 28 | const Obj1 = blueprint.object({ 29 | num: types.float, 30 | num2: types.float 31 | }); 32 | 33 | const validator = Validator(Obj1); 34 | 35 | const validate = () => { 36 | validator.validate({ num: 'Blue', num2: () => {} }); 37 | }; 38 | 39 | expect(validate).toThrow(PodengError); 40 | }); 41 | 42 | test('Returns error details when checking params via Validator', () => { 43 | const Obj1 = blueprint.object({ 44 | num: types.float, 45 | num2: types.float 46 | }); 47 | 48 | const validator = Validator(Obj1); 49 | 50 | const [err, errDetails] = validator.check({ 51 | num: '100.34', 52 | num2: () => {} 53 | }); 54 | 55 | expect(err).toBe(true); 56 | expect(errDetails).toEqual({ 57 | num2: ['failed to parse "num2" with its type'] 58 | }); 59 | }); 60 | 61 | test('Able to validate using object serialize params', () => { 62 | const Obj1 = blueprint.object({ 63 | num: types.float({ serialize: { to: 'number1' } }), 64 | num2: types.float({ min: 5.45, deserialize: { from: 'number2' } }), 65 | num3: types.float 66 | }); 67 | 68 | const validator = Validator(Obj1); 69 | const validator2 = Validator(Obj1, { deserialization: true }); 70 | 71 | const throwErr1 = () => { 72 | validator.validate({ 73 | num: 10, 74 | num2: 20, 75 | num3: 'stringss' 76 | }); 77 | }; 78 | 79 | const notThrowErr = () => { 80 | validator2.validate({ 81 | number1: 10.10, 82 | number2: '20.20', 83 | num3: '30.30' 84 | }); 85 | }; 86 | 87 | const [err1, errDetails1] = validator.check({ 88 | num: 10, 89 | num2: 3.3, 90 | num3: '30.5' 91 | }); 92 | 93 | const [err2, errDetails2] = validator2.check({ 94 | number1: 10.11, 95 | number2: '2.2', 96 | num3: '30' 97 | }); 98 | 99 | const [err3, errDetails3] = validator2.check({ 100 | number1: 10, 101 | number2: '11.23', 102 | num3: 'foobar' 103 | }); 104 | 105 | expect(throwErr1).toThrow(PodengError); 106 | expect(notThrowErr).not.toThrow(PodengError); 107 | 108 | expect(err1).toBe(true); 109 | expect(errDetails1).toEqual({ 110 | num2: ['Minimum value of "num2" is 5.45'] 111 | }); 112 | 113 | expect(err2).toBe(true); 114 | expect(errDetails2).toEqual({ 115 | number2: ['Minimum value of "number2" is 5.45'] 116 | }); 117 | 118 | expect(err3).toBe(true); 119 | expect(errDetails3).toEqual({ 120 | num3: ['failed to deserialize from "num3" to "num3" with its type'] 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/handler.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { BlueprintClass: blueprintClass } = require('../blueprint'); 4 | const blueprint = require('../blueprint'); 5 | const types = require('../types'); 6 | 7 | test('Create an instace of blueprint object', () => { 8 | const Car = blueprint.object({ 9 | type: types.string 10 | }); 11 | 12 | expect(typeof Car).toEqual('function'); 13 | expect(Car.getInstance() instanceof blueprintClass).toBe(true); 14 | }); 15 | 16 | test('Create an instance of blueprint array', () => { 17 | const Car = blueprint.object({ 18 | type: types.string 19 | }); 20 | 21 | const Cars = blueprint.array({ type: types.string() }); 22 | const Cars2 = blueprint.array(Car); 23 | 24 | expect(typeof Cars).toEqual('function'); 25 | expect(Cars.getInstance() instanceof blueprintClass).toBe(true); 26 | expect(Cars2.getInstance() instanceof blueprintClass).toBe(true); 27 | }); 28 | 29 | test('Make sure blueprint object working with at least one type', () => { 30 | const Car = blueprint.object({ 31 | type: types.string 32 | }); 33 | const Car2 = blueprint.object({ 34 | type: types.string() 35 | }); 36 | 37 | const Cars = blueprint.array({ type: types.string() }); 38 | const Cars2 = blueprint.array(Car); 39 | 40 | expect(Car({ type: 'Honda' })).toEqual({ type: 'Honda' }); 41 | expect(Car2({ type: 'Honda' })).toEqual({ type: 'Honda' }); 42 | expect(Cars([{ type: 'Honda' }])).toEqual([{ type: 'Honda' }]); 43 | expect(Cars2([{ type: 'Honda' }])).toEqual([{ type: 'Honda' }]); 44 | }); 45 | 46 | test('Frozen and Non frozen object', () => { 47 | const FrozenCar = blueprint.object({ 48 | type: types.string 49 | }, { frozen: true }); 50 | 51 | const Car = blueprint.object( 52 | { 53 | type: types.string 54 | } 55 | ); 56 | 57 | const frozenCar = FrozenCar({ type: 'Honda' }); 58 | const nonFrozenCar = Car({ type: 'Honda' }); 59 | 60 | const executeFrozenObj = () => { 61 | frozenCar.color = 'blue'; 62 | }; 63 | 64 | const executeNonFrozenObj = () => { 65 | nonFrozenCar.color = 'blue'; 66 | }; 67 | 68 | expect(executeFrozenObj).toThrow(); 69 | expect(executeNonFrozenObj).not.toThrow(); 70 | expect(nonFrozenCar).toEqual({ 71 | type: 'Honda', 72 | color: 'blue' 73 | }); 74 | }); 75 | 76 | test('Multi level object', () => { 77 | const Value1 = blueprint.object({ 78 | value: types.string 79 | }); 80 | const Value2 = blueprint.array({ 81 | value: types.string 82 | }); 83 | 84 | const Branch1 = blueprint.object({ 85 | value: Value1 86 | }); 87 | 88 | const BranchMaster1 = blueprint.object({ 89 | branch1: Branch1.embed() 90 | }); 91 | const BranchMaster2 = blueprint.object({ 92 | branch1: Value1 93 | }); 94 | const BranchMaster3 = blueprint.object({ 95 | values: Value2 96 | }); 97 | const BranchMaster4 = blueprint.object({ 98 | values: Value2.embed({ default: 'empty value' }) 99 | }); 100 | 101 | expect( 102 | BranchMaster1({ 103 | branch1: { 104 | value: 'abc' 105 | } 106 | }) 107 | ).toEqual({ branch1: { value: { value: null } } }); 108 | 109 | expect( 110 | BranchMaster2({ 111 | branch1: { value: 'abc' } 112 | }) 113 | ).toEqual({ 114 | branch1: { value: 'abc' } 115 | }); 116 | 117 | expect( 118 | BranchMaster2({ 119 | branch1: 'invalid value' 120 | }) 121 | ).toEqual({ 122 | branch1: { 123 | value: null 124 | } 125 | }); 126 | 127 | expect( 128 | BranchMaster3({ 129 | values: [{ value: 'abc' }, { value: 'cde' }] 130 | }) 131 | ).toEqual({ 132 | values: [{ value: 'abc' }, { value: 'cde' }] 133 | }); 134 | 135 | expect( 136 | BranchMaster3({ 137 | values: 'invalid value' 138 | }) 139 | ).toEqual({ 140 | values: [] 141 | }); 142 | 143 | expect( 144 | BranchMaster4({ 145 | values: 'invalid value' 146 | }) 147 | ).toEqual({ 148 | values: 'empty value' 149 | }); 150 | }); 151 | 152 | test('Allow unknown properties given', () => { 153 | const Object1 = blueprint.object( 154 | { 155 | name: types.string 156 | }, 157 | { 158 | allowUnknownProperties: true 159 | } 160 | ); 161 | 162 | const Object1Collection = blueprint.array(Object1); 163 | const Object2Collection = blueprint.array(Object1, { 164 | allowUnknownProperties: false 165 | }); 166 | 167 | const Object2 = blueprint.object( 168 | { 169 | name: types.string({ 170 | serialize: { to: 'full_name' }, 171 | deserialize: { from: 'username' } 172 | }) 173 | }, 174 | { 175 | allowUnknownProperties: true 176 | } 177 | ); 178 | 179 | const Object3Collection = blueprint.array(Object2); 180 | const Object4Collection = blueprint.array(Object2, { 181 | allowUnknownProperties: false 182 | }); 183 | 184 | const ObjectEmbed = blueprint.object({ 185 | value: types.string, 186 | embed: Object1 187 | }); 188 | 189 | expect(Object1({ name: 'Aditya', hobby: 'coding' })).toEqual({ 190 | name: 'Aditya', 191 | hobby: 'coding' 192 | }); 193 | 194 | expect( 195 | ObjectEmbed({ 196 | value: 'some value', 197 | embed: { name: 'Aditya', hobby: 'coding' } 198 | }) 199 | ).toEqual({ 200 | value: 'some value', 201 | embed: { 202 | name: 'Aditya', 203 | hobby: 'coding' 204 | } 205 | }); 206 | 207 | expect( 208 | ObjectEmbed({ 209 | value: 'some value', 210 | extraValue: 'extra value', 211 | embed: { name: 'Aditya', hobby: 'coding' } 212 | }) 213 | ).toEqual({ 214 | value: 'some value', 215 | embed: { 216 | name: 'Aditya', 217 | hobby: 'coding' 218 | } 219 | }); 220 | 221 | expect(Object1.serialize({ name: 'Aditya', hobby: 'coding' })).toEqual({ 222 | name: 'Aditya', 223 | hobby: 'coding' 224 | }); 225 | 226 | expect(Object1.deserialize({ name: 'Aditya', hobby: 'coding' })).toEqual({ 227 | name: 'Aditya', 228 | hobby: 'coding' 229 | }); 230 | 231 | expect(Object2.serialize({ name: 'Aditya', hobby: 'coding' })).toEqual({ 232 | full_name: 'Aditya', 233 | hobby: 'coding' 234 | }); 235 | 236 | expect(Object2.deserialize({ username: 'Aditya', hobby: 'coding' })).toEqual({ 237 | name: 'Aditya', 238 | hobby: 'coding' 239 | }); 240 | 241 | expect( 242 | Object1Collection([ 243 | { name: 'Aditya', hobby: 'coding' }, 244 | { name: 'Amelia', hobby: 'shopping' } 245 | ]) 246 | ).toEqual([ 247 | { name: 'Aditya', hobby: 'coding' }, 248 | { name: 'Amelia', hobby: 'shopping' } 249 | ]); 250 | 251 | expect( 252 | Object2Collection([ 253 | { name: 'Aditya', hobby: 'coding' }, 254 | { name: 'Amelia', hobby: 'shopping' } 255 | ]) 256 | ).toEqual([{ name: 'Aditya' }, { name: 'Amelia' }]); 257 | 258 | expect( 259 | Object3Collection.serialize([ 260 | { name: 'Aditya', hobby: 'coding' }, 261 | { name: 'Amelia', hobby: 'shopping' } 262 | ]) 263 | ).toEqual([ 264 | { full_name: 'Aditya', hobby: 'coding' }, 265 | { full_name: 'Amelia', hobby: 'shopping' } 266 | ]); 267 | 268 | expect( 269 | Object3Collection.deserialize([ 270 | { username: 'Aditya', hobby: 'coding' }, 271 | { username: 'Amelia', hobby: 'shopping' } 272 | ]) 273 | ).toEqual([ 274 | { name: 'Aditya', hobby: 'coding' }, 275 | { name: 'Amelia', hobby: 'shopping' } 276 | ]); 277 | 278 | expect( 279 | Object4Collection.deserialize([ 280 | { username: 'Aditya', hobby: 'coding' }, 281 | { username: 'Amelia', hobby: 'shopping' } 282 | ]) 283 | ).toEqual([{ name: 'Aditya' }, { name: 'Amelia' }]); 284 | }); 285 | 286 | test('Able to using embeded object with list', () => { 287 | const Person = blueprint.object({ 288 | id: types.integer, 289 | name: types.string, 290 | phone: types.string({ serialize: { to: 'phoneNumber' } }) 291 | }); 292 | 293 | const People = blueprint.array(Person); 294 | 295 | const CustomParser = blueprint.object({ 296 | items: People.embed({ default: [], serialize: { to: 'someItems' }, deserialize: { from: 'data' } }), 297 | total: types.integer({ default: 0 }) 298 | }); 299 | 300 | const serializeCustomParser = () => { 301 | CustomParser.serialize({ 302 | items: [{ id: 1, name: 'Aditya', phone: '+621345869' }], 303 | total: 1 304 | }); 305 | }; 306 | 307 | const deserializeCustomParser = () => { 308 | CustomParser.deserialize({ 309 | data: [{ id: 1, name: 'Aditya', phoneNumber: '+621345869' }], 310 | total: 1 311 | }); 312 | }; 313 | 314 | expect(serializeCustomParser).not.toThrow(); 315 | expect(deserializeCustomParser).not.toThrow(); 316 | expect( 317 | CustomParser({ 318 | items: [{ id: 1, name: 'Aditya', phone: '+621345869' }], 319 | total: 1 320 | }) 321 | ).toEqual({ 322 | items: [{ id: 1, name: 'Aditya', phone: '+621345869' }], 323 | total: 1 324 | }); 325 | expect( 326 | CustomParser.serialize({ 327 | items: [{ id: 1, name: 'Aditya', phone: '+621345869' }], 328 | total: 1 329 | }) 330 | ).toEqual({ 331 | someItems: [{ id: 1, name: 'Aditya', phoneNumber: '+621345869' }], 332 | total: 1 333 | }); 334 | expect( 335 | CustomParser.deserialize({ 336 | data: [{ id: 1, name: 'Aditya', phoneNumber: '+621345869' }], 337 | total: 3 338 | }) 339 | ).toEqual({ 340 | items: [{ id: 1, name: 'Aditya', phone: '+621345869' }], 341 | total: 3 342 | }); 343 | }) 344 | ; 345 | -------------------------------------------------------------------------------- /test/integer_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | 6 | test('Object able to serialize and deserialize', () => { 7 | const Obj1 = blueprint.object({ 8 | num: types.integer({ serialize: { to: 'number' } }) 9 | }); 10 | 11 | expect(Obj1.serialize({ num: '10' })).toEqual({ number: 10 }); 12 | expect(Obj1.deserialize({ number: '20' })).toEqual({ num: 20 }); 13 | }); 14 | 15 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 16 | const Obj1 = blueprint.object({ 17 | num: types.integer({ 18 | serialize: { to: 'number' }, 19 | deserialize: { from: 'a_number' } 20 | }) 21 | }); 22 | 23 | const Obj2 = blueprint.object({ 24 | num: types.integer({ 25 | min: 100, 26 | serialize: { to: 'number' } 27 | }) 28 | }); 29 | 30 | expect(Obj1.serialize({ num: '20' })).toEqual({ number: 20 }); 31 | expect(Obj1.deserialize({ a_number: '50.32' })).toEqual({ num: 50 }); 32 | expect(Obj2.deserialize({ number: 200 })).toEqual({ num: 200 }); 33 | expect(Obj2.deserialize({ number: 10 })).toEqual({ num: null }); 34 | }); 35 | 36 | test('Object able to hide on serialize', () => { 37 | const Obj1 = blueprint.object({ 38 | num: types.integer({ 39 | serialize: { to: 'a_number' } 40 | }), 41 | num2: types.integer({ serialize: { display: false } }) 42 | }); 43 | 44 | expect(Obj1.serialize({ num: 30, num2: '100' })).toEqual({ 45 | a_number: 30 46 | }); 47 | expect(Obj1({ num: 30, num2: '100' })).toEqual({ 48 | num: 30, 49 | num2: 100 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/integer_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | const PodengError = require('../validator/errors/PodengError') 8 | 9 | test('Object include integer type', () => { 10 | const Car = blueprint.object({ 11 | year: types.integer, 12 | }) 13 | 14 | expect(typeof Car).toEqual('function') 15 | }) 16 | 17 | test('Validate value on wrong type passed', () => { 18 | const Car = blueprint.object({ 19 | hp: types.integer({ hideOnFail: true }), 20 | year: types.integer, 21 | }) 22 | 23 | const Person1 = blueprint.object({ 24 | age: types.integer({ min: 1 }), 25 | }) 26 | 27 | const Person2 = blueprint.object({ 28 | age: types.integer({ max: 150 }), 29 | }) 30 | 31 | const Kid = blueprint.object({ 32 | age: types.integer({ min: 5, max: 17, default: 'adult or baby' }), 33 | }) 34 | 35 | const Shoe = blueprint.object({ 36 | number: types.integer({ 37 | minDigits: 2, 38 | maxDigits: 3, 39 | }), 40 | }) 41 | 42 | expect(Car({ hp: {}, year: '2014' })).toEqual({ year: 2014 }) 43 | expect(Person1({ age: 20 })).toEqual({ age: 20 }) 44 | expect(Person1({ age: 'invalid' })).toEqual({ age: null }) 45 | expect(Person2({ age: 0 })).toEqual({ age: 0 }) 46 | expect(Person2({ age: '150' })).toEqual({ age: 150 }) 47 | expect(Person2({ age: '150.005' })).toEqual({ age: null }) 48 | expect(Person2({ age: '140.3235' })).toEqual({ age: 140 }) 49 | expect(Kid({ age: '7' })).toEqual({ age: 7 }) 50 | expect(Kid({ age: 5 })).toEqual({ age: 5 }) 51 | expect(Kid({ age: 20 })).toEqual({ age: 'adult or baby' }) 52 | expect(Shoe({ number: '27' })).toEqual({ number: 27 }) 53 | expect(Shoe({ number: '9' })).toEqual({ number: null }) 54 | }) 55 | 56 | test('Object array with integer options', () => { 57 | const ObjInteger = blueprint.object({ 58 | value1: types.integer, 59 | value2: types.integer({ min: 200 }), 60 | }) 61 | 62 | const Collections = blueprint.array(ObjInteger) 63 | 64 | expect( 65 | Collections([ 66 | { value1: 123, value2: 123 }, 67 | { value1: 456, value2: 456 }, 68 | { value1: '789.12', value2: '789' }, 69 | ]) 70 | ).toEqual([ 71 | { 72 | value1: 123, 73 | value2: null, 74 | }, 75 | { value1: 456, value2: 456 }, 76 | { value1: 789, value2: 789 }, 77 | ]) 78 | }) 79 | 80 | test('Object include integer with validation', () => { 81 | const ObjInteger1 = blueprint.object( 82 | { 83 | value: types.integer, 84 | }, 85 | { throwOnError: true } 86 | ) 87 | 88 | const ObjInteger2 = blueprint.object( 89 | { 90 | value: types.integer, 91 | }, 92 | { throwOnError: new TypeError('The Value Error') } 93 | ) 94 | 95 | const ObjInteger3 = blueprint.object( 96 | { 97 | value: types.integer, 98 | }, 99 | { onError: TypeError('The Invalid onError value') } 100 | ) 101 | 102 | const ObjInteger4 = blueprint.object( 103 | { 104 | value: types.integer, 105 | }, 106 | { 107 | onError: { 108 | onKey: (key, err) => { 109 | throw new TypeError('Error coming from onKey') 110 | }, 111 | }, 112 | } 113 | ) 114 | 115 | const ObjInteger5 = blueprint.object( 116 | { 117 | value: types.integer, 118 | }, 119 | { 120 | onError: { 121 | onAll: errors => { 122 | throw new TypeError('Error coming from onAll') 123 | }, 124 | }, 125 | } 126 | ) 127 | 128 | const ObjInteger6 = blueprint.object({ 129 | someKey: types.integer({ min: 'abc' }), 130 | }) 131 | const ObjInteger7 = blueprint.object({ 132 | someKey: types.integer({ max: 'abc' }), 133 | }) 134 | const ObjInteger8 = blueprint.object({ 135 | someKey: types.integer({ minDigits: 'abc' }), 136 | }) 137 | const ObjInteger9 = blueprint.object({ 138 | someKey: types.integer({ maxDigits: 'abc' }), 139 | }) 140 | 141 | const willThrow = obj => { 142 | return () => { 143 | obj.call(null, { 144 | value: function () { }, 145 | }) 146 | } 147 | } 148 | 149 | expect(willThrow(ObjInteger1)).toThrow(PodengError) 150 | expect(willThrow(ObjInteger2)).toThrow(TypeError) 151 | expect(willThrow(ObjInteger3)).not.toThrow() 152 | expect(willThrow(ObjInteger4)).toThrow(TypeError('Error coming from onKey')) 153 | expect(willThrow(ObjInteger5)).toThrow(TypeError('Error coming from onAll')) 154 | expect(() => ObjInteger6({ someKey: '123' })).toThrow( 155 | TypeError( 156 | 'Integer: Invalid "min" option value for someKey, it should be in numeric type!' 157 | ) 158 | ) 159 | expect(() => ObjInteger7({ someKey: '123' })).toThrow( 160 | TypeError( 161 | 'Integer: Invalid "max" option value for someKey, it should be in numeric type!' 162 | ) 163 | ) 164 | expect(() => ObjInteger8({ someKey: '123' })).toThrow( 165 | TypeError( 166 | 'Integer: Invalid "minDigits" option value for someKey, it should be in numeric type!' 167 | ) 168 | ) 169 | expect(() => ObjInteger9({ someKey: '123' })).toThrow( 170 | TypeError( 171 | 'Integer: Invalid "maxDigits" option value for someKey, it should be in numeric type!' 172 | ) 173 | ) 174 | }) 175 | 176 | test('Will validate using custom value', () => { 177 | const Obj = blueprint.object({ 178 | value: types.integer({ 179 | validate: val => val > 100, 180 | }), 181 | }) 182 | 183 | const Obj2 = blueprint.object({ 184 | value: types.integer({ 185 | validate: val => val !== 1818, 186 | default: () => 9999, 187 | }), 188 | }) 189 | 190 | expect(Obj({ value: '50' })).toEqual({ value: null }) 191 | expect(Obj({ value: '110' })).toEqual({ value: 110 }) 192 | expect(Obj2({ value: '123' })).toEqual({ value: 123 }) 193 | expect(Obj2({ value: 1818 })).toEqual({ value: 9999 }) 194 | }) 195 | 196 | test('Ignore null value', () => { 197 | const Obj = blueprint.object({ 198 | value: types.integer, 199 | }) 200 | 201 | expect(Obj({ value: null })).toEqual({ value: null }) 202 | }) -------------------------------------------------------------------------------- /test/integer_validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | const PodengError = require('../validator/errors/PodengError'); 6 | const Validator = require('../validator'); 7 | 8 | test('Throw error when throw options set via non Validator', () => { 9 | const Obj1 = blueprint.object( 10 | { 11 | num: types.integer, 12 | num2: types.integer 13 | }, 14 | { 15 | throwOnError: true, 16 | giveWarning: true 17 | } 18 | ); 19 | 20 | const assignWrongValue = () => { 21 | Obj1({ num: 'Blue', num2: () => {} }); 22 | }; 23 | 24 | expect(assignWrongValue).toThrow(PodengError); 25 | }); 26 | 27 | test('Throw error when validate params via Validator', () => { 28 | const Obj1 = blueprint.object({ 29 | num: types.integer, 30 | num2: types.integer 31 | }); 32 | 33 | const validator = Validator(Obj1); 34 | 35 | const validate = () => { 36 | validator.validate({ num: 'Blue', num2: () => {} }); 37 | }; 38 | 39 | expect(validate).toThrow(PodengError); 40 | }); 41 | 42 | test('Returns error details when checking params via Validator', () => { 43 | const Obj1 = blueprint.object({ 44 | num: types.integer, 45 | num2: types.integer 46 | }); 47 | 48 | const validator = Validator(Obj1); 49 | 50 | const [err, errDetails] = validator.check({ 51 | num: '100.34', 52 | num2: () => {} 53 | }); 54 | 55 | expect(err).toBe(true); 56 | expect(errDetails).toEqual({ 57 | num2: ['failed to parse "num2" with its type'] 58 | }); 59 | }); 60 | 61 | test('Able to validate using object serialize params', () => { 62 | const Obj1 = blueprint.object({ 63 | num: types.integer({ serialize: { to: 'number1' } }), 64 | num2: types.integer({ min: 5, deserialize: { from: 'number2' } }), 65 | num3: types.integer 66 | }); 67 | 68 | const validator = Validator(Obj1); 69 | const validator2 = Validator(Obj1, { deserialization: true }); 70 | 71 | const throwErr1 = () => { 72 | validator.validate({ 73 | num: 10, 74 | num2: 20, 75 | num3: 'stringss' 76 | }); 77 | }; 78 | 79 | const notThrowErr = () => { 80 | validator2.validate({ 81 | number1: 10, 82 | number2: '20', 83 | num3: '30' 84 | }); 85 | }; 86 | 87 | const [err1, errDetails1] = validator.check({ 88 | num: 10, 89 | num2: 3, 90 | num3: '30' 91 | }); 92 | 93 | const [err2, errDetails2] = validator2.check({ 94 | number1: 10, 95 | number2: '2', 96 | num3: '30' 97 | }); 98 | 99 | expect(throwErr1).toThrow(PodengError); 100 | expect(notThrowErr).not.toThrow(PodengError); 101 | 102 | expect(err1).toBe(true); 103 | expect(errDetails1).toEqual({ 104 | num2: ['Minimum value of "num2" is 5'] 105 | }); 106 | 107 | expect(err2).toBe(true); 108 | expect(errDetails2).toEqual({ 109 | number2: ['Minimum value of "number2" is 5'] 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/number_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | 6 | test('Object able to serialize and deserialize', () => { 7 | const Obj1 = blueprint.object({ 8 | num: types.number({ serialize: { to: 'number' } }) 9 | }); 10 | 11 | expect(Obj1.serialize({ num: '10.10' })).toEqual({ number: 10.10 }); 12 | expect(Obj1.deserialize({ number: '20' })).toEqual({ num: 20 }); 13 | }); 14 | 15 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 16 | const Obj1 = blueprint.object({ 17 | num: types.number({ 18 | serialize: { to: 'number' }, 19 | deserialize: { from: 'a_number' } 20 | }) 21 | }); 22 | 23 | const Obj2 = blueprint.object({ 24 | num: types.number({ 25 | min: 100, 26 | serialize: { to: 'number' } 27 | }) 28 | }); 29 | 30 | expect(Obj1.serialize({ num: '20' })).toEqual({ number: 20 }); 31 | expect(Obj1.serialize({ num: '20.50' })).toEqual({ number: 20.50 }); 32 | expect(Obj1.deserialize({ a_number: '50.32' })).toEqual({ num: 50.32 }); 33 | expect(Obj2.deserialize({ number: 200 })).toEqual({ num: 200 }); 34 | expect(Obj2.deserialize({ number: 200.55 })).toEqual({ num: 200.55 }); 35 | expect(Obj2.deserialize({ number: 10 })).toEqual({ num: null }); 36 | expect(Obj2.deserialize({ number: 99.99 })).toEqual({ num: null }); 37 | }); 38 | 39 | test('Object able to hide on serialize', () => { 40 | const Obj1 = blueprint.object({ 41 | num: types.number({ 42 | serialize: { to: 'a_number' } 43 | }), 44 | num2: types.number({ serialize: { display: false } }) 45 | }); 46 | 47 | expect(Obj1.serialize({ num: 30, num2: '100' })).toEqual({ 48 | a_number: 30 49 | }); 50 | expect(Obj1.serialize({ num: 50.30, num2: '100' })).toEqual({ 51 | a_number: 50.30 52 | }); 53 | expect(Obj1({ num: 30, num2: '100' })).toEqual({ 54 | num: 30, 55 | num2: 100 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/number_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | const PodengError = require('../validator/errors/PodengError') 8 | 9 | test('Object include number type', () => { 10 | const Obj = blueprint.object({ 11 | num: types.number, 12 | }) 13 | 14 | expect(typeof Obj).toEqual('function') 15 | }) 16 | 17 | test('Validate value on wrong type passed', () => { 18 | const Obj = blueprint.object({ 19 | num: types.number({ hideOnFail: true }), 20 | num2: types.number, 21 | }) 22 | 23 | const Obj2 = blueprint.object({ 24 | num: types.number({ min: 5.5 }), 25 | }) 26 | 27 | const Obj3 = blueprint.object({ 28 | num: types.number({ max: 100.050 }), 29 | }) 30 | 31 | const Obj4 = blueprint.object({ 32 | num: types.number({ min: 5.045, max: 17.5, default: 18.3 }), 33 | }) 34 | 35 | const Obj5 = blueprint.object({ 36 | number: types.number({ 37 | minDigits: 2, 38 | maxDigits: 3, 39 | }), 40 | }) 41 | 42 | expect(Obj({ num: {}, num2: '30.059' })).toEqual({ num2: 30.059 }) 43 | expect(Obj2({ num: 20 })).toEqual({ num: 20 }) 44 | expect(Obj2({ num: 'invalid' })).toEqual({ num: null }) 45 | expect(Obj3({ num: 0 })).toEqual({ num: 0 }) 46 | expect(Obj3({ num: '80.23' })).toEqual({ num: 80.23 }) 47 | expect(Obj3({ num: '180.23' })).toEqual({ num: null }) 48 | expect(Obj3({ num: '10.3235' })).toEqual({ num: 10.3235 }) 49 | expect(Obj4({ num: '5.045' })).toEqual({ num: 5.045 }) 50 | expect(Obj4({ num: '17.5' })).toEqual({ num: 17.5 }) 51 | expect(Obj4({ num: {} })).toEqual({ num: 18.3 }) 52 | expect(Obj4({ num: 5 })).toEqual({ num: 18.3 }) 53 | expect(Obj4({ num: 20 })).toEqual({ num: 18.3 }) 54 | expect(Obj5({ number: '27' })).toEqual({ number: 27 }) 55 | expect(Obj5({ number: '9' })).toEqual({ number: null }) 56 | expect(Obj5({ number: '9.5' })).toEqual({ number: null }) 57 | expect(Obj5({ number: '1000' })).toEqual({ number: null }) 58 | expect(Obj5({ number: '100.04' })).toEqual({ number: 100.04 }) 59 | }) 60 | 61 | test('Object array with number options', () => { 62 | const Obj = blueprint.object({ 63 | value1: types.number, 64 | value2: types.number({ min: 10.18 }), 65 | }) 66 | 67 | const Collections = blueprint.array(Obj) 68 | 69 | expect( 70 | Collections([ 71 | { value1: 33.2, value2: '33' }, 72 | { value1: '10.19', value2: 10.19 }, 73 | { value1: 11, value2: 5 }, 74 | { value1: '4.12', value2: '8.18' }, 75 | ]) 76 | ).toEqual([ 77 | { 78 | value1: 33.2, 79 | value2: 33, 80 | }, 81 | { value1: 10.19, value2: 10.19 }, 82 | { value1: 11, value2: null }, 83 | { value1: 4.12, value2: null }, 84 | ]) 85 | }) 86 | 87 | test('Object include number with validation', () => { 88 | const Obj1 = blueprint.object( 89 | { 90 | value: types.number, 91 | }, 92 | { throwOnError: true } 93 | ) 94 | 95 | const Obj2 = blueprint.object( 96 | { 97 | value: types.number, 98 | }, 99 | { throwOnError: new TypeError('The Value Error') } 100 | ) 101 | 102 | const Obj3 = blueprint.object( 103 | { 104 | value: types.number, 105 | }, 106 | { onError: TypeError('The Invalid onError value') } 107 | ) 108 | 109 | const Obj4 = blueprint.object( 110 | { 111 | value: types.number, 112 | }, 113 | { 114 | onError: { 115 | onKey: (key, err) => { 116 | throw new TypeError('Error coming from onKey') 117 | }, 118 | }, 119 | } 120 | ) 121 | 122 | const Obj5 = blueprint.object( 123 | { 124 | value: types.number, 125 | }, 126 | { 127 | onError: { 128 | onAll: errors => { 129 | throw new TypeError('Error coming from onAll') 130 | }, 131 | }, 132 | } 133 | ) 134 | 135 | const Obj6 = blueprint.object({ 136 | someKey: types.number({ min: 'abc' }), 137 | }) 138 | const Obj7 = blueprint.object({ 139 | someKey: types.number({ max: 'abc' }), 140 | }) 141 | const Obj8 = blueprint.object({ 142 | someKey: types.number({ minDigits: 'abc' }), 143 | }) 144 | const Obj9 = blueprint.object({ 145 | someKey: types.number({ maxDigits: 'abc' }), 146 | }) 147 | 148 | const willThrow = obj => { 149 | return () => { 150 | obj.call(null, { 151 | value: function () { }, 152 | }) 153 | } 154 | } 155 | 156 | expect(willThrow(Obj1)).toThrow(PodengError) 157 | expect(willThrow(Obj2)).toThrow(TypeError) 158 | expect(willThrow(Obj3)).not.toThrow() 159 | expect(willThrow(Obj4)).toThrow(TypeError('Error coming from onKey')) 160 | expect(willThrow(Obj5)).toThrow(TypeError('Error coming from onAll')) 161 | expect(() => Obj6({ someKey: '123' })).toThrow( 162 | TypeError( 163 | 'Number: Invalid "min" option value for someKey, it should be in numeric type!' 164 | ) 165 | ) 166 | expect(() => Obj7({ someKey: '123' })).toThrow( 167 | TypeError( 168 | 'Number: Invalid "max" option value for someKey, it should be in numeric type!' 169 | ) 170 | ) 171 | expect(() => Obj8({ someKey: '123' })).toThrow( 172 | TypeError( 173 | 'Number: Invalid "minDigits" option value for someKey, it should be in numeric type!' 174 | ) 175 | ) 176 | expect(() => Obj9({ someKey: '123' })).toThrow( 177 | TypeError( 178 | 'Number: Invalid "maxDigits" option value for someKey, it should be in numeric type!' 179 | ) 180 | ) 181 | }) 182 | 183 | test('Will validate using custom value', () => { 184 | const Obj = blueprint.object({ 185 | value: types.number({ 186 | validate: val => val > 100, 187 | }), 188 | }) 189 | 190 | const Obj2 = blueprint.object({ 191 | value: types.number({ 192 | validate: val => val !== 1818, 193 | default: () => 9999, 194 | }), 195 | }) 196 | 197 | expect(Obj({ value: '80.23' })).toEqual({ value: null }) 198 | expect(Obj({ value: '220' })).toEqual({ value: 220 }) 199 | expect(Obj2({ value: '123' })).toEqual({ value: 123 }) 200 | expect(Obj2({ value: 1818 })).toEqual({ value: 9999 }) 201 | }) 202 | 203 | 204 | test('Ignore null value', () => { 205 | const Obj = blueprint.object({ 206 | value: types.number, 207 | }) 208 | 209 | expect(Obj({ value: null })).toEqual({ value: null }) 210 | }) -------------------------------------------------------------------------------- /test/number_validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | const PodengError = require('../validator/errors/PodengError'); 6 | const Validator = require('../validator'); 7 | 8 | test('Throw error when throw options set via non Validator', () => { 9 | const Obj1 = blueprint.object( 10 | { 11 | num: types.number, 12 | num2: types.number 13 | }, 14 | { 15 | throwOnError: true, 16 | giveWarning: true 17 | } 18 | ); 19 | 20 | const assignWrongValue = () => { 21 | Obj1({ num: 'Blue', num2: () => {} }); 22 | }; 23 | 24 | expect(assignWrongValue).toThrow(PodengError); 25 | }); 26 | 27 | test('Throw error when validate params via Validator', () => { 28 | const Obj1 = blueprint.object({ 29 | num: types.number, 30 | num2: types.number 31 | }); 32 | 33 | const validator = Validator(Obj1); 34 | 35 | const validate = () => { 36 | validator.validate({ num: 'Blue', num2: () => {} }); 37 | }; 38 | 39 | expect(validate).toThrow(PodengError); 40 | }); 41 | 42 | test('Returns error details when checking params via Validator', () => { 43 | const Obj1 = blueprint.object({ 44 | num: types.number, 45 | num2: types.number 46 | }); 47 | 48 | const validator = Validator(Obj1); 49 | 50 | const [err, errDetails] = validator.check({ 51 | num: '100.34', 52 | num2: () => {} 53 | }); 54 | 55 | expect(err).toBe(true); 56 | expect(errDetails).toEqual({ 57 | num2: ['failed to parse "num2" with its type'] 58 | }); 59 | }); 60 | 61 | test('Able to validate using object serialize params', () => { 62 | const Obj1 = blueprint.object({ 63 | num: types.number({ serialize: { to: 'number1' } }), 64 | num2: types.number({ min: 5.45, deserialize: { from: 'number2' } }), 65 | num3: types.integer 66 | }); 67 | 68 | const validator = Validator(Obj1); 69 | const validator2 = Validator(Obj1, { deserialization: true }); 70 | 71 | const throwErr1 = () => { 72 | validator.validate({ 73 | num: 10, 74 | num2: 20, 75 | num3: 'stringss' 76 | }); 77 | }; 78 | 79 | const notThrowErr = () => { 80 | validator2.validate({ 81 | number1: 10, 82 | number2: '20', 83 | num3: '30' 84 | }); 85 | }; 86 | 87 | const [err1, errDetails1] = validator.check({ 88 | num: 10, 89 | num2: 3, 90 | num3: '30' 91 | }); 92 | 93 | const [err2, errDetails2] = validator2.check({ 94 | number1: 10, 95 | number2: '2', 96 | num3: '30' 97 | }); 98 | 99 | expect(throwErr1).toThrow(PodengError); 100 | expect(notThrowErr).not.toThrow(PodengError); 101 | 102 | expect(err1).toBe(true); 103 | expect(errDetails1).toEqual({ 104 | num2: ['Minimum value of "num2" is 5.45'] 105 | }); 106 | 107 | expect(err2).toBe(true); 108 | expect(errDetails2).toEqual({ 109 | number2: ['Minimum value of "number2" is 5.45'] 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/options.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | const PodengError = require('../validator/errors/PodengError') 8 | 9 | test('Validate value on wrong type passed', () => { 10 | const Car = blueprint.object({ 11 | brand: types.options(['Honda', 'Toyota', 'Mitsubishi']), 12 | color: types.options({ list: ['Red', 'Green', 'Blue'] }), 13 | }) 14 | 15 | const Complex = blueprint.object({ 16 | data: types.options([{ data: 'value' }, 1234, 'abcde']), 17 | data2: types.options({ list: [100, 200, 300], default: '837OOO6' }), 18 | }) 19 | 20 | const Complex2 = blueprint.object({ 21 | data: types.options([ 22 | blueprint.object({ key: types.string }), 23 | { someKey: 'someValue' }, 24 | 100, 25 | 180, 26 | ]), 27 | }) 28 | 29 | expect(Car({ brand: 'Yamaha', color: 'Green' })).toEqual({ 30 | brand: null, 31 | color: 'Green', 32 | }) 33 | 34 | expect(Car({ brand: 'Yamahaa', color: 'Green' })).toEqual({ 35 | brand: null, 36 | color: 'Green', 37 | }) 38 | 39 | expect(Complex({ data: { data: 'value' }, data2: '200' })).toEqual({ 40 | data: { data: 'value' }, 41 | data2: '837OOO6', 42 | }) 43 | 44 | expect( 45 | Complex2({ 46 | data: 100, 47 | }) 48 | ).toEqual({ 49 | data: 100, 50 | }) 51 | 52 | expect( 53 | Complex2({ 54 | data: { someKey: 'someValue' }, 55 | }) 56 | ).toEqual({ 57 | data: { someKey: 'someValue' }, 58 | }) 59 | 60 | expect( 61 | Complex2({ 62 | data: 10, 63 | }) 64 | ).toEqual({ 65 | data: null, 66 | }) 67 | 68 | expect( 69 | Complex2({ 70 | data: { key: 'someValue' }, 71 | }) 72 | ).toEqual({ 73 | data: { key: 'someValue' }, 74 | }) 75 | }) 76 | 77 | test('Object array with options type', () => { 78 | const Complex = blueprint.array({ 79 | data: types.options([100, 200, 'three']), 80 | }) 81 | 82 | const Complex1 = blueprint.array({ 83 | data: types.options({ list: [100, 200, 'three'] }), 84 | }) 85 | 86 | const Complex2 = blueprint.array( 87 | blueprint.object({ 88 | data: types.options([400, 500, 'six']), 89 | }) 90 | ) 91 | 92 | const Complex3 = blueprint.array( 93 | blueprint.object({ 94 | data: types.options({ list: [400, 500, 'six'] }), 95 | }) 96 | ) 97 | 98 | expect(Complex([{ data: 100 }, { data: 200 }, { data: 300 }])).toEqual([ 99 | { data: 100 }, 100 | { data: 200 }, 101 | { data: null }, 102 | ]) 103 | 104 | expect(Complex1([{ data: 100 }, { data: 200 }, { data: 300 }])).toEqual([ 105 | { data: 100 }, 106 | { data: 200 }, 107 | { data: null }, 108 | ]) 109 | 110 | expect(Complex2([{ data: 400 }, { data: 'six' }, { data: 300 }])).toEqual([ 111 | { data: 400 }, 112 | { data: 'six' }, 113 | { data: null }, 114 | ]) 115 | 116 | expect(Complex3([{ data: 400 }, { data: 'six' }, { data: 300 }])).toEqual([ 117 | { data: 400 }, 118 | { data: 'six' }, 119 | { data: null }, 120 | ]) 121 | }) 122 | 123 | test('Object include integer with validation', () => { 124 | const Validate = blueprint.object( 125 | { 126 | data: types.options(['valid_1', 'valid_2']), 127 | }, 128 | { throwOnError: true } 129 | ) 130 | 131 | const Validate2 = blueprint.object( 132 | { 133 | number: types.options([100, 200, 340]), 134 | }, 135 | { 136 | throwOnError: new TypeError('Error on Validate2'), 137 | } 138 | ) 139 | 140 | const fnToExec1 = params => () => Validate(params) 141 | const fnToExec2 = params => () => Validate2(params) 142 | 143 | expect(fnToExec1({ data: () => { } })).toThrow(PodengError) 144 | expect(fnToExec1({ data: 'valid_3' })).toThrow(PodengError) 145 | expect(fnToExec2({ data: 350 })).toThrow(TypeError('Error on Validate2')) 146 | }) 147 | 148 | test('Ignore null value', () => { 149 | const Obj = blueprint.object({ 150 | value: types.options([10, 20, 30]), 151 | }) 152 | 153 | expect(Obj({ value: null })).toEqual({ value: null }) 154 | }) -------------------------------------------------------------------------------- /test/options_validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | const validator = require('../validator') 8 | const PodengError = require('../validator/errors/PodengError') 9 | 10 | test('Validate using list from options list', () => { 11 | const Car = blueprint.object({ 12 | wheels: types.integer({ default: 4 }), 13 | color: types.options({ list: ['Red', 'Green', 'Blue'] }), 14 | brand: types.string, 15 | }) 16 | 17 | const Truck = blueprint.extend(Car, { 18 | wheels: types.integer({ default: 6 }), 19 | }) 20 | 21 | const CarValidator = validator(Car) 22 | const TruckValidator = validator(Truck) 23 | 24 | const funcOk1 = () => { 25 | CarValidator.validate({ brand: 'Foo', color: 'Green', wheels: 5 }) 26 | } 27 | 28 | const funcOk2 = () => { 29 | TruckValidator.validate({ brand: 123, color: 'Green', wheels: 5 }) 30 | } 31 | 32 | const funcNotOk = () => { 33 | TruckValidator.validate({ brand: 123, color: 'white', wheels: 5 }) 34 | } 35 | 36 | const funcNotOk2 = () => { 37 | TruckValidator.validate({ color: 'white', wheels: 5 }) 38 | } 39 | 40 | expect(funcOk1).not.toThrow(PodengError) 41 | expect(funcOk2).not.toThrow(PodengError) 42 | expect(funcNotOk).toThrow(PodengError) 43 | expect(funcNotOk2).toThrow(PodengError) 44 | }) 45 | 46 | test('Validate using list from params', () => { 47 | const Car = blueprint.object({ 48 | wheels: types.integer({ default: 4 }), 49 | color: types.options(['Red', 'Green', 'Blue']), 50 | brand: types.string, 51 | }) 52 | 53 | const Truck = blueprint.extend(Car, { 54 | wheels: types.integer({ default: 6 }), 55 | }) 56 | 57 | const CarValidator = validator(Car) 58 | const TruckValidator = validator(Truck) 59 | 60 | const funcOk1 = () => { 61 | CarValidator.validate({ brand: 'Foo', color: 'Green', wheels: 5 }) 62 | } 63 | 64 | const funcOk2 = () => { 65 | TruckValidator.validate({ brand: 123, color: 'Green', wheels: 5 }) 66 | } 67 | 68 | const funcNotOk = () => { 69 | TruckValidator.validate({ brand: 123, color: 'white', wheels: 5 }) 70 | } 71 | 72 | const funcNotOk2 = () => { 73 | TruckValidator.validate({ color: 'white', wheels: 5 }) 74 | } 75 | 76 | expect(funcOk1).not.toThrow(PodengError) 77 | expect(funcOk2).not.toThrow(PodengError) 78 | expect(funcNotOk).toThrow(PodengError) 79 | expect(funcNotOk2).toThrow(PodengError) 80 | }) 81 | -------------------------------------------------------------------------------- /test/string_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | 6 | test('Object able to serialize and deserialize', () => { 7 | const Obj1 = blueprint.object({ 8 | name: types.string({ serialize: { to: 'firstName' } }) 9 | }); 10 | 11 | expect(Obj1.serialize({ name: 'Aditya' })).toEqual({ firstName: 'Aditya' }); 12 | expect(Obj1.deserialize({ firstName: 'Aditya' })).toEqual({ name: 'Aditya' }); 13 | }); 14 | 15 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 16 | const Obj1 = blueprint.object({ 17 | name: types.string({ 18 | serialize: { to: 'firstName' }, 19 | deserialize: { from: 'username' } 20 | }) 21 | }); 22 | 23 | const Obj2 = blueprint.object({ 24 | name: types.string({ 25 | normalize: 'uppercased', 26 | serialize: { to: 'firstName' } 27 | }) 28 | }); 29 | 30 | expect(Obj1.serialize({ name: 'Aditya' })).toEqual({ firstName: 'Aditya' }); 31 | expect(Obj1.deserialize({ username: 'Aditya' })).toEqual({ name: 'Aditya' }); 32 | expect(Obj2.deserialize({ firstName: 'Aditya' })).toEqual({ name: 'ADITYA' }); 33 | }); 34 | 35 | test('Object able to hide on serialize', () => { 36 | const Obj1 = blueprint.object({ 37 | name: types.string({ 38 | normalize: ['trimmed', 'upper_first'], 39 | serialize: { to: 'firstName' } 40 | }), 41 | address: types.string({ serialize: { display: false } }), 42 | zipcode: types.string 43 | }); 44 | 45 | expect(Obj1.serialize({ name: 'aditya', address: 'some address' })).toEqual({ 46 | firstName: 'Aditya', 47 | zipcode: null 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/string_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | const PodengError = require('../validator/errors/PodengError') 8 | 9 | test('Object include string type', () => { 10 | const Car = blueprint.object({ 11 | type: types.string, 12 | }) 13 | 14 | expect(typeof Car).toEqual('function') 15 | }) 16 | 17 | test('Hide value on wrong type passed', () => { 18 | const Car = blueprint.object({ 19 | type: types.string({ hideOnFail: true }), 20 | color: types.string, 21 | }) 22 | 23 | const Person1 = blueprint.object({ 24 | name: types.string({ min: 4 }), 25 | }) 26 | 27 | const Person2 = blueprint.object({ 28 | name: types.string({ max: 30 }), 29 | }) 30 | 31 | const Person3 = blueprint.object({ 32 | name: types.string({ min: 4, max: 30 }), 33 | }) 34 | 35 | expect(Car({ type: {}, color: 'black' })).toEqual({ color: 'black' }) 36 | expect(Person1({ name: 'Aditya' })).toEqual({ name: 'Aditya' }) 37 | expect(Person1({ name: 'Ad' })).toEqual({ name: null }) 38 | expect(Person2({ name: 'Ad' })).toEqual({ name: 'Ad' }) 39 | expect( 40 | Person2({ name: 'Lorem Ipsum is simply dummy text of the printing' }) 41 | ).toEqual({ name: null }) 42 | expect(Person3({ name: 'Ad' })).toEqual({ name: null }) 43 | expect( 44 | Person3({ name: 'Lorem Ipsum is simply dummy text of the printing' }) 45 | ).toEqual({ name: null }) 46 | expect(Person3({ name: 'Aditya' })).toEqual({ name: 'Aditya' }) 47 | }) 48 | 49 | test('Object include string type with json stringify and custom default value', () => { 50 | const ObjStringify = blueprint.object({ 51 | value: types.string, 52 | }) 53 | 54 | const ObjNonStringify = blueprint.object({ 55 | value: types.string({ stringify: false }), 56 | }) 57 | 58 | const DefaultValue = blueprint.object({ 59 | value: types.string({ stringify: false, default: 'Empty' }), 60 | }) 61 | 62 | const DefaultValueFunc = blueprint.object({ 63 | value: types.string({ stringify: false, default: () => 'ValueFunc' }), 64 | }) 65 | 66 | expect(ObjStringify({ value: { age: 27 } })).toEqual({ 67 | value: '{"age":27}', 68 | }) 69 | expect(ObjNonStringify({ value: {} })).toEqual({ 70 | value: null, 71 | }) 72 | expect(DefaultValue({ value: {} })).toEqual({ 73 | value: 'Empty', 74 | }) 75 | expect(DefaultValueFunc({ value: {} })).toEqual({ 76 | value: 'ValueFunc', 77 | }) 78 | }) 79 | 80 | test('Object array with string options', () => { 81 | const ObjString = blueprint.object({ 82 | value1: types.string({ normalize: 'uppercased' }), 83 | value2: types.string({ normalize: 'lowercased' }), 84 | }) 85 | 86 | const Collections = blueprint.array(ObjString) 87 | 88 | expect( 89 | Collections([ 90 | { value1: 'this will be uppercased', value2: 'THIS WILL BE LOWERCASED' }, 91 | { value1: 'foo', value2: 'BAR' }, 92 | { value1: 'john', value2: 'DOE' }, 93 | ]) 94 | ).toEqual([ 95 | { 96 | value1: 'THIS WILL BE UPPERCASED', 97 | value2: 'this will be lowercased', 98 | }, 99 | { value1: 'FOO', value2: 'bar' }, 100 | { value1: 'JOHN', value2: 'doe' }, 101 | ]) 102 | }) 103 | 104 | test('Object include string with normalize options', () => { 105 | const ObjString = blueprint.object({ 106 | value1: types.string({ normalize: 'uppercased' }), 107 | value2: types.string({ normalize: 'lowercased' }), 108 | value3: types.string({ normalize: 'trimmed' }), 109 | value4: types.string({ normalize: 'upper_first' }), 110 | value5: types.string({ normalize: 'upper_first_word' }), 111 | value6: types.string({ normalize: 'lower_first' }), 112 | value7: types.string({ normalize: 'lower_first_word' }), 113 | value8: types.string({ normalize: ['trimmed', 'uppercased'] }), 114 | }) 115 | 116 | expect( 117 | ObjString({ 118 | value1: 'Some Text', 119 | value2: 'Some Text', 120 | value3: ' some text ', 121 | value4: 'some text here', 122 | value5: 'some text here', 123 | value6: 'SOME TEXT HERE', 124 | value7: 'SOME TEXT HERE', 125 | value8: ' some text here ', 126 | }) 127 | ).toEqual({ 128 | value1: 'SOME TEXT', 129 | value2: 'some text', 130 | value3: 'some text', 131 | value4: 'Some text here', 132 | value5: 'Some Text Here', 133 | value6: 'sOME TEXT HERE', 134 | value7: 'sOME tEXT hERE', 135 | value8: 'SOME TEXT HERE', 136 | }) 137 | }) 138 | 139 | test('Object include string with validation', () => { 140 | const ObjString1 = blueprint.object( 141 | { 142 | value: types.string, 143 | }, 144 | { throwOnError: true } 145 | ) 146 | 147 | const ObjString2 = blueprint.object( 148 | { 149 | value: types.string, 150 | }, 151 | { throwOnError: new TypeError('The Value Error') } 152 | ) 153 | 154 | const ObjString3 = blueprint.object( 155 | { 156 | value: types.string, 157 | }, 158 | { onError: TypeError('The Value Error') } 159 | ) 160 | 161 | const ObjString4 = blueprint.object( 162 | { 163 | value: types.string, 164 | }, 165 | { 166 | onError: { 167 | onKey: (key, err) => { 168 | throw new TypeError('Error coming from onKey') 169 | }, 170 | }, 171 | } 172 | ) 173 | 174 | const ObjString5 = blueprint.object( 175 | { 176 | value: types.string, 177 | }, 178 | { 179 | onError: { 180 | onAll: errors => { 181 | throw new TypeError('Error coming from onAll') 182 | }, 183 | }, 184 | } 185 | ) 186 | 187 | const willThrow = obj => { 188 | return () => { 189 | obj.call(null, { 190 | value: function () { }, 191 | }) 192 | } 193 | } 194 | 195 | expect(willThrow(ObjString1)).toThrow(PodengError) 196 | expect(willThrow(ObjString2)).toThrow(TypeError) 197 | expect(willThrow(ObjString3)).not.toThrow() 198 | expect(willThrow(ObjString4)).toThrow(TypeError('Error coming from onKey')) 199 | expect(willThrow(ObjString5)).toThrow(TypeError('Error coming from onAll')) 200 | }) 201 | 202 | test('Will validate using custom value', () => { 203 | const Obj = blueprint.object({ 204 | value: types.string({ 205 | validate: val => val !== '123', 206 | }), 207 | }) 208 | 209 | const Obj2 = blueprint.object({ 210 | value: types.string({ 211 | validate: val => val !== '123', 212 | default: () => '54321', 213 | }), 214 | }) 215 | 216 | expect(Obj({ value: '123' })).toEqual({ value: null }) 217 | expect(Obj({ value: '321' })).toEqual({ value: '321' }) 218 | expect(Obj2({ value: '123' })).toEqual({ value: '54321' }) 219 | }) 220 | 221 | test('Null value should be interpreted as null', () => { 222 | const Obj = blueprint.object({ 223 | value: types.string, 224 | }) 225 | 226 | expect(Obj({ value: null })).toEqual({ value: null }) 227 | }) -------------------------------------------------------------------------------- /test/string_validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | const PodengError = require('../validator/errors/PodengError'); 6 | const Validator = require('../validator'); 7 | 8 | test('Throw error when throw options set via non Validator', () => { 9 | const Human = blueprint.object( 10 | { 11 | eyeColor: types.string, 12 | hairColor: types.string 13 | }, 14 | { 15 | throwOnError: true, 16 | giveWarning: true 17 | } 18 | ); 19 | 20 | const assignWrongValue = () => { 21 | Human({ eyeColor: 'Blue', hairColor: () => {} }); 22 | }; 23 | 24 | expect(assignWrongValue).toThrow(PodengError); 25 | }); 26 | 27 | test('Throw error when validate params via Validator', () => { 28 | const Human = blueprint.object({ 29 | eyeColor: types.string, 30 | hairColor: types.string 31 | }); 32 | 33 | const validator = Validator(Human); 34 | 35 | const validate = () => { 36 | validator.validate({ eyeColor: 'Blue', hairColor: () => {} }); 37 | }; 38 | 39 | expect(validate).toThrow(PodengError); 40 | }); 41 | 42 | test('Returns error details when checking params via Validator', () => { 43 | const Human = blueprint.object({ 44 | eyeColor: types.string, 45 | hairColor: types.string 46 | }); 47 | 48 | const validator = Validator(Human); 49 | 50 | const [err, errDetails] = validator.check({ 51 | eyeColor: 'Blue', 52 | hairColor: () => {} 53 | }); 54 | 55 | expect(err).toBe(true); 56 | expect(errDetails).toEqual({ 57 | hairColor: ['failed to parse "hairColor" with its type'] 58 | }); 59 | }); 60 | 61 | test('Able to validate using object serialize params', () => { 62 | const Human = blueprint.object({ 63 | eyeColor: types.string({ serialize: { to: 'eye_color' } }), 64 | hairColor: types.string({ min: 5, deserialize: { from: 'hair_color' } }), 65 | skin_color: types.string 66 | }); 67 | 68 | const validator = Validator(Human); 69 | const validator2 = Validator(Human, { deserialization: true }); 70 | 71 | const throwErr1 = () => { 72 | validator.validate({ 73 | eyeColor: 'Blue', 74 | hairColor: 'Red', 75 | skin_color: 'Brown' 76 | }); 77 | }; 78 | 79 | const notThrowErr = () => { 80 | validator2.validate({ 81 | eye_color: 'Blue', 82 | hair_color: 'Green', 83 | skin_color: 'Brown' 84 | }); 85 | }; 86 | 87 | const [err1, errDetails1] = validator.check({ 88 | eyeColor: 'Blue', 89 | hairColor: 'Red', 90 | skin_color: 'Brown' 91 | }); 92 | 93 | const [err2, errDetails2] = validator2.check({ 94 | eye_color: 'Blue', 95 | hair_color: 'Red', 96 | skin_color: 'Brown' 97 | }); 98 | 99 | expect(throwErr1).toThrow(PodengError); 100 | expect(notThrowErr).not.toThrow(PodengError); 101 | 102 | expect(err1).toBe(true); 103 | expect(errDetails1).toEqual({ 104 | hairColor: ['Minimum value of "hairColor" is 5'] 105 | }); 106 | 107 | expect(err2).toBe(true); 108 | expect(errDetails2).toEqual({ 109 | hair_color: ['Minimum value of "hair_color" is 5'] 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/transform_serializer_deserializer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const blueprint = require('../blueprint'); 4 | const types = require('../types'); 5 | 6 | test('Object able to serialize and deserialize', () => { 7 | const Obj1 = blueprint.object({ 8 | val: types.transform(v => v + 18, { 9 | serialize: { to: 'transformed_value' } 10 | }) 11 | }); 12 | 13 | expect(Obj1.serialize({ val: 10 })).toEqual({ transformed_value: 28 }); 14 | expect(Obj1.deserialize({ transformed_value: 2 })).toEqual({ val: 20 }); 15 | expect(Obj1.deserialize({ transformed_value: '100' })).toEqual({ 16 | val: '10018' 17 | }); 18 | }); 19 | 20 | test('Object able to serialize and deserialize with custom deserialize rules', () => { 21 | const Obj1 = blueprint.object({ 22 | val: types.transform('foo-bar', { 23 | serialize: { to: 'transformed' }, 24 | deserialize: { from: 'from_transformed' } 25 | }) 26 | }); 27 | 28 | const Obj2 = blueprint.object({ 29 | val: types.transform(false) 30 | }); 31 | 32 | expect(Obj1.serialize({ val: 'meh' })).toEqual({ transformed: 'foo-bar' }); 33 | expect(Obj1.deserialize({ transformed: 'meh' })).toEqual({ val: 'foo-bar' }); 34 | expect(Obj1.deserialize({})).toEqual({ 35 | val: 'foo-bar' 36 | }); 37 | expect(Obj1.deserialize()).toEqual({ 38 | val: 'foo-bar' 39 | }); 40 | 41 | expect(Obj2.serialize({ val: 'meh' })).toEqual({ val: false }); 42 | expect(Obj2.deserialize({ val: 'meh' })).toEqual({ val: false }); 43 | expect(Obj2.deserialize({})).toEqual({ 44 | val: false 45 | }); 46 | expect(Obj2.deserialize()).toEqual({ 47 | val: false 48 | }); 49 | }); 50 | 51 | test('Object able to hide on serialize', () => { 52 | const Obj1 = blueprint.object({ 53 | higherThanThousand: types.transform(num => num > 1000, { 54 | serialize: { to: 'morethan_hundred' } 55 | }), 56 | hideOnLessThousand: types.transform(num => num < 1000, { 57 | serialize: { display: false } 58 | }) 59 | }); 60 | 61 | expect( 62 | Obj1.serialize({ higherThanThousand: 10, hideOnLessThousand: 99 }) 63 | ).toEqual({ 64 | morethan_hundred: false 65 | }); 66 | expect( 67 | Obj1.serialize({ higherThanThousand: 1001, hideOnLessThousand: 10000 }) 68 | ).toEqual({ 69 | morethan_hundred: true 70 | }); 71 | expect(Obj1({ higherThanThousand: 123, hideOnLessThousand: 900 })).toEqual({ 72 | higherThanThousand: false, 73 | hideOnLessThousand: true 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/transform_type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable */ 4 | 5 | const blueprint = require('../blueprint') 6 | const types = require('../types') 7 | 8 | test('Should be able to use transform type', () => { 9 | const Obj = blueprint.object({ 10 | val1: types.transform(val => '12345'), 11 | }) 12 | 13 | const Obj2 = blueprint.object({ 14 | val1: types.transform(4737), 15 | }) 16 | 17 | const throwError = () => { 18 | blueprint.object({ 19 | value: types.transform, 20 | })({ value: 'xx' }) 21 | } 22 | 23 | expect(Obj({ val1: 'xyz' })).toEqual({ 24 | val1: '12345', 25 | }) 26 | 27 | expect(Obj2({ val1: 35 })).toEqual({ 28 | val1: 4737, 29 | }) 30 | 31 | expect(throwError).toThrow(TypeError('Invalid setup for "transform" type')) 32 | }); 33 | 34 | test('Should be able to access sibling parameters', () => { 35 | const Obj = blueprint.object({ 36 | val: types.string({ serialize: { to: 'value' } }), 37 | val1: types.transform((val, info) => { 38 | const value = info.operationType === 'deserialize' ? info.data.value : info.data.val; 39 | return value + '12345' 40 | }), 41 | }); 42 | 43 | expect(Obj({ val: 'foo', val1: 'xyz' })).toEqual({ 44 | val: 'foo', 45 | val1: 'foo12345', 46 | }) 47 | 48 | expect(Obj.serialize({ val: 'foo', val1: 'xyz' })).toEqual({ 49 | value: 'foo', 50 | val1: 'foo12345', 51 | }) 52 | 53 | expect(Obj.deserialize({ value: 'foo', val1: 'xyz' })).toEqual({ 54 | val: 'foo', 55 | val1: 'foo12345', 56 | }) 57 | }) -------------------------------------------------------------------------------- /types/any.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isNil } = require('lodash'); 4 | const { combineDefaultOptions } = require('./utils'); 5 | 6 | const parserMaker = options => { 7 | return (key, value) => { 8 | if (typeof value === 'undefined') { 9 | return [!options.allowUndefined, options.allowUndefined ? value : null]; 10 | } 11 | 12 | let parsedVal = isNil(value) ? options.default : value; 13 | return [false, parsedVal]; 14 | }; 15 | }; 16 | 17 | const validate = () => [[], true]; 18 | 19 | const getOptions = () => 20 | combineDefaultOptions({ 21 | allowUndefined: false 22 | }); 23 | 24 | const getTypeOptions = () => ({ isDirectValueSet: false }); 25 | 26 | module.exports = { 27 | getTypeOptions, 28 | parserMaker, 29 | validate, 30 | getOptions 31 | }; 32 | -------------------------------------------------------------------------------- /types/boolean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isNil, difference, keys } = require('lodash'); 4 | const { combineDefaultOptions, fetchProvidedOptions } = require('./utils'); 5 | const { isArray, isObject, isBoolean, isString } = require('./detector'); 6 | 7 | const isValidObjectOptions = arg => { 8 | const options = keys(getOptions()); 9 | const hasOptions = args => 10 | difference(options, keys(args)).length < options.length; 11 | return isObject(arg) && hasOptions(arg); 12 | }; 13 | 14 | const isValidArrayOptions = arg => isArray(arg) && arg.length > 0; 15 | 16 | const isParamsValid = params => { 17 | if (isArray(params) && (params.length === 1 || params.length === 2)) { 18 | if (params.length === 1) { 19 | const objArg = params[0]; 20 | return isValidArrayOptions(objArg) || isValidObjectOptions(objArg); 21 | } else if (params.length === 2) { 22 | if (isArray(params[0]) && isArray(params[1])) { 23 | return params[0].length > 0 && params[1].length > 0; 24 | } else if (isArray(params[0]) && isObject(params[1])) { 25 | return params[0].length > 0; 26 | } 27 | } else { 28 | return false; 29 | } 30 | } 31 | 32 | return false; 33 | }; 34 | 35 | const extractValidList = params => { 36 | if (params.length === 0) return []; 37 | return isValidArrayOptions(params[0]) 38 | ? params[0] 39 | : isValidObjectOptions(params[0]) && params[0].validList 40 | ? params[0].validList 41 | : []; 42 | }; 43 | 44 | const extractInvalidList = params => { 45 | if ( 46 | isArray(params) && 47 | params.length > 0 && 48 | !isObject(params[0]) && 49 | params.length !== 2 50 | ) { 51 | return []; 52 | } 53 | 54 | return isValidArrayOptions(params[1]) 55 | ? params[1] 56 | : isValidObjectOptions(params[0]) && params[0].invalidList 57 | ? params[0].invalidList 58 | : []; 59 | }; 60 | 61 | const checkObjectPropertyExist = (params, propertyName) => { 62 | // Repeat over the length of params 63 | for (let i = 0; i < params.length; i++) { 64 | if (isObject(params[i]) && params[i].hasOwnProperty(propertyName)) { 65 | return params[i][propertyName]; 66 | } 67 | } 68 | return null; 69 | }; 70 | 71 | const isCaseSensitiveListing = params => { 72 | const caseSensitive = checkObjectPropertyExist(params, 'caseSensitive'); 73 | return caseSensitive === null ? getOptions().caseSensitive : caseSensitive; 74 | }; 75 | 76 | const isNotNil = params => { 77 | const normalizeNil = checkObjectPropertyExist(params, 'normalizeNil'); 78 | return normalizeNil === null ? getOptions().normalizeNil : normalizeNil; 79 | }; 80 | 81 | const evaluatesCondition = (value, validList, invalidList, caseSensitive) => { 82 | if (isBoolean(value)) return value; 83 | 84 | if (validList.length > 0) { 85 | if (!caseSensitive) { 86 | const validListLowerCased = validList.map( 87 | item => (isString(item) ? item.toLowerCase() : item) 88 | ); 89 | validList = [...validList, ...validListLowerCased]; 90 | } 91 | return validList.includes(value); 92 | } else if (invalidList.length > 0) { 93 | if (!caseSensitive) { 94 | const invalidListLowerCased = invalidList.map( 95 | item => (isString(item) ? item.toLowerCase() : item) 96 | ); 97 | invalidList = [...invalidList, ...invalidListLowerCased]; 98 | } 99 | return !invalidList.includes(value); 100 | } 101 | 102 | return null; 103 | }; 104 | 105 | const parserMaker = (...params) => { 106 | if (params.length > 0 && !isParamsValid(params)) { 107 | throw new TypeError('Invalid setup for "bool" type'); 108 | } 109 | 110 | return (key, value) => { 111 | let parsedVal = null; 112 | 113 | const validList = extractValidList(params); 114 | const invalidList = extractInvalidList(params); 115 | const isCaseSensitive = isCaseSensitiveListing(params); 116 | const normalizeNil = isNotNil(params); 117 | 118 | if ( 119 | (!validList || validList.length === 0) && 120 | (!invalidList || invalidList.length === 0) && 121 | normalizeNil 122 | ) { 123 | return [isNil(value), !isNil(value)]; 124 | } 125 | 126 | parsedVal = evaluatesCondition( 127 | value, 128 | validList, 129 | invalidList, 130 | isCaseSensitive 131 | ); 132 | 133 | if (parsedVal === null && normalizeNil) { 134 | parsedVal = !isNil(value); 135 | } 136 | 137 | return [parsedVal === null, parsedVal]; 138 | }; 139 | }; 140 | 141 | const validate = paramsOrOptions => { 142 | return (key, value, options) => { 143 | const errorDetails = []; 144 | let valid = true; 145 | 146 | const providedOptions = fetchProvidedOptions(getOptions(), options); 147 | let validList = providedOptions.validList; 148 | let invalidList = providedOptions.invalidList; 149 | 150 | if (!providedOptions.caseSensitive) { 151 | const validListLowerCased = validList && isArray(validList) 152 | ? validList.map(item => (isString(item) ? item.toLowerCase() : item)) 153 | : []; 154 | const invalidListLowerCased = invalidList && isArray(invalidList) 155 | ? invalidList.map(item => (isString(item) ? item.toLowerCase() : item)) 156 | : []; 157 | 158 | if (validListLowerCased.length > 0) { 159 | validList = [...validList, ...validListLowerCased]; 160 | } 161 | 162 | if (invalidListLowerCased.length > 0) { 163 | invalidList = [...invalidList, ...invalidListLowerCased]; 164 | } 165 | } 166 | 167 | if ( 168 | (!validList || validList.length === 0) && 169 | (!invalidList || invalidList.length === 0) && 170 | providedOptions.normalizeNil 171 | ) { 172 | valid = valid && !isNil(value); 173 | 174 | if (!valid) { 175 | errorDetails.push(`Nil value indentified for "${key}"`); 176 | } 177 | } 178 | 179 | if (validList && validList.length > 0) { 180 | valid = valid && validList.includes(value); 181 | } else if (invalidList && invalidList.length > 0) { 182 | valid = valid && invalidList.includes(value); 183 | } 184 | 185 | if (!valid && isBoolean(value)) valid = true; // check for boolean type value 186 | 187 | return [errorDetails, valid]; 188 | }; 189 | }; 190 | 191 | const getOptions = () => 192 | combineDefaultOptions({ 193 | validList: null, 194 | invalidList: null, 195 | caseSensitive: true, 196 | normalizeNil: false // doesn't effect if has validList and/or invalidList setup before 197 | }); 198 | 199 | const getTypeOptions = () => ({ isDirectValueSet: true }); 200 | 201 | module.exports = { 202 | getTypeOptions, 203 | parserMaker, 204 | validate, 205 | getOptions 206 | }; 207 | -------------------------------------------------------------------------------- /types/conditions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { reduce, isNil } = require('lodash'); 4 | const { isArray, isFunction, isBoolean, isObject } = require('./detector'); 5 | const { combineDefaultOptions } = require('./utils'); 6 | 7 | const isParamsValid = params => { 8 | if (isArray(params) && (params.length === 1 || params.length === 3)) { 9 | if (params.length === 1) { 10 | const objArg = params[0]; 11 | 12 | if (isFunction(objArg)) return true; 13 | 14 | const validObj = isObject(objArg); 15 | if (!validObj) return false; 16 | 17 | return isFunction(objArg.evaluates) && objArg.onOk && objArg.onFail; 18 | } else { 19 | return isFunction(params[0]) && params[1] && params[2]; 20 | } 21 | } 22 | 23 | return false; 24 | }; 25 | 26 | const resolverEvaluator = (resolvers, value) => { 27 | if (isArray(resolvers)) { 28 | return reduce( 29 | resolvers, 30 | (initial, resolverFunction) => initial && resolverFunction(value), 31 | true 32 | ); 33 | } else if (isFunction(resolvers)) { 34 | return resolvers(value); 35 | } 36 | 37 | return null; 38 | }; 39 | 40 | const evaluatesCondition = ({ 41 | value, 42 | resolvers, 43 | positiveValue, 44 | negativeValue 45 | }) => { 46 | const evaluatesResult = resolverEvaluator(resolvers, value); 47 | let okStatus = false; 48 | if (isBoolean(evaluatesResult)) { 49 | okStatus = evaluatesResult; 50 | } else { 51 | okStatus = !isNil(evaluatesResult); 52 | } 53 | return okStatus ? positiveValue : negativeValue; 54 | }; 55 | 56 | const parserMaker = (...params) => { 57 | if (!isParamsValid(params)) { 58 | throw new TypeError('Invalid setup for "conditions" type'); 59 | } 60 | 61 | return (key, value) => { 62 | let parsedVal = null; 63 | if (params.length === 3) { 64 | const evaluator = params[0]; 65 | const positiveValue = params[1]; 66 | const negativeValue = params[2]; 67 | parsedVal = evaluatesCondition({ 68 | value, 69 | resolvers: evaluator, 70 | positiveValue, 71 | negativeValue 72 | }); 73 | } else if (params.length === 1) { 74 | const objParam = params[0]; 75 | const evaluator = objParam.evaluates; 76 | const positiveValue = objParam.onOk; 77 | const negativeValue = objParam.onFail; 78 | parsedVal = evaluatesCondition({ 79 | value, 80 | resolvers: evaluator, 81 | positiveValue, 82 | negativeValue 83 | }); 84 | } 85 | 86 | return [parsedVal === null, parsedVal]; 87 | }; 88 | }; 89 | 90 | const validate = paramsOrOptions => (key, value, paramsOrOptions) => [[], true]; 91 | 92 | const getOptions = () => combineDefaultOptions(); 93 | 94 | const getTypeOptions = () => ({ isDirectValueSet: true }); 95 | 96 | module.exports = { 97 | getTypeOptions, 98 | parserMaker, 99 | validate, 100 | getOptions 101 | }; 102 | -------------------------------------------------------------------------------- /types/datetime.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const moment = require('moment'); 4 | const { isNil } = require('lodash'); 5 | const { combineDefaultOptions, fetchProvidedOptions } = require('./utils'); 6 | const { isArray, isObject, isDate, isString } = require('./detector'); 7 | 8 | moment.suppressDeprecationWarnings = true; 9 | 10 | const isParamsValid = params => { 11 | if (isArray(params) && (params.length === 1 || params.length === 2)) { 12 | if (params.length === 2) { 13 | return isString(params[0]) && isObject(params[1]); 14 | } 15 | if (params.length === 1) return isObject(params[0]) || isString(params[0]); 16 | } 17 | 18 | return false; 19 | }; 20 | 21 | const getDateParser = useTimezone => (useTimezone ? moment : moment.utc); 22 | 23 | const getValidDate = (dateVal, format, useTimezone) => { 24 | try { 25 | // detect date by ISO 8601 or RFC 2822 Date time formats 26 | const dateParser = getDateParser(useTimezone); 27 | const strict = true; 28 | const date = dateParser( 29 | dateVal, 30 | !isNil(format) ? format : undefined, 31 | strict 32 | ); 33 | 34 | if (date.isValid()) return date; 35 | } catch (err) { 36 | return null; 37 | } 38 | return null; 39 | }; 40 | 41 | const evaluatesDate = (dateVal, format, useTimezone) => { 42 | if (isString(dateVal) || isDate(dateVal)) { 43 | return getValidDate(dateVal, format, useTimezone); 44 | } 45 | 46 | return null; 47 | }; 48 | 49 | const getDateFormat = (parseFormat, params) => { 50 | return !parseFormat 51 | ? isString(params[0]) ? params[0] : parseFormat 52 | : parseFormat; 53 | }; 54 | 55 | const parserMaker = (...params) => { 56 | if (params.length > 0 && !isParamsValid(params)) { 57 | throw new TypeError('Invalid setup for "date" type'); 58 | } 59 | 60 | return (key, value) => { 61 | let parsedVal = null; 62 | let dateReturnFormat; 63 | 64 | const { 65 | parseFormat, 66 | returnFormat, 67 | timezoneAware, 68 | asMoment, 69 | dateOnly, 70 | timeOnly 71 | } = fetchProvidedOptions(getOptions(), params); 72 | 73 | dateReturnFormat = returnFormat; 74 | 75 | // get format string from index params 0 if detected as string 76 | const strFormat = getDateFormat(parseFormat, params); 77 | parsedVal = evaluatesDate(value, strFormat, timezoneAware); 78 | 79 | if (dateOnly || timeOnly) { 80 | dateReturnFormat = dateOnly 81 | ? isString(dateOnly) ? dateOnly : 'YYYY-MM-DD' 82 | : isString(timeOnly) ? timeOnly : 'HH:mm:ss'; 83 | } 84 | 85 | if (parsedVal !== null) { 86 | if (!asMoment) { 87 | parsedVal = parsedVal.format( 88 | !isNil(dateReturnFormat) ? dateReturnFormat : undefined 89 | ); 90 | } 91 | } 92 | 93 | return [parsedVal === null, parsedVal]; 94 | }; 95 | }; 96 | 97 | const validate = paramsOrOptions => { 98 | return (key, value, paramsOrOptions) => { 99 | const errorDetails = []; 100 | let valid = true; 101 | 102 | const { parseFormat, timezoneAware } = paramsOrOptions; 103 | 104 | const strFormat = getDateFormat(parseFormat, paramsOrOptions); 105 | valid = evaluatesDate(value, strFormat, timezoneAware) !== null; 106 | 107 | if (!valid) { 108 | errorDetails.push(`Unable to parse "${key}" with value: ${value}`); 109 | } 110 | 111 | return [errorDetails, valid]; 112 | }; 113 | }; 114 | 115 | const getOptions = () => 116 | combineDefaultOptions({ 117 | parseFormat: null, 118 | returnFormat: null, 119 | timezoneAware: true, 120 | asMoment: false, 121 | dateOnly: false, 122 | timeOnly: false 123 | }); 124 | 125 | const getTypeOptions = () => ({ isDirectValueSet: true }); 126 | 127 | module.exports = { 128 | getTypeOptions, 129 | parserMaker, 130 | validate, 131 | getOptions 132 | }; 133 | -------------------------------------------------------------------------------- /types/detector.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ARRAY_V = '[object Array]'; 4 | const OBJECT_V = '[object Object]'; 5 | const STRING_V = '[object String]'; 6 | const FUNC_V = '[object Function]'; 7 | const NUMBER_V = '[object Number]'; 8 | const BOOL_V = '[object Boolean]'; 9 | const NULL_V = '[object Null]'; 10 | const DATE_V = '[object Date]'; 11 | const UNDEF_V = '[object Undefined]'; 12 | const ERROR_V = '[object Error]'; 13 | const SYMBOL_V = '[object Symbol]'; 14 | 15 | const comparator = comp => { 16 | return obj => Object.prototype.toString.call(obj) === comp; 17 | }; 18 | 19 | const isArray = comparator(ARRAY_V); 20 | const isObject = comparator(OBJECT_V); 21 | const isFunction = comparator(FUNC_V); 22 | const isString = comparator(STRING_V); 23 | const isNumber = comparator(NUMBER_V); 24 | const isBoolean = comparator(BOOL_V); 25 | const isNull = comparator(NULL_V); 26 | const isDate = comparator(DATE_V); 27 | const isUndefined = comparator(UNDEF_V); 28 | const isError = comparator(ERROR_V); 29 | const isSymbol = comparator(SYMBOL_V); 30 | const isInt = num => !isSymbol(num) && Number(num) === num && num % 1 === 0; 31 | const isFloat = num => !isSymbol(num) && Number(num) === num && num % 1 !== 0; 32 | const isStringFloat = num => 33 | !isNull(num) && 34 | !isFloat(num) && 35 | !isNaN(num) && 36 | num.toString().indexOf('.') !== -1; 37 | const isJSONObject = obj => { 38 | const isObj = isObject(obj); 39 | if (!isObj) return false; 40 | 41 | if (typeof obj.constructor !== 'function') return true; 42 | 43 | // determine the constructor type 44 | const type = Object.prototype.toString.call(obj.constructor()); 45 | 46 | return type === OBJECT_V; 47 | }; 48 | 49 | module.exports = { 50 | isArray, 51 | isObject, 52 | isFunction, 53 | isString, 54 | isNumber, 55 | isBoolean, 56 | isNull, 57 | isDate, 58 | isUndefined, 59 | isError, 60 | isSymbol, 61 | isInt, 62 | isFloat, 63 | isStringFloat, 64 | isJSONObject 65 | }; 66 | -------------------------------------------------------------------------------- /types/float.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isNil } = require('lodash'); 4 | const { combineDefaultOptions } = require('./utils'); 5 | const { 6 | isFloat, 7 | isString, 8 | isStringFloat, 9 | isFunction, 10 | isNumber 11 | } = require('./detector'); 12 | 13 | const parseValue = value => { 14 | if (isFloat(value)) return [false, value]; 15 | const parsedValue = parseFloat(value); 16 | const invalid = isNaN(parsedValue); 17 | return [invalid, parsedValue]; 18 | }; 19 | 20 | const parserMaker = options => { 21 | return (key, value) => { 22 | let parsedVal = options.default; 23 | 24 | let [err, result] = parseValue(value); 25 | 26 | if (!err) { 27 | const [, valid] = validate(key, value, options); 28 | if (valid) parsedVal = result; 29 | } 30 | 31 | // handle custom validation 32 | // custom validation must not return null|undefined|false to be valid 33 | if (!err && options.validate !== null) { 34 | if (isFunction(options.validate)) { 35 | const result = options.validate.call(null, parsedVal); 36 | if (isNil(result) || result === false) err = true; 37 | } 38 | } 39 | 40 | if (err) { 41 | parsedVal = isFunction(options.default) 42 | ? options.default.call(null) 43 | : options.default; 44 | } 45 | 46 | return [err, parsedVal]; 47 | }; 48 | }; 49 | 50 | const validate = ( 51 | key, 52 | value, 53 | options = { min: null, max: null, minDigits: null, maxDigits: null } 54 | ) => { 55 | const errorDetails = []; 56 | let { min, max, minDigits, maxDigits } = options; 57 | 58 | if (min && !isNumber(min)) { 59 | throw new TypeError( 60 | `Float: Invalid "min" option value for ${key}, it should be in numeric type!` 61 | ); 62 | } 63 | if (max && !isNumber(max)) { 64 | throw new TypeError( 65 | `Float: Invalid "max" option value for ${key}, it should be in numeric type!` 66 | ); 67 | } 68 | if (minDigits && !isNumber(minDigits)) { 69 | throw new TypeError( 70 | `Float: Invalid "minDigits" option value for ${key}, it should be in numeric type!` 71 | ); 72 | } 73 | if (maxDigits && !isNumber(maxDigits)) { 74 | throw new TypeError( 75 | `Float: Invalid "maxDigits" option value for ${key}, it should be in numeric type!` 76 | ); 77 | } 78 | 79 | if (min) min = parseNumericType(min); 80 | if (max) max = parseNumericType(max); 81 | if (minDigits) minDigits = parseNumericType(minDigits); 82 | if (maxDigits) parseNumericType(maxDigits); 83 | 84 | const validMin = min ? value >= min : true; 85 | if (!validMin) errorDetails.push(`Minimum value of "${key}" is ${min}`); 86 | 87 | const validMax = max ? value <= max : true; 88 | if (!validMax) errorDetails.push(`Maximum value of "${key}" is ${max}`); 89 | 90 | const validMinDigits = minDigits 91 | ? value.toString().split('.')[0].length >= minDigits 92 | : true; 93 | 94 | if (!validMinDigits) { 95 | errorDetails.push(`Minimum value of "${key}" is ${minDigits} digits`); 96 | } 97 | 98 | const validMaxDigits = maxDigits 99 | ? value.toString().split('.')[0].length <= maxDigits 100 | : true; 101 | if (!validMaxDigits) { 102 | errorDetails.push(`Maximum value of "${key}" is ${maxDigits} digits`); 103 | } 104 | 105 | const valid = validMin && validMax && validMinDigits && validMaxDigits; 106 | 107 | return [errorDetails, valid]; 108 | }; 109 | 110 | const getOptions = () => 111 | combineDefaultOptions({ 112 | min: null, 113 | max: null, 114 | minDigits: null, 115 | maxDigits: null 116 | }); 117 | 118 | const getTypeOptions = () => ({ isDirectValueSet: false }); 119 | 120 | const parseNumericType = value => { 121 | if (isString(value)) { 122 | const parsed = isStringFloat(value) ? parseFloat(value) : parseInt(value); 123 | return isNaN(parsed) ? null : parsed; 124 | } 125 | 126 | return isNumber(value) ? value : null; 127 | }; 128 | 129 | module.exports = { 130 | getTypeOptions, 131 | parserMaker, 132 | validate, 133 | getOptions 134 | }; 135 | -------------------------------------------------------------------------------- /types/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { fetchProvidedOptions } = require('./utils'); 4 | const { isString, isNumber, isBoolean } = require('./detector'); 5 | const stringType = require('./string'); 6 | const integerType = require('./integer'); 7 | const floatType = require('./float'); 8 | const numberType = require('./number'); 9 | const optionsType = require('./options'); 10 | const conditionsType = require('./conditions'); 11 | const booleanType = require('./boolean'); 12 | const transformType = require('./transform'); 13 | const datetimeType = require('./datetime'); 14 | const anyType = require('./any'); 15 | 16 | const makeHandler = ({ parserMaker, validate, getOptions, getTypeOptions }) => { 17 | const handler = (...paramsOrOptions) => { 18 | const typeOptions = getTypeOptions(); 19 | 20 | const directValueSet = typeOptions.isDirectValueSet; 21 | 22 | const additionalOptions = directValueSet 23 | ? fetchProvidedOptions(getOptions(), paramsOrOptions) 24 | : paramsOrOptions[0]; 25 | const options = Object.assign(getOptions(), additionalOptions); 26 | 27 | const objHandler = () => {}; 28 | 29 | const parseArgs = directValueSet ? paramsOrOptions : [options]; 30 | objHandler.parse = parserMaker.apply(null, parseArgs); 31 | 32 | objHandler.validate = directValueSet 33 | ? validate.apply(null, paramsOrOptions) 34 | : validate; 35 | 36 | /** 37 | * Returning serialized name if set 38 | * default is null 39 | */ 40 | objHandler.getSerializeName = () => 41 | (isString(options.serialize.to) || isNumber(options.serialize.to) 42 | ? options.serialize.to 43 | : null); 44 | 45 | /** 46 | * Returning deserialized name if set 47 | * default is null 48 | */ 49 | objHandler.getDeserializeName = () => 50 | (isString(options.deserialize.from) || isNumber(options.deserialize.from) 51 | ? options.deserialize.from 52 | : null); 53 | 54 | /** 55 | * Returning status of hide the value on serialization 56 | */ 57 | objHandler.isHideOnSerialization = () => 58 | !(isBoolean(options.serialize.display) ? options.serialize.display : true); 59 | 60 | objHandler.isHideOnDeserialization = () => 61 | !(isBoolean(options.deserialize.display) 62 | ? options.deserialize.display 63 | : true); 64 | 65 | objHandler.isHideOnFail = () => options.hideOnFail; 66 | 67 | objHandler.getOptions = () => options; 68 | 69 | return objHandler; 70 | }; 71 | 72 | return handler; 73 | }; 74 | 75 | module.exports = { 76 | string: makeHandler(stringType), 77 | integer: makeHandler(integerType), 78 | float: makeHandler(floatType), 79 | number: makeHandler(numberType), 80 | options: makeHandler(optionsType), 81 | conditions: makeHandler(conditionsType), 82 | bool: makeHandler(booleanType), 83 | transform: makeHandler(transformType), 84 | datetime: makeHandler(datetimeType), 85 | any: makeHandler(anyType) 86 | }; 87 | -------------------------------------------------------------------------------- /types/integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isNil } = require('lodash'); 4 | const { combineDefaultOptions } = require('./utils'); 5 | const { isInt, isFunction, isNumber, isString } = require('./detector'); 6 | 7 | const parseValue = value => { 8 | if (isInt(value)) return [false, value]; 9 | const parsedValue = parseInt(value); 10 | const invalid = isNaN(parsedValue) && !isInt(parsedValue); 11 | return [invalid, parsedValue]; 12 | }; 13 | 14 | const parserMaker = options => { 15 | return (key, value) => { 16 | let parsedVal = options.default; 17 | 18 | let [err, result] = parseValue(value); 19 | 20 | if (!err) { 21 | const [, valid] = validate(key, value, options); 22 | if (valid) parsedVal = result; 23 | } 24 | 25 | // handle custom validation 26 | // custom validation must not return null|undefined|false to be valid 27 | if (!err && options.validate !== null) { 28 | if (isFunction(options.validate)) { 29 | const result = options.validate.call(null, parsedVal); 30 | if (isNil(result) || result === false) err = true; 31 | } 32 | } 33 | 34 | if (err) { 35 | parsedVal = isFunction(options.default) 36 | ? options.default.call(null) 37 | : options.default; 38 | } 39 | 40 | return [err, parsedVal]; 41 | }; 42 | }; 43 | 44 | const validate = ( 45 | key, 46 | value, 47 | options = { min: null, max: null, minDigits: null, maxDigits: null } 48 | ) => { 49 | const errorDetails = []; 50 | let { min, max, minDigits, maxDigits } = options; 51 | 52 | if (min && !isNumber(min)) { 53 | throw new TypeError( 54 | `Integer: Invalid "min" option value for ${key}, it should be in numeric type!` 55 | ); 56 | } 57 | if (max && !isNumber(max)) { 58 | throw new TypeError( 59 | `Integer: Invalid "max" option value for ${key}, it should be in numeric type!` 60 | ); 61 | } 62 | if (minDigits && !isNumber(minDigits)) { 63 | throw new TypeError( 64 | `Integer: Invalid "minDigits" option value for ${key}, it should be in numeric type!` 65 | ); 66 | } 67 | if (maxDigits && !isNumber(maxDigits)) { 68 | throw new TypeError( 69 | `Integer: Invalid "maxDigits" option value for ${key}, it should be in numeric type!` 70 | ); 71 | } 72 | 73 | if (min) min = isString(min) ? parseInt(min) : min; 74 | if (max) max = isString(max) ? parseInt(max) : max; 75 | if (minDigits) { 76 | minDigits = isString(minDigits) ? parseInt(minDigits) : minDigits; 77 | } 78 | if (maxDigits) { 79 | maxDigits = isString(maxDigits) ? parseInt(maxDigits) : maxDigits; 80 | } 81 | 82 | const validMin = min ? value >= min : true; 83 | if (!validMin) errorDetails.push(`Minimum value of "${key}" is ${min}`); 84 | 85 | const validMax = max ? value <= max : true; 86 | if (!validMax) errorDetails.push(`Maximum value of "${key}" is ${max}`); 87 | 88 | const validMinDigits = minDigits 89 | ? value.toString().split('.')[0].length >= minDigits 90 | : true; 91 | if (!validMinDigits) { 92 | errorDetails.push(`Minimum value of "${key}" is ${minDigits} digits`); 93 | } 94 | 95 | const validMaxDigits = maxDigits 96 | ? value.toString().split('.')[0].length <= maxDigits 97 | : true; 98 | if (!validMaxDigits) { 99 | errorDetails.push(`Maximum value of "${key}" is ${maxDigits} digits`); 100 | } 101 | 102 | const valid = validMin && validMax && validMinDigits && validMaxDigits; 103 | 104 | return [errorDetails, valid]; 105 | }; 106 | 107 | const getOptions = () => 108 | combineDefaultOptions({ 109 | min: null, 110 | max: null, 111 | minDigits: null, 112 | maxDigits: null 113 | }); 114 | 115 | const getTypeOptions = () => ({ isDirectValueSet: false }); 116 | 117 | module.exports = { 118 | getTypeOptions, 119 | parserMaker, 120 | validate, 121 | getOptions 122 | }; 123 | -------------------------------------------------------------------------------- /types/number.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isNil } = require('lodash'); 4 | const { combineDefaultOptions } = require('./utils'); 5 | const { 6 | isFloat, 7 | isString, 8 | isStringFloat, 9 | isFunction, 10 | isNumber 11 | } = require('./detector'); 12 | 13 | const parseValue = value => { 14 | if (isFloat(value)) return [false, value]; 15 | const parsedValue = parseFloat(value); 16 | const invalid = isNaN(parsedValue) && !isFloat(parsedValue); 17 | return [invalid, parsedValue]; 18 | }; 19 | 20 | const parserMaker = options => { 21 | return (key, value) => { 22 | let parsedVal = options.default; 23 | 24 | let [err, result] = parseValue(value); 25 | 26 | if (!err) { 27 | const [, valid] = validate(key, value, options); 28 | if (valid) parsedVal = result; 29 | } 30 | 31 | // handle custom validation 32 | // custom validation must not return null|undefined|false to be valid 33 | if (!err && options.validate !== null) { 34 | if (isFunction(options.validate)) { 35 | const result = options.validate.call(null, parsedVal); 36 | if (isNil(result) || result === false) err = true; 37 | } 38 | } 39 | 40 | if (err) { 41 | parsedVal = isFunction(options.default) 42 | ? options.default.call(null) 43 | : options.default; 44 | } 45 | 46 | return [err, parsedVal]; 47 | }; 48 | }; 49 | 50 | const validate = ( 51 | key, 52 | value, 53 | options = { min: null, max: null, minDigits: null, maxDigits: null } 54 | ) => { 55 | const errorDetails = []; 56 | let { min, max, minDigits, maxDigits } = options; 57 | 58 | if (min && !isNumber(min)) { 59 | throw new TypeError( 60 | `Number: Invalid "min" option value for ${key}, it should be in numeric type!` 61 | ); 62 | } 63 | if (max && !isNumber(max)) { 64 | throw new TypeError( 65 | `Number: Invalid "max" option value for ${key}, it should be in numeric type!` 66 | ); 67 | } 68 | if (minDigits && !isNumber(minDigits)) { 69 | throw new TypeError( 70 | `Number: Invalid "minDigits" option value for ${key}, it should be in numeric type!` 71 | ); 72 | } 73 | if (maxDigits && !isNumber(maxDigits)) { 74 | throw new TypeError( 75 | `Number: Invalid "maxDigits" option value for ${key}, it should be in numeric type!` 76 | ); 77 | } 78 | 79 | if (min) min = parseNumericType(min); 80 | if (max) max = parseNumericType(max); 81 | if (minDigits) minDigits = parseNumericType(minDigits); 82 | if (maxDigits) parseNumericType(maxDigits); 83 | 84 | const validMin = min ? value >= min : true; 85 | if (!validMin) errorDetails.push(`Minimum value of "${key}" is ${min}`); 86 | 87 | const validMax = max ? value <= max : true; 88 | if (!validMax) errorDetails.push(`Maximum value of "${key}" is ${max}`); 89 | 90 | const validMinDigits = minDigits 91 | ? value.toString().split('.')[0].length >= minDigits 92 | : true; 93 | 94 | if (!validMinDigits) { 95 | errorDetails.push(`Minimum value of "${key}" is ${minDigits} digits`); 96 | } 97 | 98 | const validMaxDigits = maxDigits 99 | ? value.toString().split('.')[0].length <= maxDigits 100 | : true; 101 | if (!validMaxDigits) { 102 | errorDetails.push(`Maximum value of "${key}" is ${maxDigits} digits`); 103 | } 104 | 105 | const valid = validMin && validMax && validMinDigits && validMaxDigits; 106 | 107 | return [errorDetails, valid]; 108 | }; 109 | 110 | const getOptions = () => 111 | combineDefaultOptions({ 112 | min: null, 113 | max: null, 114 | minDigits: null, 115 | maxDigits: null 116 | }); 117 | 118 | const getTypeOptions = () => ({ isDirectValueSet: false }); 119 | 120 | const parseNumericType = value => { 121 | if (isString(value)) { 122 | const parsed = isStringFloat(value) ? parseFloat(value) : parseInt(value); 123 | return isNaN(parsed) ? null : parsed; 124 | } 125 | 126 | return isNumber(value) ? value : null; 127 | }; 128 | 129 | module.exports = { 130 | getTypeOptions, 131 | parserMaker, 132 | validate, 133 | getOptions 134 | }; 135 | -------------------------------------------------------------------------------- /types/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const moment = require('moment'); 4 | const { forEach, isEqual } = require('lodash'); 5 | const { 6 | isArray, 7 | isString, 8 | isNumber, 9 | isBoolean, 10 | isDate, 11 | isObject, 12 | isFunction 13 | } = require('./detector'); 14 | const { combineDefaultOptions, isBlueprintObject } = require('./utils'); 15 | 16 | const parseValue = (listOfOptions, value) => { 17 | let validValue = false; 18 | 19 | forEach(listOfOptions, optionValue => { 20 | if (!validValue) { 21 | if ( 22 | isString(optionValue) || 23 | isNumber(optionValue) || 24 | isBoolean(optionValue) 25 | ) { 26 | validValue = value === optionValue; 27 | } else if (isDate(optionValue) || moment.isMoment(optionValue)) { 28 | validValue = moment(optionValue).isSame(value); 29 | } else if (isBlueprintObject(optionValue)) { 30 | const instance = optionValue.getInstance(); 31 | const [gotError] = instance.normalize(value); 32 | validValue = !gotError; 33 | } else if (isObject(optionValue)) { 34 | validValue = isEqual(optionValue, value); 35 | } 36 | } 37 | }); 38 | 39 | return [validValue, value]; 40 | }; 41 | 42 | const parserMaker = paramsOrOptions => { 43 | return (key, value) => { 44 | let parsedVal = null; 45 | 46 | if (!isArray(paramsOrOptions) && !paramsOrOptions.list) { 47 | throw new TypeError(`List options of ${key} is undefined!`); 48 | } 49 | 50 | const listOptions = Object.assign( 51 | [], 52 | isArray(paramsOrOptions) ? paramsOrOptions : paramsOrOptions.list 53 | ); 54 | 55 | const [isValidValue, result] = parseValue(listOptions, value); 56 | 57 | if (isValidValue) parsedVal = result; 58 | 59 | if (!isValidValue && !isArray(paramsOrOptions)) { 60 | parsedVal = isFunction(paramsOrOptions.default) 61 | ? paramsOrOptions.default.call(null) 62 | : paramsOrOptions.default ? paramsOrOptions.default : null; 63 | } 64 | 65 | const err = !isValidValue; 66 | 67 | return [err, parsedVal]; 68 | }; 69 | }; 70 | 71 | const validate = paramsOrOptions => { 72 | return (key, value, options) => { 73 | const errorDetails = []; 74 | let valid = false; 75 | 76 | if (isArray(paramsOrOptions)) { 77 | ;[valid] = parseValue(paramsOrOptions, value); 78 | } else if (options.list) { 79 | ;[valid] = parseValue(options.list, value); 80 | } else { 81 | throw new TypeError(`List options of ${key} is undefined!`); 82 | } 83 | 84 | if (!valid) { 85 | errorDetails.push(`Value ${value} is not listed on options of ${key}`); 86 | } 87 | 88 | return [errorDetails, valid]; 89 | }; 90 | }; 91 | 92 | const getOptions = () => 93 | combineDefaultOptions({ 94 | list: null 95 | }); 96 | 97 | const getTypeOptions = () => ({ isDirectValueSet: true }); 98 | 99 | module.exports = { 100 | getTypeOptions, 101 | parserMaker, 102 | validate, 103 | getOptions 104 | }; 105 | -------------------------------------------------------------------------------- /types/string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isFunction, isString, isArray } = require('./detector'); 4 | const { 5 | trim, 6 | toUpper, 7 | toLower, 8 | upperFirst, 9 | forEach, 10 | lowerFirst, 11 | isNil 12 | } = require('lodash'); 13 | const { combineDefaultOptions } = require('./utils'); 14 | 15 | const NORMALIZER = { 16 | trimmed: value => trim(value), 17 | uppercased: value => toUpper(value), 18 | lowercased: value => toLower(value), 19 | upper_first: value => upperFirst(value), 20 | upper_first_word: value => { 21 | let upperCasedFirst = []; 22 | forEach(value.split(/\s/g), v => upperCasedFirst.push(upperFirst(v))); 23 | return upperCasedFirst.length > 0 ? upperCasedFirst.join(' ') : value; 24 | }, 25 | lower_first: value => lowerFirst(value), 26 | lower_first_word: value => { 27 | let lowerCasedFirst = []; 28 | forEach(value.split(/\s/g), v => lowerCasedFirst.push(lowerFirst(v))); 29 | return lowerCasedFirst.length > 0 ? lowerCasedFirst.join(' ') : value; 30 | } 31 | }; 32 | 33 | const parseValue = (value, stringify = true) => { 34 | let err = null; 35 | const parsedValue = isString(value) 36 | ? value 37 | : (stringify && value !== null) 38 | ? JSON.stringify(value) 39 | : undefined; 40 | 41 | if (!parsedValue) err = true; 42 | return [err, parsedValue]; 43 | }; 44 | 45 | const validate = (key, value, options = { min: null, max: null }) => { 46 | const errorDetails = []; 47 | const { min, max } = options; 48 | 49 | const validMin = min ? value.length >= min : true; 50 | if (!validMin) errorDetails.push(`Minimum value of "${key}" is ${min}`); 51 | 52 | const validMax = max ? value.length <= max : true; 53 | if (!validMax) errorDetails.push(`Maximum value of "${key}" is ${max}`); 54 | 55 | const valid = !!(validMin && validMax); 56 | 57 | return [errorDetails, valid]; 58 | }; 59 | 60 | const getOptions = () => 61 | combineDefaultOptions({ 62 | stringify: true, 63 | min: null, 64 | max: null, 65 | normalize: null 66 | }); 67 | 68 | const parserMaker = options => { 69 | return (key, value) => { 70 | let parsedVal = options.default; 71 | 72 | /** 73 | * Disable stringify if "hideOnFail" value is true 74 | */ 75 | const doStringify = options.hideOnFail ? false : options.stringify; 76 | let [err, result] = parseValue(value, doStringify); 77 | 78 | if (!err) { 79 | const [, valid] = validate(key, value, options); 80 | if (valid) parsedVal = result; 81 | } 82 | 83 | // handle single normalization 84 | if (!err && options.normalize && NORMALIZER[options.normalize]) { 85 | parsedVal = NORMALIZER[options.normalize](parsedVal); 86 | } 87 | 88 | // handle multiple normalization 89 | if (!err && options.normalize && isArray(options.normalize)) { 90 | forEach(options.normalize, normalizeValue => { 91 | if (NORMALIZER[normalizeValue]) { 92 | parsedVal = NORMALIZER[normalizeValue](parsedVal); 93 | } 94 | }); 95 | } 96 | 97 | // handle custom normalization via custom function 98 | if (!err && options.normalize && isFunction(options.normalize)) { 99 | parsedVal = options.normalize(parsedVal); 100 | } 101 | 102 | // handle custom validation 103 | // custom validation must not return null|undefined|false to be valid 104 | if (!err && options.validate !== null) { 105 | if (isFunction(options.validate)) { 106 | const result = options.validate.call(null, parsedVal); 107 | if (isNil(result) || result === false) err = true; 108 | } 109 | } 110 | 111 | if (err) { 112 | parsedVal = isFunction(options.default) 113 | ? options.default.call(null) 114 | : options.default; 115 | } 116 | 117 | return [err, parsedVal]; 118 | }; 119 | }; 120 | 121 | const getTypeOptions = () => ({ isDirectValueSet: false }); 122 | 123 | module.exports = { 124 | getTypeOptions, 125 | parserMaker, 126 | validate, 127 | getOptions 128 | }; 129 | -------------------------------------------------------------------------------- /types/transform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { combineDefaultOptions } = require('./utils'); 4 | const { isFunction, isArray, isObject } = require('./detector'); 5 | 6 | const isParamsValid = params => { 7 | if (isArray(params) && (params.length === 1 || params.length === 2)) { 8 | if (params.length === 2) { 9 | return isObject(params[1]); 10 | } 11 | return true; 12 | } 13 | return false; 14 | }; 15 | 16 | const parserMaker = (...params) => { 17 | if (!isParamsValid(params)) { 18 | throw new TypeError('Invalid setup for "transform" type'); 19 | } 20 | 21 | return (key, value, info) => { 22 | let parsedVal = null; 23 | 24 | parsedVal = isFunction(params[0]) 25 | ? params[0].apply(null, [value, info]) 26 | : params[0]; 27 | 28 | return [parsedVal === null, parsedVal]; 29 | }; 30 | }; 31 | 32 | const validate = paramsOrOptions => (key, value, paramsOrOptions) => [[], true]; 33 | 34 | const getOptions = () => combineDefaultOptions(); 35 | 36 | const getTypeOptions = () => ({ isDirectValueSet: true }); 37 | 38 | module.exports = { 39 | getTypeOptions, 40 | parserMaker, 41 | validate, 42 | getOptions 43 | }; 44 | -------------------------------------------------------------------------------- /types/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { includes, keys, intersection, forEach } = require('lodash'); 4 | const BlueprintClass = require('../blueprint/base/cls'); 5 | const { isFunction, isObject } = require('./detector'); 6 | 7 | const DEFAULT_OPTIONS = { 8 | hideOnFail: false, 9 | default: null, 10 | validate: null, 11 | serialize: { 12 | to: null, 13 | display: true 14 | }, 15 | deserialize: { 16 | from: null, 17 | display: true 18 | } 19 | }; 20 | 21 | const combineDefaultOptions = options => 22 | Object.assign({}, DEFAULT_OPTIONS, options); 23 | 24 | const isBlueprintObject = obj => { 25 | const isValidFunction = 26 | includes(keys(obj), 'getHandler') && 27 | isFunction(obj.getHandler) && 28 | includes(keys(obj), 'getInstance') && 29 | isFunction(obj.getInstance); 30 | return isValidFunction ? obj.getInstance() instanceof BlueprintClass : false; 31 | }; 32 | 33 | const fetchProvidedOptions = (options, params) => { 34 | const defaultOptionList = keys(options); 35 | const objOptions = {}; 36 | 37 | for (let i = 0; i < params.length; i++) { 38 | if (isObject(params[i])) { 39 | const givenOpts = intersection(keys(params[i]), defaultOptionList); 40 | 41 | if (givenOpts.length > 0) { 42 | forEach(givenOpts, key => { 43 | objOptions[key] = params[i][key]; 44 | }); 45 | } 46 | } 47 | } 48 | 49 | return Object.assign({}, options, objOptions); 50 | }; 51 | 52 | module.exports = { 53 | combineDefaultOptions, 54 | isBlueprintObject, 55 | fetchProvidedOptions 56 | }; 57 | -------------------------------------------------------------------------------- /validator/errors/PodengError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const PodengError = function (options) { 4 | options = options || {}; 5 | 6 | this.name = options.name || 'PodengError'; 7 | this.message = options.message; 8 | this.details = options.details; 9 | this.cause = options.cause; 10 | 11 | this._err = new Error(); 12 | this.chain = this.cause ? [this].concat(this.cause.chain) : [this]; 13 | }; 14 | 15 | PodengError.prototype = Object.create(Error.prototype, { 16 | constructor: { 17 | value: PodengError, 18 | writable: true, 19 | configurable: true 20 | } 21 | }); 22 | 23 | Object.defineProperty(PodengError.prototype, 'stack', { 24 | get: function stack () { 25 | return ( 26 | this.name + 27 | ': ' + 28 | this.message + 29 | '\n' + 30 | this._err.stack.split('\n').slice(2).join('\n') 31 | ); 32 | } 33 | }); 34 | 35 | Object.defineProperty(PodengError.prototype, 'why', { 36 | get: function why () { 37 | let _why = this.name + ': ' + this.message; 38 | for (var i = 1; i < this.chain.length; i++) { 39 | var e = this.chain[i]; 40 | _why += ' <- ' + e.name + ': ' + e.message; 41 | } 42 | return _why; 43 | } 44 | }); 45 | 46 | /** 47 | * How to Use 48 | */ 49 | // function fail() { 50 | // throw new PodengError({ 51 | // name: 'BAR', 52 | // message: 'I messed up.' 53 | // }); 54 | // } 55 | 56 | // function failFurther() { 57 | // try { 58 | // fail(); 59 | // } catch (err) { 60 | // throw new PodengError({ 61 | // name: 'FOO', 62 | // message: 'Something went wrong.', 63 | // cause: err 64 | // }); 65 | // } 66 | // } 67 | 68 | // try { 69 | // failFurther(); 70 | // } catch (err) { 71 | // console.error(err.why); 72 | // console.error(err.stack); 73 | // console.error(err.cause.stack); 74 | // } 75 | 76 | module.exports = PodengError; 77 | -------------------------------------------------------------------------------- /validator/errors/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { forEach, get } = require('lodash'); 4 | const { isArray, isObject, isFunction } = require('../../types/detector'); 5 | const PodengError = require('./PodengError'); 6 | 7 | const ERROR_VALIDATION_NAME = 'PodengValidationError'; 8 | 9 | const warningInspector = objErrorDetails => { 10 | forEach(objErrorDetails, (value, key) => { 11 | console.warn(`Podeng Warning: [${key}] ${value}`); 12 | }); 13 | }; 14 | 15 | const warningHandler = options => { 16 | return errorDetails => { 17 | if (options.giveWarning) { 18 | if (isArray(errorDetails)) { 19 | forEach(errorDetails, objErrorDetails => 20 | warningInspector(objErrorDetails) 21 | ); 22 | } else { 23 | warningInspector(errorDetails); 24 | } 25 | } 26 | }; 27 | }; 28 | 29 | const errorHandler = options => { 30 | return errorDetails => { 31 | /** 32 | * Default error throw 33 | */ 34 | if (options.throwOnError === true) { 35 | throw new PodengError({ 36 | name: ERROR_VALIDATION_NAME, 37 | message: `Validation fails ${JSON.stringify(errorDetails, null, 2)}`, 38 | details: JSON.stringify(errorDetails) 39 | }); 40 | } 41 | 42 | /** 43 | * Handle custom error class 44 | */ 45 | if (options.throwOnError instanceof Error) { 46 | throw options.throwOnError // eslint-disable-line 47 | } 48 | 49 | /** 50 | * exec custom func error 51 | */ 52 | if (isObject(options.onError)) { 53 | const onKeyFunction = get(options.onError, 'onKey'); 54 | const onAllFunction = get(options.onError, 'onAll'); 55 | 56 | if (isFunction(onKeyFunction)) { 57 | forEach(errorDetails, (err, key) => { 58 | onKeyFunction.call(null, key, err) // eslint-disable-line 59 | }); 60 | } 61 | 62 | if (isFunction(onAllFunction)) { 63 | onAllFunction.call(null, errorDetails) // eslint-disable-line 64 | } 65 | } 66 | }; 67 | }; 68 | 69 | const errorInitializer = (options = { throwOnError: false, onError: {} }) => 70 | errorHandler(options); 71 | const warningInitializer = (options = { giveWarning: false }) => 72 | warningHandler(options); 73 | 74 | module.exports = { 75 | errorInitializer, 76 | warningInitializer, 77 | ERROR_VALIDATION_NAME 78 | }; 79 | -------------------------------------------------------------------------------- /validator/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { keys, difference } = require('lodash'); 4 | const BlueprintClass = require('../blueprint/base/cls'); 5 | const { combineDefaultOptions, isBlueprintObject } = require('./utils'); 6 | const { isFunction } = require('../types/detector'); 7 | const PodengError = require('./errors/PodengError'); 8 | const { errorInitializer, ERROR_VALIDATION_NAME } = require('./errors'); 9 | 10 | const ERROR_INVALID_COMPONENT = 11 | 'You must supply blueprint object to create validate'; 12 | const ERROR_NO_VALUE_GIVEN = 'No value was given on validate'; 13 | const showErrorUnknownProperties = unknownProps => 14 | `Unknown parameters for ${unknownProps.join(',')}`; 15 | 16 | const validatorCreator = (component, options = {}) => { 17 | const isValidObject = isBlueprintObject(component); 18 | 19 | if (!isValidObject) throw new TypeError(ERROR_INVALID_COMPONENT); 20 | 21 | if (isValidObject) { 22 | if (!(component.getInstance() instanceof BlueprintClass)) { 23 | throw new TypeError(ERROR_INVALID_COMPONENT); 24 | } 25 | } 26 | 27 | options = combineDefaultOptions(options); 28 | 29 | const Validator = function (comp, options) { 30 | this.component = comp; 31 | this.validate = validate; 32 | this.check = check; 33 | this.options = options; 34 | }; 35 | 36 | const handleUnknownParams = (schema, params, throwing = true) => { 37 | let errorUnknownParams = null; 38 | const unknownParams = difference(keys(params), keys(schema)); 39 | if (unknownParams.length > 0) { 40 | if (throwing) { 41 | throw new PodengError({ 42 | name: ERROR_VALIDATION_NAME, 43 | message: showErrorUnknownProperties(unknownParams), 44 | details: unknownParams 45 | }); 46 | } 47 | 48 | errorUnknownParams = `Unkown parameter(s) detected: ${unknownParams.join(', ')}`; 49 | } 50 | 51 | return errorUnknownParams; 52 | }; 53 | 54 | const handleCustomThrows = (errorDetails, isCustomThrowMessage = false) => { 55 | if (isFunction(isCustomThrowMessage)) { 56 | return isCustomThrowMessage.apply(errorDetails); 57 | } else if (isCustomThrowMessage instanceof Error) { 58 | throw isCustomThrowMessage; 59 | } 60 | }; 61 | 62 | const validate = function (params) { 63 | if (!params) throw new TypeError(ERROR_NO_VALUE_GIVEN); 64 | 65 | const [err, errorDetails] = !this.options.deserialization 66 | ? this.component.getInstance().normalize(params, true) 67 | : this.component.getInstance().deserialize(params, true); 68 | 69 | if (err && !this.options.allowUnknownProperties) { 70 | handleUnknownParams(this.component.getSchema(), params); 71 | } 72 | 73 | if (err && this.options.customThrowMessage) { 74 | handleCustomThrows(errorDetails, this.options.customThrowMessage); 75 | } 76 | 77 | if (err) { 78 | const errorHandler = errorInitializer({ throwOnError: true }); 79 | errorHandler(errorDetails); 80 | } 81 | }; 82 | 83 | const check = function (params) { 84 | if (!params) throw new TypeError(ERROR_NO_VALUE_GIVEN); 85 | 86 | const [err, errorDetails] = !this.options.deserialization 87 | ? this.component.getInstance().normalize(params, true) 88 | : this.component.getInstance().deserialize(params, true); 89 | 90 | if (err && !this.options.allowUnknownProperties) { 91 | const errorUnknown = handleUnknownParams( 92 | this.component.getSchema(), 93 | params, 94 | false 95 | ); 96 | if (errorUnknown) { 97 | errorDetails.unknown_params = errorUnknown; 98 | } 99 | } 100 | 101 | return [err, errorDetails]; 102 | }; 103 | 104 | const objValidator = new Validator(component, options); 105 | 106 | return objValidator; 107 | }; 108 | 109 | module.exports = validatorCreator; 110 | -------------------------------------------------------------------------------- /validator/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isBlueprintObject } = require('../types/utils'); 4 | 5 | const DEFAULT_OPTIONS = { 6 | allowUnknownProperties: true, 7 | customThrowMessage: null, 8 | deserialization: false 9 | }; 10 | 11 | const combineDefaultOptions = options => 12 | Object.assign({}, DEFAULT_OPTIONS, options); 13 | 14 | const makeError = errorName => { 15 | return (message, { details, object }) => { 16 | const error = new Error(message); 17 | error.isPodeng = true; 18 | error.name = errorName; 19 | 20 | if (details) error.details = details; 21 | 22 | if (object) error._object = object; 23 | 24 | return error; 25 | }; 26 | }; 27 | 28 | module.exports = { 29 | isBlueprintObject, 30 | combineDefaultOptions, 31 | makeError 32 | }; 33 | --------------------------------------------------------------------------------