├── .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:
- `trimmed`: Trimming space of the value
- `uppercased`: make all characters upper
- `lowercased`: make all characters lower
- `upper_first`: make first character upper
- `upper_first_word`: make first part or word in string to upper
- `lower_first`: make first character lower
- `lower_first_word`: make first part of word in string to lower
| 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 |
--------------------------------------------------------------------------------