├── .eslintrc
├── .github
└── workflows
│ └── nodejs.yml
├── .gitignore
├── .npmignore
├── .snyk
├── API.md
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── package-lock.json
├── package.json
├── src
├── creators.js
├── index.js
├── lib
│ ├── error.js
│ ├── plugins.js
│ └── validators.js
├── modifiers.js
├── rules.js
├── typeStrategies
│ ├── README.md
│ ├── any.js
│ ├── array.js
│ ├── boolean.js
│ ├── email.js
│ ├── index.js
│ ├── ip.js
│ ├── number.js
│ ├── object.js
│ ├── phone.js
│ ├── string.js
│ ├── url.js
│ ├── uuid.js
│ └── zip.js
└── types.js
└── test
├── fixtures
├── core.js
├── creators.js
├── modifiers.js
└── validators.js
├── integration
├── core.spec.js
├── creators.spec.js
├── modifiers.spec.js
└── validators.spec.js
└── src
├── creators.spec.js
├── index.spec.js
├── lib
├── error.spec.js
└── validators.spec.js
├── modifiers.spec.js
├── rules.spec.js
├── typeStrategies
├── README.md
├── any.spec.js
├── array.spec.js
├── boolean.spec.js
├── email.spec.js
├── ip.spec.js
├── number.spec.js
├── object.spec.js
├── phone.spec.js
├── string.spec.js
├── url.spec.js
├── uuid.spec.js
└── zip.spec.js
└── types.spec.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "node": true,
5 | "es6": true
6 | },
7 | "ecmaFeatures": {
8 | "arrowFunctions": true,
9 | "blockBindings": true,
10 | "classes": true,
11 | "defaultParams": true,
12 | "destructuring": true,
13 | "forOf": true,
14 | "generators": false,
15 | "modules": true,
16 | "objectLiteralComputedProperties": true,
17 | "objectLiteralDuplicateProperties": false,
18 | "objectLiteralShorthandMethods": true,
19 | "objectLiteralShorthandProperties": true,
20 | "spread": true,
21 | "superInFunctions": true,
22 | "templateStrings": true,
23 | "jsx": true
24 | },
25 | "rules": {
26 | /**
27 | * Strict mode
28 | */
29 | // babel inserts "use strict"; for us
30 | // http://eslint.org/docs/rules/strict
31 | "strict": [2, "never"],
32 |
33 | /**
34 | * ES6
35 | */
36 | "no-var": 2, // http://eslint.org/docs/rules/no-var
37 |
38 | /**
39 | * Variables
40 | */
41 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow
42 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
43 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars
44 | "vars": "local",
45 | "args": "after-used"
46 | }],
47 | "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
48 |
49 | /**
50 | * Possible errors
51 | */
52 | "comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle
53 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign
54 | "no-console": 1, // http://eslint.org/docs/rules/no-console
55 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger
56 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert
57 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition
58 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
59 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
60 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty
61 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
62 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast
63 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
64 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
65 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
66 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
67 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
68 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
69 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
70 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
71 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
72 | "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
73 |
74 | /**
75 | * Best practices
76 | */
77 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
78 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly
79 | "default-case": 2, // http://eslint.org/docs/rules/default-case
80 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation
81 | "allowKeywords": true
82 | }],
83 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq
84 | "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in
85 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller
86 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return
87 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
88 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval
89 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
90 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
91 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
92 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
93 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
94 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
95 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
96 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
97 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
98 | "no-new": 2, // http://eslint.org/docs/rules/no-new
99 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
100 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
101 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal
102 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
103 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign
104 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto
105 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
106 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
107 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url
108 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
109 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
110 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
111 | "no-with": 2, // http://eslint.org/docs/rules/no-with
112 | "radix": 2, // http://eslint.org/docs/rules/radix
113 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top
114 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife
115 | "yoda": 2, // http://eslint.org/docs/rules/yoda
116 |
117 | /**
118 | * Style
119 | */
120 | "indent": [2, 2], // http://eslint.org/docs/rules/
121 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style
122 | "1tbs", {
123 | "allowSingleLine": true
124 | }],
125 | "quotes": [
126 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes
127 | ],
128 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase
129 | "properties": "never"
130 | }],
131 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing
132 | "before": false,
133 | "after": true
134 | }],
135 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style
136 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last
137 | "func-names": 0, // http://eslint.org/docs/rules/func-names
138 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing
139 | "beforeColon": false,
140 | "afterColon": true
141 | }],
142 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap
143 | "newIsCap": true
144 | }],
145 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines
146 | "max": 2
147 | }],
148 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary
149 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object
150 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func
151 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces
152 | "no-extra-parens": 2, // http://eslint.org/docs/rules/no-wrap-func
153 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle
154 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var
155 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks
156 | "semi": [ 2, "never" ], // http://eslint.org/docs/rules/semi
157 | "keyword-spacing": 2, // http://eslint.org/docs/rules/space-after-keywords
158 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks
159 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren
160 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops
161 | "spaced-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment
162 | }
163 | }
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [14.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v1
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - name: Run `npm install`
21 | run: |
22 | npm install
23 | - name: Run `npm test`
24 | run: |
25 | npm run ci
26 | env:
27 | CI: true
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .c9/
2 | build/
3 | node_modules/
4 | coverage/
5 | **/*.log
6 | sandbox/
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | coverage
3 |
--------------------------------------------------------------------------------
/.snyk:
--------------------------------------------------------------------------------
1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
2 | # ignores vulnerabilities until expiry date; change duration by modifying expiry date
3 | ignore:
4 | 'npm:marked:20150520':
5 | - jsdoc > marked:
6 | reason: Dev Dependecy
7 | expires: '2016-05-28T00:25:33.252Z'
8 | SNYK-JS-LODASH-450202:
9 | - lodash:
10 | reason: None given
11 | expires: '2019-08-03T20:58:36.052Z'
12 | patch: {}
13 | version: v1.13.5
14 |
--------------------------------------------------------------------------------
/API.md:
--------------------------------------------------------------------------------
1 | ## Objects
2 |
3 |
4 | - creators :
object
5 | Creators allow for methods which create values during validation when a
6 | value is not supplied
7 |
8 | - obey :
object
9 | The main object for Obey; exposes the core API methods for standard use as
10 | well as the API for all other modules
11 |
12 | - modifiers :
object
13 | Modifiers allow for coercion/modification of a value present in the object
14 | when validation occurs
15 |
16 | - rules :
object
17 | Rules is responsible for determining the execution of schema definition
18 | properties during validation
19 |
20 | - types :
object
21 | Types determine and execute the appropriate validation to be performed on the
22 | data during validation
23 |
24 |
25 |
26 | ## Functions
27 |
28 |
29 | - getMessages(msgObjs) ⇒
Array.<string>
30 | Compiles array items into string error messages
31 |
32 | - ValidationError(message)
33 | Creates ValidationError object for throwing
34 |
35 | - validateByKeys(context, keyPrefix) ⇒
Promise.<Object>
36 | Validates an object using the definition's keys
property
37 |
38 | - validateByValues(context, keyPrefix) ⇒
Promise.<Object>
39 | Validates an object using the definition's values
property
40 |
41 |
42 |
43 |
44 |
45 | ## creators : object
46 | Creators allow for methods which create values during validation when a
47 | value is not supplied
48 |
49 | **Kind**: global namespace
50 |
51 | * [creators](#creators) : object
52 | * [.lib](#creators.lib)
53 | * [.execute(def, value)](#creators.execute) ⇒ function
54 | * [.add(name, fn)](#creators.add)
55 |
56 |
57 |
58 | ### creators.lib
59 | **Kind**: static property of [creators](#creators)
60 | **Properties**
61 |
62 | | Name | Type | Description |
63 | | --- | --- | --- |
64 | | Library | Object
| of creators |
65 |
66 |
67 |
68 | ### creators.execute(def, value) ⇒ function
69 | Execute method calls the appropriate creator and returns the method or
70 | throws and error if the creator does not exist
71 |
72 | **Kind**: static method of [creators](#creators)
73 | **Returns**: function
- The creator function
74 |
75 | | Param | Type | Description |
76 | | --- | --- | --- |
77 | | def | Object
| The property configuration |
78 | | value | \*
| The value being validated |
79 |
80 |
81 |
82 | ### creators.add(name, fn)
83 | Adds a creator to the library
84 |
85 | **Kind**: static method of [creators](#creators)
86 |
87 | | Param | Type | Description |
88 | | --- | --- | --- |
89 | | name | string
| The name of the creator |
90 | | fn | function
| The creator's method |
91 |
92 |
93 |
94 | ## obey : object
95 | The main object for Obey; exposes the core API methods for standard use as
96 | well as the API for all other modules
97 |
98 | **Kind**: global namespace
99 |
100 | * [obey](#obey) : object
101 | * [.rule(def)](#obey.rule) ⇒ Object
102 | * [.model(obj, [strict])](#obey.model) ⇒ Object
103 | * [.type(name, handler)](#obey.type)
104 | * [.modifier(name, fn)](#obey.modifier)
105 | * [.creator(name, fn)](#obey.creator)
106 |
107 |
108 |
109 | ### obey.rule(def) ⇒ Object
110 | Returns a composed rule from a definition object
111 |
112 | **Kind**: static method of [obey](#obey)
113 |
114 | | Param | Type | Description |
115 | | --- | --- | --- |
116 | | def | Object
| The rule definition |
117 |
118 |
119 |
120 | ### obey.model(obj, [strict]) ⇒ Object
121 | Returns a composed model from a definition object
122 |
123 | **Kind**: static method of [obey](#obey)
124 |
125 | | Param | Type | Default | Description |
126 | | --- | --- | --- | --- |
127 | | obj | Object
| | The definition object |
128 | | [strict] | boolean
| true
| Whether or not to enforce strict validation |
129 |
130 |
131 |
132 | ### obey.type(name, handler)
133 | Creates and stores (or replaces) a type
134 |
135 | **Kind**: static method of [obey](#obey)
136 |
137 | | Param | Type | Description |
138 | | --- | --- | --- |
139 | | name | string
| The name of the type |
140 | | handler | Object
| function
| The type method or object of methods |
141 |
142 |
143 |
144 | ### obey.modifier(name, fn)
145 | Creates and stores a modifier
146 |
147 | **Kind**: static method of [obey](#obey)
148 |
149 | | Param | Type | Description |
150 | | --- | --- | --- |
151 | | name | string
| The modifier's name |
152 | | fn | function
| The method for the modifier |
153 |
154 |
155 |
156 | ### obey.creator(name, fn)
157 | Creates and stores a creator
158 |
159 | **Kind**: static method of [obey](#obey)
160 |
161 | | Param | Type | Description |
162 | | --- | --- | --- |
163 | | name | string
| The creator's name |
164 | | fn | function
| The method for the creator |
165 |
166 |
167 |
168 | ## modifiers : object
169 | Modifiers allow for coercion/modification of a value present in the object
170 | when validation occurs
171 |
172 | **Kind**: global namespace
173 |
174 | * [modifiers](#modifiers) : object
175 | * [.lib](#modifiers.lib)
176 | * [.execute(def, value)](#modifiers.execute) ⇒ function
177 | * [.add(name, fn)](#modifiers.add)
178 |
179 |
180 |
181 | ### modifiers.lib
182 | **Kind**: static property of [modifiers](#modifiers)
183 | **Properties**
184 |
185 | | Name | Type | Description |
186 | | --- | --- | --- |
187 | | Library | Object
| of modifiers |
188 |
189 |
190 |
191 | ### modifiers.execute(def, value) ⇒ function
192 | Execute method calls the appropriate modifier and passes in the value or
193 | throws an error if the modifier does not exist
194 |
195 | **Kind**: static method of [modifiers](#modifiers)
196 | **Returns**: function
- The modifier function
197 |
198 | | Param | Type | Description |
199 | | --- | --- | --- |
200 | | def | Object
| The property configuration |
201 | | value | \*
| The value being validated |
202 |
203 |
204 |
205 | ### modifiers.add(name, fn)
206 | Adds new modifier to the library
207 |
208 | **Kind**: static method of [modifiers](#modifiers)
209 |
210 | | Param | Type | Description |
211 | | --- | --- | --- |
212 | | name | string
| The name of the modifier |
213 | | fn | function
| The modifier's method |
214 |
215 |
216 |
217 | ## rules : object
218 | Rules is responsible for determining the execution of schema definition
219 | properties during validation
220 |
221 | **Kind**: global namespace
222 |
223 | * [rules](#rules) : object
224 | * [.props](#rules.props)
225 | * [.makeValidate(def)](#rules.makeValidate)
226 | * [.validate(def, data, [opts], [key], [errors], [rejectOnFail], [initData])](#rules.validate) ⇒ Promise.<\*>
227 | * [.build(def)](#rules.build) ⇒ Object
228 | * [.getProps(def, data)](#rules.getProps) ⇒ Array
229 |
230 |
231 |
232 | ### rules.props
233 | **Kind**: static property of [rules](#rules)
234 | **Properties**
235 |
236 | | Name | Type | Description |
237 | | --- | --- | --- |
238 | | Validation | Object
| property setup and order of operations |
239 |
240 |
241 |
242 | ### rules.makeValidate(def)
243 | Binds rule definition in validate method
244 |
245 | **Kind**: static method of [rules](#rules)
246 |
247 | | Param | Type | Description |
248 | | --- | --- | --- |
249 | | def | Object
| The rule definition object |
250 |
251 |
252 |
253 | ### rules.validate(def, data, [opts], [key], [errors], [rejectOnFail], [initData]) ⇒ Promise.<\*>
254 | Iterates over the properties present in the rule definition and sets the
255 | appropriate bindings to required methods
256 |
257 | **Kind**: static method of [rules](#rules)
258 | **Returns**: Promise.<\*>
- Resolves with the resulting data, with any defaults, creators, and modifiers applied.
259 | Rejects with a ValidationError if applicable.
260 |
261 | | Param | Type | Default | Description |
262 | | --- | --- | --- | --- |
263 | | def | Object
| | The rule definition object |
264 | | data | \*
| | The data (value) to validate |
265 | | [opts] | Object
| {partial: false}
| Specific options for validation process |
266 | | [key] | string
| null
| null
| Key for tracking parent in nested iterations |
267 | | [errors] | Array.<{type: string, sub: string, key: string, value: \*, message: string}>
| []
| An error array to which any additional error objects will be added. If not specified, a new array will be created. |
268 | | [rejectOnFail] | boolean
| true
| If true, resulting promise will reject if the errors array is not empty; otherwise ValidationErrors will not cause a rejection |
269 | | [initData] | Object
| null
|
| Initial data object |
270 |
271 |
272 |
273 | ### rules.build(def) ⇒ Object
274 | Adds new rule to the lib
275 |
276 | **Kind**: static method of [rules](#rules)
277 |
278 | | Param | Type | Description |
279 | | --- | --- | --- |
280 | | def | Object
| The rule definition |
281 |
282 |
283 |
284 | ### rules.getProps(def, data) ⇒ Array
285 | Gets props list according to partial, required, and allowNull specifications
286 |
287 | **Kind**: static method of [rules](#rules)
288 |
289 | | Param | Type | Description |
290 | | --- | --- | --- |
291 | | def | Object
| The rule definition |
292 | | data | \*
| The value being evaluated |
293 |
294 |
295 |
296 | ## types : object
297 | Types determine and execute the appropriate validation to be performed on the
298 | data during validation
299 |
300 | **Kind**: global namespace
301 |
302 | * [types](#types) : object
303 | * [.strategies](#types.strategies)
304 | * [.checkSubType(def)](#types.checkSubType) ⇒ Object
305 | * [.validate(def, value, key, errors, initData)](#types.validate) ⇒ \*
| Promise.<\*>
306 | * [.add(name, handler)](#types.add)
307 | * [.check(context)](#types.check) ⇒ Promise.<\*>
308 |
309 |
310 |
311 | ### types.strategies
312 | **Kind**: static property of [types](#types)
313 | **Properties**
314 |
315 | | Name | Type | Description |
316 | | --- | --- | --- |
317 | | Contains | Object
| type strategies |
318 |
319 |
320 |
321 | ### types.checkSubType(def) ⇒ Object
322 | Checks for and applies sub-type to definition
323 |
324 | **Kind**: static method of [types](#types)
325 |
326 | | Param | Type | Description |
327 | | --- | --- | --- |
328 | | def | Object
| The rule defintion |
329 |
330 |
331 |
332 | ### types.validate(def, value, key, errors, initData) ⇒ \*
| Promise.<\*>
333 | Sets up the `fail` method and handles `empty` or `undefined` values. If neither
334 | empty or undefined, calls the appropriate `type` and executes validation
335 |
336 | **Kind**: static method of [types](#types)
337 | **Returns**: \*
| Promise.<\*>
- The value if empty or undefined, check method if value requires type validation
338 |
339 | | Param | Type | Description |
340 | | --- | --- | --- |
341 | | def | Object
| The property configuration |
342 | | value | \*
| The value being validated |
343 | | key | string
| The key name of the property |
344 | | errors | Array.<{type: string, sub: (string\|number), key: string, value: \*, message: string}>
| An error array to which any additional error objects will be added |
345 | | initData | Object
| Initial data object |
346 |
347 |
348 |
349 | ### types.add(name, handler)
350 | Add (or override) a type in the library
351 |
352 | **Kind**: static method of [types](#types)
353 |
354 | | Param | Type | Description |
355 | | --- | --- | --- |
356 | | name | string
| The name of the type |
357 | | handler | Object
| function
| The type strategy method |
358 |
359 |
360 |
361 | ### types.check(context) ⇒ Promise.<\*>
362 | Ensures that the strategy exists, loads if not already in memory, then ensures
363 | subtype and returns the applied type strategy
364 |
365 | **Kind**: static method of [types](#types)
366 | **Returns**: Promise.<\*>
- Resolves with the provided data, possibly modified by the type strategy
367 |
368 | | Param | Type | Description |
369 | | --- | --- | --- |
370 | | context | Object
| A type context |
371 |
372 |
373 |
374 | ## getMessages(msgObjs) ⇒ Array.<string>
375 | Compiles array items into string error messages
376 |
377 | **Kind**: global function
378 |
379 | | Param | Type | Description |
380 | | --- | --- | --- |
381 | | msgObjs | Array.<{type: string, sub: (string\|number), key: string, value: \*, message: string}>
| Original array of error message objects |
382 |
383 |
384 |
385 | ## ValidationError(message)
386 | Creates ValidationError object for throwing
387 |
388 | **Kind**: global function
389 |
390 | | Param | Type | Description |
391 | | --- | --- | --- |
392 | | message | Array.<{type: string, sub: (string\|number), key: string, value: \*, message: string}>
| Raw array of error objects |
393 |
394 |
395 |
396 | ## validateByKeys(context, keyPrefix) ⇒ Promise.<Object>
397 | Validates an object using the definition's `keys` property
398 |
399 | **Kind**: global function
400 | **Returns**: Promise.<Object>
- Resolves with the final object
401 |
402 | | Param | Type | Description |
403 | | --- | --- | --- |
404 | | context | Object
| An Obey type context |
405 | | keyPrefix | string
| A prefix to include before the key in an error message |
406 |
407 |
408 |
409 | ## validateByValues(context, keyPrefix) ⇒ Promise.<Object>
410 | Validates an object using the definition's `values` property
411 |
412 | **Kind**: global function
413 | **Returns**: Promise.<Object>
- Resolves with the final object
414 |
415 | | Param | Type | Description |
416 | | --- | --- | --- |
417 | | context | Object
| An Obey type context |
418 | | keyPrefix | string
| A prefix to include before the key in an error message |
419 |
420 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### v5.0.1 (2022-10-10):
2 |
3 | * Fixes `min` and `max` validation and messaging
4 |
5 | ### v5.0.0 (2020-09-01):
6 |
7 | * Migrated tests from mocha to jest.
8 | * Update default `uuid` type to be case insensitive.
9 | * Replace travis with github actions for CI.
10 |
11 | ### v4.1.1 (2019-07-04):
12 |
13 | * Fixed bug around `allow` stomping on `empty` when validating strings. Originally using `allow` would require specifying `''` as an allowed value, even if `empty` was also enabled.
14 |
15 | ### v4.1.0 (2019-06-19):
16 |
17 | * Added `jexl` validator method for evaluating data against [Jexl](https://github.com/TomFrost/Jexl) expressions.
18 |
19 | * Added root `use` method for adding plugin references to external packages. (Currently only used by `jexl` validator.)
20 |
21 | ### v4.0.2 (2019-06-01):
22 |
23 | * Fixed bug in `zip` type that disallowed numeric values. Now values are coerced to strings before validation checks.
24 |
25 | ### v4.0.1 (2019-05-16):
26 |
27 | * Added Node 12 to Travis config.
28 |
29 | * Updated vulnerable dependencies.
30 |
31 | ### v4.0.0 (2019-05-03):
32 |
33 | * *BREAKING*: Fixed bug allowing empty string values for predefined types, even when required ([#76](https://github.com/psvet/obey/issues/76)).
34 |
35 | ### v3.0.5 (2019-04-19):
36 |
37 | * Conditional require rules (`requiredIf`, `requiredIfNot`) are now removed if a `creator` or `default` rule is defined.
38 |
39 | ### v3.0.0 (2019-01-05):
40 |
41 | * *BREAKING*: Removed Babel dependency. This effectively drops support for Node versions <6.
42 |
43 | * Fixed bug around `allowNull` and non-`null` falsey default values ([#71](https://github.com/psvet/obey/issues/71)).
44 |
45 | ### v2.0.0 (2018-03-28):
46 |
47 | * *BREAKING*: Changed default `zip` to US regex pattern. The original default pattern was discovered to be overly lenient and was removed.
48 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2015 TechnologyAdvice
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Obey
2 |
3 | Asynchronous Data Modelling and Validation.
4 |
5 | [](https://travis-ci.org/psvet/obey)
6 | 
7 | [](https://snyk.io/test/npm/obey)
8 |
9 | ## Contents
10 |
11 | - [Introduction](#introduction)
12 | - [Installation](#installation)
13 | - [API Documentation](#api-documentation)
14 | - [Rules](#rules)
15 | - [Models](#models)
16 | - [Validation](#validation)
17 | - [Validating Partials](#validating-partials)
18 | - [Validation Error Handling](#validation-error-handling)
19 | - [Definition Properties](#definition-properties)
20 | - [Types](#types)
21 | - [Adding New Types](#adding-new-types)
22 | - [Adding Single-Method Type](#adding-single-method-type)
23 | - [Adding Type with Subs](#adding-type-with-subs)
24 | - [Allow](#allow)
25 | - [Modifiers](#modifiers)
26 | - [Creating Modifiers](#creating-modifiers)
27 | - [Creators](#creators)
28 | - [Creating Creators](#creating-creators)
29 | - [Strict Mode](#strict-mode)
30 | - [Asynchronous Validation](#asynchronous-validation)
31 | - [Contributing](#contributing)
32 | - [License](#license)
33 |
34 | ## Introduction
35 |
36 | Obey is a library for creating asynchronous data models and rules. The core goal of the project is to provide methods for managing data models both through synchronous and asynchronous validation and alignment using [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
37 |
38 | ## Installation
39 |
40 | Obey can be installed via [NPM](https://www.npmjs.com/package/obey): `npm install obey --save`
41 |
42 | ## API Documentation
43 |
44 | Detailed [API Documentation](API.md) is available for assistance in using, modifying, or [contributing](#contributing) to the Obey library.
45 |
46 | ## Rules
47 |
48 | > Rules are core definitions of how a value should be validated:
49 |
50 | ```javascript
51 | const obey = require('obey')
52 |
53 | const firstName = obey.rule({ type: 'string', min: 2, max: 45, required: true })
54 | ```
55 |
56 | ## Models
57 |
58 | > Models allow for creating validation rules for entire object schemas. The following demonstrates a model being created with Obey:
59 |
60 | ```javascript
61 | const obey = require('obey')
62 |
63 | const userModel = obey.model({
64 | id: { type: 'uuid', creator: 'uuid', required: true },
65 | email: { type: 'email', required: true },
66 | password: { type: 'string', modifier: 'encryptPassword', required: true },
67 | passwordConfirm: { type: 'string', equalTo: 'password' },
68 | fname: { type: 'string', description: 'First Name' },
69 | lname: { type: 'string', description: 'Last Name', empty: true },
70 | suffix: { type: 'string', allowNull: true },
71 | phone: { type: 'phone:numeric', min: 7, max: 10 },
72 | phoneType: { type: 'string', requiredIf: 'phone' },
73 | carrier: { type: 'string', requiredIf: { phoneType: 'mobile' }},
74 | // Array
75 | labels: { type: 'array', values: {
76 | type: 'object', keys: {
77 | label: { type: 'string' }
78 | }
79 | }},
80 | // Nested object
81 | address: { type: 'object', keys: {
82 | street: { type: 'string', max: 45 },
83 | city: { type: 'string', max: 45 },
84 | state: { type: 'string', max: 2, modifier: 'upperCase' },
85 | zip: { type: 'number', min: 10000, max: 99999 }
86 | }},
87 | // Key-independent object validation
88 | permissions: { type: 'object', values: {
89 | type: 'string'
90 | }},
91 | account: { type: 'string', allow: [ 'user', 'admin' ], default: 'user' }
92 | })
93 | ```
94 |
95 | ## Validation
96 |
97 | Using the example above, validation is done by calling the `validate` method and supplying data. This applies to both individual rules and data models:
98 |
99 | ```javascript
100 | userModel.validate({ /* some data */ })
101 | .then(data => {
102 | // Passes back `data` object, includes any defaults set,
103 | // generated, or modified data
104 | })
105 | .catch(error => {
106 | // Returns instance of ValidationError
107 | // `error.message` => String format error messages
108 | // `error.collection` => Raw array of error objects
109 | })
110 | ```
111 |
112 | The validate method returns a promise (for more information see [Asynchronous Validation](#Asynchronous Validation)). A passing run will resolve with the data, any failures will reject and the `ValidationError` instance will be returned.
113 |
114 | ### Validating Partials
115 |
116 | The `validate` method has the ability to validate partial data objects:
117 |
118 | ```javascript
119 | // Allow partial validation by supplying second argument with `partial: true`
120 | userModel.validate({ /* some (partial) data */ }, { partial: true })
121 | ```
122 |
123 | The default for the partial option is `false`, but passing `true` will allow for validation of an object containing a subset (i.e. will not throw errors for `required` properties).
124 |
125 | The common use-case for validating partials is `PATCH` updates.
126 |
127 | _Note: Running a partial validation will prevent running `creator`'s on any properties_
128 |
129 | ### Validation Error Handling
130 |
131 | Validation errors are collected and thrown after all validation has run. This is as opposed to blocking, or stopping, on the first failure.
132 |
133 | As shown in the example above, the `catch` will contain an instance of `ValidationError` with two properties; `message` and `collection`. The `message` simply contains the description of all errors.
134 |
135 | The `collection` is an array of objects containing details on each of the validation errors. For example, if a `type` evaluation for `phone:numeric` was performed and the value failed the following would be contained as an object in the array:
136 |
137 | ```javascript
138 | {
139 | type: 'phone', // The type evaluation performed
140 | sub: 'numeric', // The sub-type (if applicable)
141 | key: 'primaryPhone', // Name of the property in the model
142 | value: '(555) 123-4567', // The value evaluated
143 | message: 'Value must be a numeric phone number' // Message
144 | }
145 | ```
146 |
147 | ## Definition Properties
148 |
149 | When setting definitions for rules or model properties, the following are supported:
150 |
151 | * `type`: The type of value with (optional) sub-type see [Types](#types)
152 | * `keys`: Property of `object` type, indicates nested object properties
153 | * `values`: Defines value specification for arrays or key-independent objects
154 | * `modifier`: uses a method and accepts a passed value to modify or transform data, see [Modifiers](#modifiers)
155 | * `creator`: uses a method to create a default value if no value is supplied, see [Creators](#creators)
156 | * `empty`: Set to `true` allows empty string or array, (default `false`)
157 | * `default`: The default value if no value specified
158 | * `min`: The minimum character length for a string, lowest number, or minimum items in array
159 | * `max`: The maximum character length for a string, highest number, or maximum items in array
160 | * `required`: Enforces the value cannot be `undefined` during validation (default `false`)
161 | * `requiredIf`: Enforces the value cannot be `undefined` if a value exists or matches a given value (`{ propertyName: 'requiredValue' }`), or any of a list of values (`{ propertyName: [ 'val1', 'val2' ] }`)
162 | * `requiredIfNot`: Enforces the value cannot be `undefined` if a value _does not_ exist or match a given value (`{ propertyName: 'requiredValue' }`), or any of a list of values (`{ propertyName: [ 'val1', 'val2' ] }`)
163 | * `equalTo`: Enforces the value to be the same as the corresponding field
164 | * `allow`: Object, array or single value representing allowed value(s), see [Allow](#allow)
165 | * `allowNull`: Accepts a null value or processes specified type
166 | * `strict`: Enable or disable strict checking of an object, see [Strict Mode](#strict-mode)
167 | * `description`: A description of the property
168 | * `jexl`: One or more objects containing an `expr` string for validating data against Jexl expressions. See [Jexl Validation](#jexl-validation).
169 |
170 | ## Types
171 |
172 | **Reference: [Type Documentation](/src/typeStrategies#types)**
173 |
174 | > Types are basic checks against native types, built-ins or customs. The library includes native types (`boolean`, `number`, `string`, `array`, and `object`) as well other common types. A [list of built-in types](/src/typeStrategies#types) is contained in the source.
175 |
176 | The `type` definition can also specify a sub-type, for example:
177 |
178 | ```javascript
179 | phone: { type: 'phone:numeric' }
180 | ```
181 |
182 | The above would specify the general type `phone` with sub-type `numeric` (only allowing numbers).
183 |
184 | ### Adding New Types
185 |
186 | New types can be added to the Obey lib with the `obey.type` method. Types can be added as single methods or objects supporting sub-types:
187 |
188 | #### Adding Single-Method Type
189 |
190 | ```javascript
191 | obey.type('lowerCaseOnly', context => {
192 | if (!/[a-z]/.test(context.value)) {
193 | context.fail(`${context.key} must be lowercase`)
194 | }
195 | })
196 | ```
197 |
198 | #### Adding Type with Subs
199 |
200 | ```javascript
201 | obey.type('password', {
202 | default: context => {
203 | if (context.value.length < 6) {
204 | context.fail(`${context.key} must contain at least 6 characters`)
205 | }
206 | },
207 | strong: context => {
208 | if (!context.value.test((/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,}$/))) {
209 | context.fail(`${context.key} must contain a number, letter, and be at least 8 characters`)
210 | }
211 | }
212 | })
213 | ```
214 |
215 | The definition object contains keys that indicate the subtype (or `default` if no sub-type specified).
216 |
217 | Each method will be passed a `context` object at runtime. This object has the following properties:
218 |
219 | * `def`: The entire rule for the property in the model
220 | * `sub`: The sub-type (if provided)
221 | * `key`: The name of the property being tested (if an element in a model/object)
222 | * `value`: The value to test
223 | * `fail`: A function accepting a failure message as an argument
224 |
225 | The above would add a new type which would then be available for setting in the model configuration for any properties.
226 |
227 | ```javascript
228 | password: { type: 'password', /* ...additional config... */ }
229 |
230 | /* ...or... */
231 |
232 | password: { type: 'password:strong', /* ...additional config... */ }
233 | ```
234 |
235 | Types can be synchronous or asynchronous. For example, if a unique email is required the following could be used to define a `uniqueEmail` type:
236 |
237 | ```javascript
238 | obey.type('uniqueEmail', context => {
239 | return someDataSource.find({ email: context.value })
240 | .then(record => {
241 | if (record.length >= 1) {
242 | context.fail(`${context.key} already exists`)
243 | }
244 | })
245 | }
246 | ```
247 |
248 | Types _can_ return/resolve a value, though it is not required and is recommended any coercion be handled with a modifier.
249 |
250 | Regardless of if a value is returned/resolved, asynchronous types must resolve. Errors should be handled with the `context.fail()` method.
251 |
252 | ## Allow
253 |
254 | The `allow` property in definition objects accepts three formats; `string`, `array` or `object`
255 |
256 | The `string` and `array` methods are straight-forward:
257 |
258 | ```javascript
259 | // Only allow 'bar'
260 | foo: { type: 'string', allow: 'bar' }
261 | // Allow 'buzz', 'bazz', 'bizz'
262 | fizz: { type: 'string', allow: [ 'buzz', 'bazz', 'bizz' ] }
263 | ```
264 |
265 | The `object` representation of the `allow` property gives the ability to store enums alongside the model structure making sharing/reuse of the objects simplified:
266 |
267 | ```javascript
268 | const allowedStatuses = {
269 | 'prog': 'in progress',
270 | 'comp': 'completed',
271 | 'arch': 'archived'
272 | }
273 |
274 | // Allow statuses
275 | { status: { type: 'string', allow: allowedStatuses } }
276 | ```
277 |
278 | In the above example, the model would only accept the keys (`prog`, `comp`, `arch`) during validation.
279 |
280 | ## Modifiers
281 |
282 | > Modifiers allow custom methods to return values which are modified/transformed versions of the received value.
283 |
284 | ### Creating Modifiers
285 |
286 | Modifiers can be added to the Obey lib with the `obey.modifier` method:
287 |
288 | ```javascript
289 | obey.modifier('upperCase', val => val.toUpperCase())
290 | ```
291 |
292 | When the model is validated, the value in any fields with the `upperCase` modifier will be transformed to uppercase.
293 |
294 | Similar to types, modifiers may be synchronous (returning a value) or asynchronous (returning a promise).
295 |
296 | ## Creators
297 |
298 | > Creators allow custom methods to return values which set the value similar to the `default` property. When validating, if a value is not provided the creator assigned will be used to set the value.
299 |
300 | ### Creating Creators
301 |
302 | Creators can be added to the Obey lib with the `obey.creator` method:
303 |
304 | ```javascript
305 | obey.creator('timestamp', () => new Date().getTime())
306 | ```
307 |
308 | The above example would add a creator named `timestamp` which could be assigned as shown below:
309 |
310 | ```javascript
311 | created: { type: 'number', creator: 'timestamp' }
312 | ```
313 |
314 | When the model is validated, if no `created` property is provided the `timestamp` creator will assign the property a UTC timestamp.
315 |
316 | Similar to modifiers, creators may be synchronous (returning a value) or asynchronous (returning a promise).
317 |
318 | ## Strict Mode
319 |
320 | By default, Obey enforces strict matching on objects; meaning an object must define any keys that will be present in the data object being validated.
321 |
322 | To disable strict mode on a rule or object set the `strict` property to false:
323 |
324 | ```javascript
325 | foo: { type: 'object', strict: false, keys: { /* ... */ } }
326 | ```
327 |
328 | To disable strict mode on a model pass the (optional) strict argument as `false`:
329 |
330 | ```javascript
331 | const model = obey.model({ /* definition */ }, false)
332 | ```
333 |
334 | ## Jexl Validation
335 |
336 | Obey allows for validating data against [Jexl](https://github.com/TomFrost/Jexl) expressions via the `jexl` rule:
337 |
338 | ```javascript
339 | obey.model({
340 | exprVal: {
341 | type: 'string',
342 | jexl: [{
343 | expr: "value == root.testVal.nestedObjArray[.name == 'some specific name'].payload",
344 | message: "Do not seek the treasure" // Optional expression-specific error message
345 | }]
346 | },
347 | testVal: {
348 | type: 'object',
349 | keys: {
350 | nestedObjArray: {
351 | type: 'array',
352 | values: {
353 | type: 'object',
354 | keys: {
355 | name: { type: 'string' },
356 | payload: { type: 'string' }
357 | }
358 | }
359 | }
360 | }
361 | }
362 | })
363 | ```
364 |
365 | **Note**: the `jexl` validator uses `value` and `root` as its context keys for the specific value being validated and the corresponding data, respectively, so expression strings should be constructed accordingly.
366 |
367 | If necessary, a preconfigured Jexl instance can be passed in before the model is constructed, in order to utitlize user-defined transforms in validation:
368 |
369 | ```javascript
370 | const obey = require('obey')
371 | const jexl = require('jexl')
372 | jexl.addTransform('upper', val => val.toUpperCase(val))
373 | obey.use('jexl', jexl)
374 | obey.model({/* ...definition... */})
375 | ```
376 |
377 | ## Asynchronous Validation
378 |
379 | The goal with Obey is to provide more than just standard type/regex checks against data to validate values and models. The ability to write both synchronous and asynchronous checks, creators, and modifiers, and include data coercion in the validation simplifies the process of validation and checking before moving onto data source interactions.
380 |
381 | Additionally, with the widespread use of promises, this structure fits well in the scheme of data processing in general:
382 |
383 | ```javascript
384 | // Define a model somewhere in your code...
385 | const user = obey.model(/* ...Model Definition... */)
386 |
387 | // Use it to validate before creating a record...
388 | user.validate(/* ...some data object... */)
389 | .then(createUser)
390 | .then(/* ...response or other action... */)
391 | .catch(/* ...handle errors... */)
392 | ```
393 |
394 | ## Contributing
395 |
396 | Contibutions to Obey are welcomed and encouraged. If you would like to make a contribution please fork the repository and submit a PR with changes. Acceptance of PR's is based on a review by core contributors. To increase the likelihood of acceptance please ensure the following:
397 |
398 | * The PR states the reason for the modification/addition to the API **in detail**
399 | * All tests are passing and coverage is at, or near, 100%
400 | * The code submitted follows the conventions used throughout the library
401 | * [JSDoc](http://usejsdoc.org/) is in place and generates via `npm run doc`
402 | * Any needed documentation on the `README` is supplied
403 |
404 | ## License
405 |
406 | Obey was originally created at [TechnologyAdvice](http://www.technologyadvice.com) in Nashville, TN and released under the [MIT](LICENSE.txt) license.
407 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "obey",
3 | "version": "5.0.1",
4 | "description": "Data modelling and validation library",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "clean": "rm -rf node_modules",
8 | "test": "snyk test && npm run lint && npm run jest",
9 | "jest": "NODE_PATH=./ RESOURCES_PATH=./src jest --coverage --forceExit --runInBand --colors",
10 | "jest:watch": "NODE_PATH=./ RESOURCES_PATH=./src jest --watchAll --runInBand --colors",
11 | "lint": "standard ./src ./test --fix",
12 | "ci": "npm run lint && npm run jest"
13 | },
14 | "keywords": [
15 | "obey",
16 | "valid",
17 | "validate",
18 | "validation",
19 | "model",
20 | "modelling"
21 | ],
22 | "repository": {
23 | "type": "git",
24 | "url": "git+ssh://git@github.com/psvet/obey.git"
25 | },
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/psvet/obey/issues"
29 | },
30 | "homepage": "https://github.com/psvet/obey#readme",
31 | "devDependencies": {
32 | "jest": "^26.4.2",
33 | "snyk": "^1.387.0",
34 | "standard": "^12.0.1"
35 | },
36 | "dependencies": {
37 | "bluebird": "^3.5.3",
38 | "dot-object": "^2.1.3",
39 | "jexl": "^2.1.1",
40 | "lodash": "^4.17.20"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/creators.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 TechnologyAdvice
3 | */
4 |
5 | /**
6 | * Creators allow for methods which create values during validation when a
7 | * value is not supplied
8 | * @namespace creators
9 | */
10 | const creators = {
11 | /**
12 | * @memberof creators
13 | * @property {Object} Library of creators
14 | */
15 | lib: {},
16 |
17 | /**
18 | * Execute method calls the appropriate creator and returns the method or
19 | * throws and error if the creator does not exist
20 | * @memberof creators
21 | * @param {Object} def The property configuration
22 | * @param {*} value The value being validated
23 | * @returns {function} The return value of the creator function
24 | */
25 | execute: function(def, value) {
26 | if (value !== undefined) return value
27 | if (creators.lib[def.creator]) return creators.lib[def.creator]()
28 | throw new Error(`creator '${def.creator}' does not exist`)
29 | },
30 |
31 | /**
32 | * Adds a creator to the library
33 | * @memberof creators
34 | * @param {string} name The name of the creator
35 | * @param {function} fn The creator's method
36 | */
37 | add: (name, fn) => {
38 | if (typeof name !== 'string') throw new Error('creator name should be a string')
39 | if (typeof fn !== 'function') throw new Error('creator method should be a function')
40 | creators.lib[name] = fn
41 | }
42 | }
43 |
44 | module.exports = creators
45 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 TechnologyAdvice
3 | */
4 |
5 | const rules = require('./rules')
6 | const types = require('./types')
7 | const modifiers = require('./modifiers')
8 | const creators = require('./creators')
9 | const validators = require('./lib/validators')
10 | const ValidationError = require('./lib/error')
11 | const plugins = require('./lib/plugins')
12 |
13 | /**
14 | * The main object for Obey; exposes the core API methods for standard use as
15 | * well as the API for all other modules
16 | * @namespace obey
17 | */
18 | module.exports = {
19 | /**
20 | * API, exposes modules to make lib API accessible
21 | */
22 | rules, types, modifiers, creators, validators, ValidationError,
23 |
24 | /**
25 | * Returns a composed rule from a definition object
26 | * @memberof obey
27 | * @param {Object} def The rule definition
28 | * @returns {Object}
29 | */
30 | rule: def => rules.build(def),
31 |
32 | /**
33 | * Returns a composed model from a definition object
34 | * @memberof obey
35 | * @param {Object} obj The definition object
36 | * @param {boolean} [strict=true] Whether or not to enforce strict validation
37 | * @returns {Object}
38 | */
39 | model: (obj, strict = true) => rules.build({ type: 'object', keys: obj, strict }),
40 |
41 | /**
42 | * Creates and stores (or replaces) a type
43 | * @memberof obey
44 | * @param {string} name The name of the type
45 | * @param {Object|function} handler The type method or object of methods
46 | */
47 | type: (name, handler) => types.add(name, handler),
48 |
49 | /**
50 | * Creates and stores a modifier
51 | * @memberof obey
52 | * @param {string} name The modifier's name
53 | * @param {function} fn The method for the modifier
54 | */
55 | modifier: (name, fn) => modifiers.add(name, fn),
56 |
57 | /**
58 | * Creates and stores a creator
59 | * @memberof obey
60 | * @param {string} name The creator's name
61 | * @param {function} fn The method for the creator
62 | */
63 | creator: (name, fn) => creators.add(name, fn),
64 |
65 | /**
66 | * Adds given package to plugins lib.
67 | *
68 | * @param {string} name The package name
69 | * @param {function|Object} pkg The package reference
70 | */
71 | // TODO: make this actually useful
72 | use: (name, pkg) => plugins.add(name, pkg)
73 | }
74 |
--------------------------------------------------------------------------------
/src/lib/error.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 TechnologyAdvice
3 | */
4 | const util = require('util')
5 |
6 | /**
7 | * Compiles array items into string error messages
8 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} msgObjs Original array
9 | * of error message objects
10 | * @returns {Array}
11 | */
12 | const getMessages = (msgObjs) => {
13 | const messages = []
14 | msgObjs.forEach(obj => {
15 | messages.push((obj.key ? `${obj.key} (${obj.value}):` : `${obj.value}:`) + ` ${obj.message}`)
16 | })
17 | return messages
18 | }
19 |
20 | /**
21 | * Creates ValidationError object for throwing
22 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} message Raw array of
23 | * error objects
24 | */
25 | function ValidationError(message) {
26 | Object.defineProperty(this, 'name', { value: 'ValidationError' })
27 | Object.defineProperty(this, 'message', { value: getMessages(message).join('\n') })
28 | Object.defineProperty(this, 'collection', { value: message })
29 |
30 | // Fixes captureStackTrace missing on Safari
31 | if (typeof Error.captureStackTrace === 'function') {
32 | Error.captureStackTrace(this, ValidationError)
33 | } else {
34 | this.stack = new Error().stack
35 | }
36 | }
37 |
38 | // Creates instance of ValidationError as Error object
39 | util.inherits(ValidationError, Error)
40 |
41 | module.exports = ValidationError
42 |
--------------------------------------------------------------------------------
/src/lib/plugins.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace plugins
3 | */
4 | const plugins = {
5 | /**
6 | * @memberof plugins
7 | * @property {Object} Library of plugins
8 | */
9 | lib: {},
10 | /**
11 | * Adds a package to the library
12 | * @memberof creators
13 | * @param {string} name The name of the package
14 | * @param {function|Object} fn The package reference
15 | */
16 | add: (name, pkg) => {
17 | if (typeof name !== 'string') throw new Error('plugin name should be a string')
18 | if (typeof pkg !== 'object' && typeof pkg !== 'function') {
19 | throw new Error('plugin package should be an object or function')
20 | }
21 | plugins.lib[name] = pkg
22 | }
23 | }
24 |
25 | module.exports = plugins
26 |
--------------------------------------------------------------------------------
/src/lib/validators.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 TechnologyAdvice
3 | */
4 | /* eslint no-console: 0, consistent-return: 0 */
5 | const dot = require('dot-object')
6 | const jexl = require('jexl')
7 | const plugins = require('./plugins')
8 | const cloneDeep = require('lodash/cloneDeep')
9 |
10 | const validators = {
11 | /**
12 | * Validator default method, used by model
13 | * @param {Object} def The property configuration
14 | * @param {*} value The value being validated
15 | */
16 | default: function(def, value) {
17 | if (/^(number|boolean)$/.test(def.type)) {
18 | if (value === 0 || value === false) return value
19 | }
20 | return value || cloneDeep(def.default)
21 | },
22 |
23 | /**
24 | * Validator allowed method, used by model
25 | * @param {Object} def The property configuration
26 | * @param {*} value The value being validated
27 | * @param {string} key The key name of the property
28 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
29 | * to which any additional error objects will be added
30 | */
31 | allow: function(def, value, key, errors) {
32 | const type = 'allow'
33 | const sub = typeof def.allow === 'object' && !Array.isArray(def.allow) ? Object.keys(def.allow) : def.allow
34 | const subIsArray = Array.isArray(sub)
35 | if ((subIsArray && sub.indexOf(value) === -1) || (!subIsArray && sub !== value)) {
36 | if (value === '' && def.empty) return
37 | errors.push({ type, sub, key, value, message: `Value '${value}' is not allowed` })
38 | }
39 | },
40 |
41 | /**
42 | * Validator min method, used by model
43 | * @param {Object} def The property configuration
44 | * @param {*} value The value being validated
45 | * @param {string} key The key name of the property
46 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
47 | * to which any additional error objects will be added
48 | */
49 | min: function(def, value, key, errors) {
50 | const type = 'min'
51 | const sub = def.min
52 | if ((Array.isArray(value) || typeof value === 'string') && value.length < def.min) {
53 | errors.push({ type, sub, key, value, message: `Length must be greater than or equal to ${def.min}` })
54 | } else if (typeof value === 'number' && value < def.min) {
55 | errors.push({ type, sub, key, value, message: `Value must be greater than or equal to ${def.min}` })
56 | }
57 | },
58 |
59 | /**
60 | * Validator max method, used by model
61 | * @param {Object} def The property configuration
62 | * @param {*} value The value being validated
63 | * @param {string} key The key name of the property
64 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
65 | * to which any additional error objects will be added
66 | */
67 | max: function(def, value, key, errors) {
68 | const type = 'max'
69 | const sub = def.max
70 | if ((Array.isArray(value) || typeof value === 'string') && value.length > def.max) {
71 | errors.push({ type, sub, key, value, message: `Length must be less than or equal to ${def.max}` })
72 | } else if (typeof value === 'number' && value > def.max) {
73 | errors.push({ type, sub, key, value, message: `Value must be less than or equal to ${def.max}` })
74 | }
75 | },
76 |
77 | /**
78 | * Validator requiredIf method, used by model
79 | * @param {Object} def The property configuration
80 | * @param {*} value The value being validated
81 | * @param {string} key The key name of the property
82 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
83 | * to which any additional error objects will be added
84 | * @param {Object} data The full initial data object
85 | */
86 | requiredIf: function(def, value, key, errors, data) {
87 | const type = 'requiredIf'
88 | const sub = def.requiredIf
89 | if (typeof sub === 'object') {
90 | const field = Object.keys(sub)[0]
91 | const fieldArr = Array.isArray(sub[field]) ? sub[field] : [ sub[field] ]
92 | fieldArr.some(val => {
93 | /* istanbul ignore else */
94 | if (dot.pick(field, data) === val && value === undefined) {
95 | errors.push({ type, sub, key, value, message: `Value required by existing '${field}' value` })
96 | return true
97 | }
98 | })
99 | } else if (dot.pick(sub, data) !== undefined && value === undefined) {
100 | errors.push({ type, sub, key, value, message: `Value required because '${sub}' exists` })
101 | }
102 | },
103 | /**
104 | * Alias for requiredIf
105 | */
106 | requireIf: function(def, value, key, errors, data) {
107 | console.log('-----\nObey Warning: `requireIf` should be `requiredIf`\n-----')
108 | def.requiredIf = def.requireIf
109 | delete def.requireIf
110 | validators.requiredIf(def, value, key, errors, data)
111 | },
112 |
113 | /**
114 | * Validator requiredIfNot method, used by model
115 | * @param {Object} def The property configuration
116 | * @param {*} value The value being validated
117 | * @param {string} key The key name of the property
118 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
119 | * to which any additional error objects will be added
120 | * @param {Object} data The full initial data object
121 | */
122 | requiredIfNot: function(def, value, key, errors, data) {
123 | const type = 'requiredIfNot'
124 | const sub = def.requiredIfNot
125 | if (typeof sub === 'object') {
126 | const field = Object.keys(sub)[0]
127 | const fieldArr = Array.isArray(sub[field]) ? sub[field] : [ sub[field] ]
128 | fieldArr.some(val => {
129 | /* istanbul ignore else */
130 | if (dot.pick(field, data) !== val && value === undefined) {
131 | errors.push({ type, sub, key, value, message: `Value required because '${field}' value is not one specified` })
132 | return true
133 | }
134 | })
135 | } else if (dot.pick(sub, data) === undefined && value === undefined) {
136 | errors.push({ type, sub, key, value, message: `Value required because '${sub}' is undefined`})
137 | }
138 | },
139 | /**
140 | * Alias for requiredIfNot
141 | */
142 | requireIfNot: function(def, value, key, errors, data) {
143 | console.log('-----\nObey Warning: `requireIfNot` should be `requiredIfNot`\n-----')
144 | def.requiredIfNot = def.requireIfNot
145 | delete def.requireIfNot
146 | validators.requiredIfNot(def, value, key, errors, data)
147 | },
148 |
149 | /**
150 | * Validator equalTo method, used by model
151 | * @param {Object} def The property configuration
152 | * @param {*} value The value being validated
153 | * @param {string} key The key name of the property
154 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
155 | * to which any additional error objects will be added
156 | * @param {Object} data The full initial data object
157 | */
158 | equalTo: function(def, value, key, errors, data) {
159 | const type = 'equalTo'
160 | const sub = def.equalTo
161 | if (dot.pick(sub, data) !== value) {
162 | errors.push({ type, sub, key, value, message: `Value must match ${sub} value`})
163 | }
164 | },
165 |
166 | /**
167 | * Validator jexl method, used by model
168 | * @param {Object} def The property configuration
169 | * @param {*} value The value being validated
170 | * @param {string} key The key name of the property
171 | * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
172 | * to which any additional error objects will be added
173 | * @param {Object} data The full initial data object
174 | */
175 | jexl: (def, value, key, errors, data) => {
176 | const type = 'jexl'
177 | const sub = Array.isArray(def.jexl) ? def.jexl : [ def.jexl ]
178 | const promises = sub.map((obj) => {
179 | const {
180 | expr,
181 | message = 'Value failed Jexl evaluation'
182 | } = obj
183 | const instance = plugins.lib.jexl || jexl
184 | return instance.eval(expr, { root: data, value })
185 | .then(val => {
186 | if (!val) errors.push({ type, sub: obj, key, value, message })
187 | })
188 | .catch(() => {
189 | errors.push({ type, sub: obj, key, value, message })
190 | })
191 | })
192 | Promise.all(promises)
193 | }
194 | }
195 |
196 | module.exports = validators
197 |
--------------------------------------------------------------------------------
/src/modifiers.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 TechnologyAdvice
3 | */
4 |
5 | /**
6 | * Modifiers allow for coercion/modification of a value present in the object
7 | * when validation occurs
8 | * @namespace modifiers
9 | */
10 | const modifiers = {
11 | /**
12 | * @memberof modifiers
13 | * @property {Object} Library of modifiers
14 | */
15 | lib: {},
16 |
17 | /**
18 | * Execute method calls the appropriate modifier and passes in the value or
19 | * throws an error if the modifier does not exist
20 | * @memberof modifiers
21 | * @param {Object} def The property configuration
22 | * @param {*} value The value being validated
23 | * @returns {function} The return value of the modifier function
24 | */
25 | execute: function(def, value) {
26 | if (modifiers.lib[def.modifier]) return modifiers.lib[def.modifier](value)
27 | throw new Error(`Modifier '${def.modifier}' does not exist`)
28 | },
29 |
30 | /**
31 | * Adds new modifier to the library
32 | * @memberof modifiers
33 | * @param {string} name The name of the modifier
34 | * @param {function} fn The modifier's method
35 | */
36 | add: (name, fn) => {
37 | if (typeof name !== 'string') throw new Error('Modifier name should be a string')
38 | if (typeof fn !== 'function') throw new Error('Modifier method should be a function')
39 | modifiers.lib[name] = fn
40 | }
41 | }
42 |
43 | module.exports = modifiers
44 |
--------------------------------------------------------------------------------
/src/rules.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 TechnologyAdvice
3 | */
4 | /* eslint no-console: 0 */
5 | const types = require('./types')
6 | const modifiers = require('./modifiers')
7 | const creators = require('./creators')
8 | const Promise = require('bluebird')
9 | const validators = require('./lib/validators')
10 | const ValidationError = require('./lib/error')
11 |
12 | /**
13 | * @memberof rules
14 | * Defines all definition property checks available
15 | */
16 | const allProps = {
17 | creator: { name: 'creator', fn: creators.execute },
18 | default: { name: 'default', fn: validators.default },
19 | modifier: { name: 'modifier', fn: modifiers.execute },
20 | allow: { name: 'allow', fn: validators.allow },
21 | min: { name: 'min', fn: validators.min },
22 | max: { name: 'max', fn: validators.max },
23 | type: { name: 'type', fn: types.validate },
24 | requiredIf: { name: 'requiredIf', fn: validators.requiredIf },
25 | requiredIfNot: { name: 'requiredIfNot', fn: validators.requiredIfNot },
26 | requireIf: { name: 'requireIf', fn: validators.requireIf },
27 | requireIfNot: { name: 'requireIfNot', fn: validators.requireIfNot },
28 | equalTo: { name: 'equalTo', fn: validators.equalTo },
29 | jexl: { name: 'jexl', fn: validators.jexl }
30 | }
31 |
32 | /**
33 | * Rules is responsible for determining the execution of schema definition
34 | * properties during validation
35 | * @namespace rules
36 | */
37 | const rules = {
38 | /**
39 | * @memberof rules
40 | * @property {Object} Validation property setup and order of operations
41 | */
42 | props: {
43 | // Default props
44 | default: [
45 | allProps.creator,
46 | allProps.default,
47 | allProps.modifier,
48 | allProps.allow,
49 | allProps.min,
50 | allProps.max,
51 | allProps.type,
52 | allProps.requiredIf,
53 | allProps.requiredIfNot,
54 | allProps.requireIf,
55 | allProps.requireIfNot,
56 | allProps.equalTo,
57 | allProps.jexl
58 | ],
59 | // No value/undefined
60 | noVal: [
61 | allProps.creator,
62 | allProps.default,
63 | allProps.modifier,
64 | allProps.requiredIf,
65 | allProps.requiredIfNot,
66 | allProps.requireIf,
67 | allProps.requireIfNot,
68 | allProps.equalTo,
69 | allProps.jexl
70 | ],
71 | // No value, partial
72 | noValPartial: []
73 | },
74 |
75 | /**
76 | * Binds rule definition in validate method
77 | * @memberof rules
78 | * @param {Object} def The rule definition object
79 | */
80 | makeValidate: def => rules.validate.bind(null, def),
81 |
82 | /**
83 | * Iterates over the properties present in the rule definition and sets the
84 | * appropriate bindings to required methods
85 | * @memberof rules
86 | * @param {Object} def The rule definition object
87 | * @param {*} data The data (value) to validate
88 | * @param {Object} [opts={partial: false}] Specific options for validation process
89 | * @param {string|null} [key=null] Key for tracking parent in nested iterations
90 | * @param {Array<{type: string, sub: string, key: string, value: *, message: string}>} [errors=[]] An error array
91 | * to which any additional error objects will be added. If not specified, a new array will be created.
92 | * @param {boolean} [rejectOnFail=true] If true, resulting promise will reject if the errors array is not empty;
93 | * otherwise ValidationErrors will not cause a rejection
94 | * @param {Object|null} [initData=null] Initial data object
95 | * @returns {Promise.<*>} Resolves with the resulting data, with any defaults, creators, and modifiers applied.
96 | * Rejects with a ValidationError if applicable.
97 | */
98 | validate: (def, data, opts = { partial: false }, key = null, errors = [], rejectOnFail = true, initData = null) => {
99 | let passthruData = initData === null ? data : initData
100 | let curData = data
101 | def.opts = opts
102 | const props = rules.getProps(def, data)
103 | if (!def.type) throw new Error('Model properties must define a \'type\'')
104 | let chain = Promise.resolve(data)
105 | props.forEach(prop => {
106 | if (def.hasOwnProperty(prop.name)) {
107 | chain = chain
108 | .then(val => prop.fn(def, val, key, errors, passthruData))
109 | .then(res => {
110 | if (res !== undefined) curData = res
111 | return curData
112 | })
113 | }
114 | })
115 | return chain.then(res => {
116 | if (rejectOnFail && errors.length > 0) throw new ValidationError(errors)
117 | return res
118 | })
119 | },
120 |
121 | /**
122 | * Adds new rule to the lib
123 | * @memberof rules
124 | * @param {Object} def The rule definition
125 | * @returns {Object}
126 | */
127 | build: def => {
128 | return {
129 | def,
130 | validate: rules.makeValidate(def)
131 | }
132 | },
133 |
134 | /**
135 | * Gets props list according to partial, required, and allowNull specifications
136 | * @memberof rules
137 | * @param {Object} def The rule definition
138 | * @param {*} val The value being evaluated
139 | * @returns {Array}
140 | */
141 | getProps: (def, val) => {
142 | // Require(d) alias
143 | if (def.require) {
144 | def.required = def.require
145 | delete def.require
146 | console.log('-----\nObey Warning: `require` should be `required`\n-----')
147 | }
148 |
149 |
150 | // If default or creator defined, no need for conditional requires.
151 | if (def.default || def.creator) {
152 | const conditionalRequires = ['requireIf', 'requireIfNot', 'requiredIf', 'requiredIfNot']
153 | const rules = []
154 | conditionalRequires.forEach((key) => {
155 | if (def[key]) {
156 | rules.push(key)
157 | delete def[key]
158 | }
159 | })
160 | if (rules.length) {
161 | const message = [
162 | '-----\nObey Warning: removing conditional require',
163 | `rule(s) (${rules.join(', ')}) due to 'default' or`,
164 | '\'creator\' being defined\n-----'
165 | ].join(' ')
166 | console.log(message)
167 | }
168 | }
169 |
170 | // Partial and undefined
171 | if (def.opts.partial && val === undefined) return rules.props.noValPartial
172 | // Not required, undefined
173 | if (!def.required && val === undefined) return rules.props.noVal
174 | // AllowNull
175 | if (def.allowNull) {
176 | // val is null, look no further
177 | if (val === null) {
178 | return rules.props.noVal
179 | }
180 | // val is otherwise falsey, but not undefined, AND default is null
181 | if (!val && val !== undefined && def.default === null) {
182 | return rules.props.noVal
183 | }
184 | }
185 | // Use default
186 | return rules.props.default
187 | }
188 | }
189 |
190 | module.exports = rules
191 |
--------------------------------------------------------------------------------
/src/typeStrategies/README.md:
--------------------------------------------------------------------------------
1 | # Types
2 |
3 | Below is a list of supported, built-in types for the Obey library
4 |
5 | ## Any
6 |
7 | Supports any data type or format
8 |
9 | ## Array
10 |
11 | Checks for native type `array`
12 |
13 | ## Boolean
14 |
15 | Checks for native type `boolean`
16 |
17 | ## Email
18 |
19 | Checks for valid email, includes valid characters, `@` separator for address and domain, and valid TLD.
20 |
21 | ## IP
22 |
23 | Checks for valid IP Address:
24 |
25 | * `ip`: Default, checks IPv4 format
26 | * `ip:v4`: Checks IPv4 format
27 | * `ip:v6`: Checks IPv6 format
28 |
29 | ## Number
30 |
31 | Checks for native type `number`
32 |
33 | ## Object
34 |
35 | Checks for native type `object`
36 |
37 | ## Phone
38 |
39 | Checks for valid phone numbers:
40 |
41 | * `phone`: Default, valid with or without separators
42 | * `phone:numeric`: Check value for numeric phone number, 7-10 digits
43 |
44 | ## String
45 |
46 | Checks for valid string types:
47 |
48 | * `string`: Default, `typeof` should be `string`
49 | * `string:alphanumeric`: Checks value contains only alpha-numeric characters
50 |
51 | ## URL
52 |
53 | Checks for valid URL
54 |
55 | ## UUID
56 |
57 | Checks for valid v4 UUID
58 |
59 | ## Zip
60 |
61 | Checks for valid zip/postal codes:
62 |
63 | * `zip`: Default, checks generic postal code
64 | * `zip:us`: Checks US zip code format
65 | * `zip:ca`: Checks Canadian zip code format
--------------------------------------------------------------------------------
/src/typeStrategies/any.js:
--------------------------------------------------------------------------------
1 | const any = {
2 | default: context => context.value
3 | }
4 |
5 | module.exports = any
6 |
--------------------------------------------------------------------------------
/src/typeStrategies/array.js:
--------------------------------------------------------------------------------
1 | const Promise = require('bluebird')
2 | let rules
3 |
4 | const loadRules = () => {
5 | if (!rules) rules = require('../rules')
6 | }
7 |
8 | const array = {
9 | default: context => {
10 | // Ensure array
11 | if (!Array.isArray(context.value)) {
12 | return context.fail('Value must be an array')
13 | }
14 | // If empty (and empty allowed), move forward
15 | if (context.def.empty && context.value.length === 0) {
16 | return context.value
17 | }
18 | // If empty (and not empty allowed), fail
19 | if (!context.def.empty && context.value.length === 0) {
20 | return context.fail('Value must not be empty array')
21 | }
22 | // Specific array sub-validation
23 | if (!context.def.values) return context.value
24 |
25 | loadRules()
26 | const promises = context.value.map((elem, idx) => {
27 | return rules.validate(context.def.values, elem, context.def.opts, `${context.key}[${idx}]`, context.errors, false, context.initData)
28 | })
29 | return Promise.all(promises)
30 | }
31 | }
32 |
33 | module.exports = array
34 |
--------------------------------------------------------------------------------
/src/typeStrategies/boolean.js:
--------------------------------------------------------------------------------
1 | const boolean = {
2 | default: context => {
3 | if (typeof context.value !== 'boolean') {
4 | context.fail('Value must be a boolean')
5 | }
6 | }
7 | }
8 |
9 | module.exports = boolean
10 |
--------------------------------------------------------------------------------
/src/typeStrategies/email.js:
--------------------------------------------------------------------------------
1 | const email = {
2 | _regex: {
3 | default: /.+@.+\.\S+/
4 | },
5 | default: context => {
6 | if (context.value == null || !context.value || !context.value.toString().match(email._regex.default)) {
7 | context.fail('Value must be a valid email')
8 | }
9 | }
10 | }
11 |
12 | module.exports = email
13 |
--------------------------------------------------------------------------------
/src/typeStrategies/index.js:
--------------------------------------------------------------------------------
1 | // Import all strategies
2 | const any = require('./any')
3 | const array = require('./array')
4 | const boolean = require('./boolean')
5 | const email = require('./email')
6 | const ip = require('./ip')
7 | const number = require('./number')
8 | const object = require('./object')
9 | const phone = require('./phone')
10 | const string = require('./string')
11 | const url = require('./url')
12 | const uuid = require('./uuid')
13 | const zip = require('./zip')
14 |
15 | // Export object with all built-in strategies
16 | module.exports = {
17 | any,
18 | array,
19 | boolean,
20 | email,
21 | ip,
22 | number,
23 | object,
24 | phone,
25 | string,
26 | url,
27 | uuid,
28 | zip
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/typeStrategies/ip.js:
--------------------------------------------------------------------------------
1 | const ip = {
2 | _regex: {
3 | v4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
4 | v6: /^((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$/
5 | },
6 | v4: context => {
7 | if (context.value == null || !context.value.length || !context.value.toString().match(ip._regex.v4)) {
8 | context.fail('Value must be a valid IPv4 address')
9 | }
10 | },
11 | v6: context => {
12 | if (context.value == null || !context.value.length || !context.value.toString().match(ip._regex.v6)) {
13 | context.fail('Value must be a valid IPv6 address')
14 | }
15 | },
16 | default: context => ip.v4(context)
17 | }
18 |
19 | module.exports = ip
20 |
--------------------------------------------------------------------------------
/src/typeStrategies/number.js:
--------------------------------------------------------------------------------
1 | const number = {
2 | default: context => {
3 | if (typeof context.value !== 'number' || Number.isNaN(context.value)) {
4 | context.fail('Value must be a number')
5 | }
6 | }
7 | }
8 |
9 | module.exports = number
10 |
--------------------------------------------------------------------------------
/src/typeStrategies/object.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | const Promise = require('bluebird')
3 | let rules
4 |
5 | const loadRules = () => {
6 | if (!rules) rules = require('../rules')
7 | }
8 |
9 | /**
10 | * Validates an object using the definition's `keys` property
11 | * @param {Object} context An Obey type context
12 | * @param {string} keyPrefix A prefix to include before the key in an error message
13 | * @returns {Promise.