monastery 1.28.5 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.json CHANGED
File without changes
package/docs/errors.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/docs/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ![](./assets/imgs/monastery.jpg)
2
2
 
3
- [![NPM](https://img.shields.io/npm/v/monastery.svg)](https://www.npmjs.com/package/monastery) [![Build Status](https://travis-ci.com/boycce/monastery.svg?branch=master)](https://travis-ci.com/boycce/monastery)
3
+ [![NPM](https://img.shields.io/npm/v/monastery.svg)](https://www.npmjs.com/package/monastery) [![Build Status](https://travis-ci.com/boycce/monastery.svg?branch=master)](https://app.travis-ci.com/github/boycce/monastery)
4
4
 
5
5
  ## Features
6
6
 
package/docs/rules.md CHANGED
File without changes
package/lib/index.js CHANGED
File without changes
@@ -213,6 +213,7 @@ module.exports = {
213
213
  * @param {object} dataRoot - data
214
214
  * @param {object} field - field schema
215
215
  * @param {string} path - full field path
216
+ * @param {object} opts - original validate() options
216
217
  * @this model
217
218
  * @return {array} errors
218
219
  */
@@ -242,38 +243,32 @@ module.exports = {
242
243
 
243
244
  for (let ruleName in field) {
244
245
  if (this._ignoredRules.indexOf(ruleName) > -1) continue
245
- // ignore undefined updated root properties, or
246
- if (util.isUndefined(value) && ((opts.update && !path.match(/\./)) || opts.ignoreUndefined)) continue
247
- let error = this._validateRule(dataRoot, ruleName, field, field[ruleName], value, path)
246
+ let error = this._validateRule(dataRoot, ruleName, field, field[ruleName], value, opts, path)
248
247
  if (error && ruleName == 'required') return [error] // only show the required error
249
248
  if (error) errors.push(error)
250
249
  }
251
250
  return errors
252
251
  },
253
252
 
254
- _validateRule: function(dataRoot, ruleName, field, ruleArg, value, path) {
255
- //this.debug(path, field, ruleName, ruleArg, value)
253
+ _validateRule: function(dataRoot, ruleName, field, ruleArg, value, opts, path) {
254
+ // this.debug(path, field, ruleName, ruleArg, value)
256
255
  // Remove [] from the message path, and simply ignore non-numeric children to test for all array items
257
256
  ruleArg = ruleArg === true? undefined : ruleArg
258
257
  let rule = this.rules[ruleName] || rules[ruleName]
259
258
  let fieldName = path.match(/[^.]+$/)[0]
260
259
  let ruleMessageKey = this._getMostSpecificKeyMatchingPath(this.messages, path)
261
260
  let ruleMessage = ruleMessageKey && this.messages[ruleMessageKey][ruleName]
261
+ let ignoreUndefined = util.isDefined(opts.ignoreUndefined) ? opts.ignoreUndefined : rule.ignoreUndefined
262
262
  if (!ruleMessage) ruleMessage = rule.message
263
263
 
264
- if (ruleName !== 'required') {
265
- // Ignore undefined when not testing 'required'
266
- if (typeof value === 'undefined') return
264
+ // Ignore undefined (if updated root property, or ignoring)
265
+ if ((ignoreUndefined || (opts.update && !path.match(/\./))) && typeof value === 'undefined') return
267
266
 
268
- // Ignore null if not testing required
269
- if (value === null && !field.isObject && !field.isArray) return
270
-
271
- // Ignore null if nullObject is set on objects or arrays
272
- if (value === null && field.nullObject) return
273
- }
267
+ // Ignore null (if nullObject is set on objects or arrays) (todo: change to ignoreNull)
268
+ if (field.nullObject && (field.isObject || field.isArray) && value === null) return
274
269
 
275
270
  // Ignore empty strings
276
- if (value === '' && rule.ignoreEmptyString) return
271
+ if (rule.ignoreEmptyString && value === '') return
277
272
 
278
273
  // Rule failed
279
274
  if (!rule.fn.call(dataRoot, value, ruleArg, path, this)) return {
package/lib/model.js CHANGED
@@ -61,6 +61,7 @@ let Model = module.exports = function(name, opts, manager) {
61
61
  // Update with formatted rule
62
62
  let formattedRule = util.isObject(rule)? rule : { fn: rule }
63
63
  if (!formattedRule.message) formattedRule.message = `Invalid data property for rule "${ruleName}".`
64
+ if (typeof formattedRule.ignoreUndefined == 'undefined') formattedRule.ignoreUndefined = true
64
65
  this.rules[ruleName] = formattedRule
65
66
  }
66
67
  }, this)
package/lib/rules.js CHANGED
@@ -5,6 +5,7 @@ let validator = require('validator')
5
5
  module.exports = {
6
6
 
7
7
  required: {
8
+ ignoreUndefined: false,
8
9
  message: 'This field is required.',
9
10
  fn: function(x) {
10
11
  if (util.isArray(x) && !x.length) return false
@@ -12,9 +13,10 @@ module.exports = {
12
13
  }
13
14
  },
14
15
 
15
- // Rules below ignore null
16
+ // Type rules below ignore undefined (default for custom model rules)
16
17
 
17
18
  'isBoolean': {
19
+ ignoreUndefined: true,
18
20
  message: 'Value was not a boolean.',
19
21
  tryParse: function(x) {
20
22
  if (typeof x === 'string' && x === 'true') return true
@@ -25,13 +27,8 @@ module.exports = {
25
27
  return typeof x === 'boolean'
26
28
  }
27
29
  },
28
- 'isNotEmptyString': {
29
- message: 'Value was an empty string.',
30
- fn: function(x) {
31
- return x !== ''
32
- }
33
- },
34
30
  'isArray': {
31
+ ignoreUndefined: true,
35
32
  message: 'Value was not an array.',
36
33
  tryParse: function(x) {
37
34
  if (x === '') return null
@@ -44,6 +41,7 @@ module.exports = {
44
41
  }
45
42
  },
46
43
  'isDate': {
44
+ ignoreUndefined: true,
47
45
  message: 'Value was not a unix timestamp.',
48
46
  tryParse: function(x) {
49
47
  if (util.isString(x) && x.match(/^[+-]?[0-9]+$/)) return x // keep string nums intact
@@ -55,6 +53,7 @@ module.exports = {
55
53
  }
56
54
  },
57
55
  'isImageObject': {
56
+ ignoreUndefined: true,
58
57
  message: 'Invalid image value',
59
58
  messageLong: 'Image fields need to either be null, undefined, file, or an object containing the following '
60
59
  + 'fields \'{ bucket, date, filename, filesize, path, uid }\'',
@@ -70,6 +69,7 @@ module.exports = {
70
69
  }
71
70
  },
72
71
  'isInteger': {
72
+ ignoreUndefined: true,
73
73
  message: 'Value was not an integer.',
74
74
  tryParse: function(x) {
75
75
  if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return x // keep string nums intact
@@ -81,6 +81,7 @@ module.exports = {
81
81
  }
82
82
  },
83
83
  'isNumber': {
84
+ ignoreUndefined: true,
84
85
  message: 'Value was not a number.',
85
86
  tryParse: function(x) {
86
87
  if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return x // keep string nums intact
@@ -92,6 +93,7 @@ module.exports = {
92
93
  }
93
94
  },
94
95
  'isObject': {
96
+ ignoreUndefined: true,
95
97
  message: 'Value was not an object.',
96
98
  tryParse: function(x) {
97
99
  if (x === '') return null
@@ -104,18 +106,21 @@ module.exports = {
104
106
  }
105
107
  },
106
108
  'isString': {
109
+ ignoreUndefined: true,
107
110
  message: 'Value was not a string.',
108
111
  fn: function(x) {
109
112
  return typeof x === 'string'
110
113
  }
111
114
  },
112
115
  'isAny': {
116
+ ignoreUndefined: true,
113
117
  message: '',
114
118
  fn: function(x) {
115
119
  return true
116
120
  }
117
121
  },
118
122
  'isId': {
123
+ ignoreUndefined: true,
119
124
  message: 'Value was not a valid ObjectId.',
120
125
  tryParse: function(x) {
121
126
  // Try and parse value to a mongodb ObjectId
@@ -130,6 +135,7 @@ module.exports = {
130
135
  }
131
136
  },
132
137
  'max': {
138
+ ignoreUndefined: true,
133
139
  message: (x, arg) => 'Value was greater than the configured maximum (' + arg + ')',
134
140
  fn: function(x, arg) {
135
141
  if (typeof x !== 'number') { throw new Error ('Value was not a number.') }
@@ -137,16 +143,27 @@ module.exports = {
137
143
  }
138
144
  },
139
145
  'min': {
146
+ ignoreUndefined: true,
140
147
  message: (x, arg) => 'Value was less than the configured minimum (' + arg + ')',
141
148
  fn: function(x, arg) {
142
149
  if (typeof x !== 'number') { throw new Error ('Value was not a number.') }
143
150
  return x >= arg
144
151
  }
145
152
  },
153
+ 'isNotEmptyString': {
154
+ ignoreUndefined: true,
155
+ message: 'Value was an empty string.',
156
+ fn: function(x) {
157
+ return x !== ''
158
+ }
159
+ },
160
+
161
+ // Rules below ignore undefined & empty strings
162
+ // (e.g. an empty email field can be saved that isn't required)
146
163
 
147
- // Rules below ignore null & empty strings
148
164
  'enum': {
149
165
  ignoreEmptyString: true,
166
+ ignoreUndefined: true,
150
167
  message: (x, arg) => 'Invalid enum value',
151
168
  fn: function(x, arg) {
152
169
  for (let item of arg) {
@@ -154,62 +171,73 @@ module.exports = {
154
171
  }
155
172
  }
156
173
  },
157
- 'hasAgreed': {
158
- message: (x, arg) => 'Please agree to the terms and conditions.',
159
- fn: function(x, arg) { return !x }
160
- },
174
+ // 'hasAgreed': {
175
+ // message: (x, arg) => 'Please agree to the terms and conditions.',
176
+ // fn: function(x, arg) { return !x }
177
+ // },
161
178
  'isAfter': {
162
179
  ignoreEmptyString: true,
180
+ ignoreUndefined: true,
163
181
  message: (x, arg) => 'Value was before the configured time (' + arg + ')',
164
182
  fn: function(x, arg) { return validator.isAfter(x, arg) }
165
183
  },
166
184
  'isBefore': {
167
185
  ignoreEmptyString: true,
186
+ ignoreUndefined: true,
168
187
  message: (x, arg) => 'Value was after the configured time (' + arg + ')',
169
188
  fn: function(x, arg) { return validator.isBefore(x, arg) }
170
189
  },
171
190
  'isCreditCard': {
172
191
  ignoreEmptyString: true,
192
+ ignoreUndefined: true,
173
193
  message: 'Value was not a valid credit card.',
174
194
  fn: function(x, arg) { return validator.isCreditCard(x, arg) }
175
195
  },
176
196
  'isEmail': {
177
197
  ignoreEmptyString: true,
198
+ ignoreUndefined: true,
178
199
  message: 'Please enter a valid email address.',
179
200
  fn: function(x, arg) { return validator.isEmail(x, arg) }
180
201
  },
181
202
  'isHexColor': {
182
203
  ignoreEmptyString: true,
204
+ ignoreUndefined: true,
183
205
  message: 'Value was not a valid hex color.',
184
206
  fn: function(x, arg) { return validator.isHexColor(x, arg) }
185
207
  },
186
208
  'isIn': {
187
209
  ignoreEmptyString: true,
210
+ ignoreUndefined: true,
188
211
  message: (x, arg) => 'Value was not in the configured whitelist (' + arg.join(', ') + ')',
189
212
  fn: function(x, arg) { return validator.isIn(x, arg) }
190
213
  },
191
214
  'isIP': {
192
215
  ignoreEmptyString: true,
216
+ ignoreUndefined: true,
193
217
  message: 'Value was not a valid IP address.',
194
218
  fn: function(x, arg) { return validator.isIP(x, arg) }
195
219
  },
196
220
  'isNotIn': {
197
221
  ignoreEmptyString: true,
222
+ ignoreUndefined: true,
198
223
  message: (x, arg) => 'Value was in the configured blacklist (' + arg.join(', ') + ')',
199
224
  fn: function(x, arg) { return !validator.isIn(x, arg) }
200
225
  },
201
226
  'isURL': {
202
227
  ignoreEmptyString: true,
228
+ ignoreUndefined: true,
203
229
  message: 'Value was not a valid URL.',
204
230
  fn: function(x, arg) { return validator.isURL(x, arg === true? undefined : arg) }
205
231
  },
206
232
  'isUUID': {
207
233
  ignoreEmptyString: true,
234
+ ignoreUndefined: true,
208
235
  message: 'Value was not a valid UUID.',
209
236
  fn: function(x, arg) { return validator.isUUID(x) }
210
237
  },
211
238
  'minLength': {
212
239
  ignoreEmptyString: true,
240
+ ignoreUndefined: true,
213
241
  message: function(x, arg) {
214
242
  if (typeof x === 'string') return 'Value needs to be at least ' + arg + ' characters long.'
215
243
  else return 'Value needs to contain a minimum of ' + arg + ' items.'
@@ -222,6 +250,7 @@ module.exports = {
222
250
  },
223
251
  'maxLength': {
224
252
  ignoreEmptyString: true,
253
+ ignoreUndefined: true,
225
254
  message: function(x, arg) {
226
255
  if (typeof x === 'string') return 'Value was longer than the configured maximum length (' + arg + ')'
227
256
  else return 'Value cannot contain more than ' + arg + ' items.'
@@ -234,6 +263,7 @@ module.exports = {
234
263
  },
235
264
  'regex': {
236
265
  ignoreEmptyString: true,
266
+ ignoreUndefined: true,
237
267
  message: (x, arg) => 'Value did not match the configured regular expression (' + arg + ')',
238
268
  fn: function(x, arg) {
239
269
  if (util.isRegex(arg)) return validator.matches(x, arg)
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "monastery",
3
3
  "description": "⛪ A straight forward MongoDB ODM built around Monk",
4
4
  "author": "Ricky Boyce",
5
- "version": "1.28.5",
5
+ "version": "1.29.0",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/test/monk.js CHANGED
File without changes
package/test/populate.js CHANGED
File without changes
package/test/util.js CHANGED
File without changes
package/test/validate.js CHANGED
@@ -481,7 +481,7 @@ module.exports = function(monastery, opendb) {
481
481
  name: { type: 'string', bigName: 8 },
482
482
  animals: [{
483
483
  name: { type: 'string', bigName: 8 }
484
- }]
484
+ }],
485
485
  },
486
486
  rules: {
487
487
  bigName: function(value, ruleArg) {
@@ -489,6 +489,21 @@ module.exports = function(monastery, opendb) {
489
489
  }
490
490
  }
491
491
  })
492
+ let user2 = db.model('user2', {
493
+ fields: {
494
+ name: { type: 'string' },
495
+ nickname: { type: 'string', requiredIfNoName: true },
496
+ age: { type: 'number', required: true },
497
+ },
498
+ rules: {
499
+ requiredIfNoName: {
500
+ ignoreUndefined: false,
501
+ fn: function(value, ruleArg) {
502
+ return value || this.name
503
+ }
504
+ }
505
+ }
506
+ })
492
507
 
493
508
  // Basic field
494
509
  await expect(user.validate({ name: 'benjamin' })).resolves.toEqual({ name: 'benjamin' })
@@ -509,6 +524,39 @@ module.exports = function(monastery, opendb) {
509
524
  detail: 'Invalid data property for rule "bigName".',
510
525
  meta: { rule: 'bigName', model: 'user', field: 'name' }
511
526
  })
527
+
528
+ // Required rule based off another field (create)
529
+ await expect(user2.validate({ name: 'benjamin', age: 12 })).resolves.toEqual({
530
+ name: 'benjamin',
531
+ age: 12
532
+ })
533
+ await expect(user2.validate({ nickname: 'benny', age: 12 })).resolves.toEqual({
534
+ nickname: 'benny',
535
+ age: 12
536
+ })
537
+ await expect(user2.validate({ })).rejects.toEqual([
538
+ {
539
+ 'detail': 'Invalid data property for rule "requiredIfNoName".',
540
+ 'meta': { 'field': 'nickname', 'model': 'user2', 'rule': 'requiredIfNoName'},
541
+ 'status': '400',
542
+ 'title': 'nickname'
543
+ }, {
544
+ 'detail': 'This field is required.',
545
+ 'meta': { 'field': 'age', 'model': 'user2', 'rule': 'required'},
546
+ 'status': '400',
547
+ 'title': 'age'
548
+ }
549
+ ])
550
+ await expect(user2.validate({ }, { ignoreUndefined: true })).resolves.toEqual({})
551
+
552
+ // Required rule based off another field (update)
553
+ await expect(user2.validate({ }, { update: true })).resolves.toEqual({})
554
+ await expect(user2.validate({ nickname: '' }, { update: true })).rejects.toEqual([{
555
+ 'detail': 'Invalid data property for rule "requiredIfNoName".',
556
+ 'meta': { 'field': 'nickname', 'model': 'user2', 'rule': 'requiredIfNoName'},
557
+ 'status': '400',
558
+ 'title': 'nickname'
559
+ }])
512
560
  })
513
561
 
514
562
  test('Validated data', async () => {
@@ -532,8 +580,13 @@ module.exports = function(monastery, opendb) {
532
580
  // Ignores invalid data
533
581
  await expect(user.validate({ badprop: true, schema: {} })).resolves.toEqual({})
534
582
 
535
- // Allows null data
536
- await expect(user.validate({ name: null })).resolves.toEqual({ name: null })
583
+ // Rejects null for non object/array fields
584
+ await expect(user.validate({ name: null })).rejects.toEqual([{
585
+ 'detail': 'Value was not a string.',
586
+ 'meta': {'detailLong': undefined, 'field': 'name', 'model': 'user', 'rule': 'isString'},
587
+ 'status':'400',
588
+ 'title': 'name'
589
+ }])
537
590
 
538
591
  // String data
539
592
  await expect(user.validate({ name: 'Martin Luther' })).resolves.toEqual({ name: 'Martin Luther' })
@@ -556,9 +609,14 @@ module.exports = function(monastery, opendb) {
556
609
 
557
610
  // Subdocument property data (null)
558
611
  await expect(user.validate({ animals: { dog: null }}))
559
- .resolves.toEqual({ animals: { dog: null }})
560
-
561
- // Subdocument property data (bad data)
612
+ .rejects.toEqual([{
613
+ 'detail': 'Value was not a string.',
614
+ 'meta': {'detailLong': undefined, 'field': 'dog', 'model': 'user', 'rule': 'isString'},
615
+ 'status': '400',
616
+ 'title': 'animals.dog',
617
+ }])
618
+
619
+ // Subdocument property data (unknown data)
562
620
  await expect(user.validate({ animals: { dog: 'sparky', cat: 'grumpy' } }))
563
621
  .resolves.toEqual({ animals: { dog: 'sparky' } })
564
622
 
@@ -668,7 +726,12 @@ module.exports = function(monastery, opendb) {
668
726
  await expect(user2.validate({ amount: 0 })).resolves.toEqual({ amount: 0 }) // required
669
727
  await expect(user.validate({ amount: '0' })).resolves.toEqual({ amount: 0 }) // required
670
728
  await expect(user.validate({ amount: undefined })).resolves.toEqual({}) // not required
671
- await expect(user.validate({ amount: null })).resolves.toEqual({ amount: null }) // not required
729
+ await expect(user.validate({ amount: null })).rejects.toEqual([{ // type error
730
+ 'detail': 'Value was not a number.',
731
+ 'meta': { 'field': 'amount', 'model': 'user', 'rule': 'isNumber'},
732
+ 'status': '400',
733
+ 'title': 'amount'
734
+ }])
672
735
 
673
736
  // Number required
674
737
  let mock1 = {
@@ -734,6 +797,12 @@ module.exports = function(monastery, opendb) {
734
797
  }})
735
798
 
736
799
  // Subdocument data (null/string)
800
+ await expect(user.validate({ animals: 'notAnObject' })).rejects.toEqual([{
801
+ 'detail': 'Value was not an object.',
802
+ 'meta': {'detailLong': undefined, 'field': 'animals', 'model': 'user', 'rule': 'isObject'},
803
+ 'status': '400',
804
+ 'title': 'animals'
805
+ }])
737
806
  await expect(user.validate({ animals: '', names: null })).resolves.toEqual({ animals: null, names: null })
738
807
 
739
808
  db.close()