@vsaas/loopback-datasource-juggler 10.0.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/LICENSE +25 -0
- package/NOTICE +23 -0
- package/README.md +74 -0
- package/dist/_virtual/_rolldown/runtime.js +4 -0
- package/dist/index.js +26 -0
- package/dist/lib/browser.depd.js +13 -0
- package/dist/lib/case-utils.js +21 -0
- package/dist/lib/connectors/kv-memory.js +158 -0
- package/dist/lib/connectors/memory.js +810 -0
- package/dist/lib/connectors/transient.js +126 -0
- package/dist/lib/dao.js +2445 -0
- package/dist/lib/datasource.js +2215 -0
- package/dist/lib/date-string.js +87 -0
- package/dist/lib/geo.js +244 -0
- package/dist/lib/globalize.js +33 -0
- package/dist/lib/hooks.js +79 -0
- package/dist/lib/id-utils.js +66 -0
- package/dist/lib/include.js +795 -0
- package/dist/lib/include_utils.js +104 -0
- package/dist/lib/introspection.js +37 -0
- package/dist/lib/jutil.js +65 -0
- package/dist/lib/kvao/delete-all.js +57 -0
- package/dist/lib/kvao/delete.js +43 -0
- package/dist/lib/kvao/expire.js +35 -0
- package/dist/lib/kvao/get.js +34 -0
- package/dist/lib/kvao/index.js +28 -0
- package/dist/lib/kvao/iterate-keys.js +38 -0
- package/dist/lib/kvao/keys.js +55 -0
- package/dist/lib/kvao/set.js +39 -0
- package/dist/lib/kvao/ttl.js +35 -0
- package/dist/lib/list.js +101 -0
- package/dist/lib/mixins.js +58 -0
- package/dist/lib/model-builder.js +608 -0
- package/dist/lib/model-definition.js +231 -0
- package/dist/lib/model-utils.js +368 -0
- package/dist/lib/model.js +586 -0
- package/dist/lib/observer.js +235 -0
- package/dist/lib/relation-definition.js +2604 -0
- package/dist/lib/relations.js +587 -0
- package/dist/lib/scope.js +392 -0
- package/dist/lib/transaction.js +183 -0
- package/dist/lib/types.js +58 -0
- package/dist/lib/utils.js +625 -0
- package/dist/lib/validations.js +742 -0
- package/dist/package.js +93 -0
- package/package.json +85 -0
- package/types/common.d.ts +28 -0
- package/types/connector.d.ts +52 -0
- package/types/datasource.d.ts +324 -0
- package/types/date-string.d.ts +21 -0
- package/types/inclusion-mixin.d.ts +44 -0
- package/types/index.d.ts +36 -0
- package/types/kv-model.d.ts +201 -0
- package/types/model.d.ts +368 -0
- package/types/observer-mixin.d.ts +174 -0
- package/types/persisted-model.d.ts +505 -0
- package/types/query.d.ts +108 -0
- package/types/relation-mixin.d.ts +577 -0
- package/types/relation.d.ts +301 -0
- package/types/scope.d.ts +92 -0
- package/types/transaction-mixin.d.ts +47 -0
- package/types/types.d.ts +65 -0
- package/types/validation-mixin.d.ts +287 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.js");
|
|
3
|
+
const require_lib_globalize = require("./globalize.js");
|
|
4
|
+
//#region src/lib/validations.ts
|
|
5
|
+
var require_validations = /* @__PURE__ */ require_runtime.__commonJSMin(((exports) => {
|
|
6
|
+
const g = require_lib_globalize();
|
|
7
|
+
const util = require("util");
|
|
8
|
+
/*!
|
|
9
|
+
* Module exports
|
|
10
|
+
*/
|
|
11
|
+
exports.ValidationError = ValidationError;
|
|
12
|
+
exports.Validatable = Validatable;
|
|
13
|
+
/**
|
|
14
|
+
* This class provides methods that add validation cababilities to models.
|
|
15
|
+
* Each of the validations runs when the `obj.isValid()` method is called.
|
|
16
|
+
*
|
|
17
|
+
* All of the methods have an options object parameter that has a
|
|
18
|
+
* `message` property. When there is only a single error message, this property is just a string;
|
|
19
|
+
* for example: `Post.validatesPresenceOf('title', { message: 'can not be blank' });`
|
|
20
|
+
*
|
|
21
|
+
* In more complicated cases it can be a set of messages, for each possible error condition; for example:
|
|
22
|
+
* `User.validatesLengthOf('password', { min: 6, max: 20, message: {min: 'too short', max: 'too long'}});`
|
|
23
|
+
* @class Validatable
|
|
24
|
+
*/
|
|
25
|
+
function Validatable() {}
|
|
26
|
+
/**
|
|
27
|
+
* Validate presence of one or more specified properties.
|
|
28
|
+
*
|
|
29
|
+
* Requires a model to include a property to be considered valid; fails when validated field is blank.
|
|
30
|
+
*
|
|
31
|
+
* For example, validate presence of title
|
|
32
|
+
* ```
|
|
33
|
+
* Post.validatesPresenceOf('title');
|
|
34
|
+
* ```
|
|
35
|
+
* Validate that model has first, last, and age properties:
|
|
36
|
+
* ```
|
|
37
|
+
* User.validatesPresenceOf('first', 'last', 'age');
|
|
38
|
+
* ```
|
|
39
|
+
* Example with custom message
|
|
40
|
+
* ```
|
|
41
|
+
* Post.validatesPresenceOf('title', {message: 'Cannot be blank'});
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param {String} propertyName One or more property names.
|
|
45
|
+
* @options {Object} options Configuration parameters; see below.
|
|
46
|
+
* @property {String} message Error message to use instead of default.
|
|
47
|
+
* @property {String} if Validate only if `if` exists.
|
|
48
|
+
* @property {String} unless Validate only if `unless` exists.
|
|
49
|
+
*/
|
|
50
|
+
Validatable.validatesPresenceOf = getConfigurator("presence");
|
|
51
|
+
/**
|
|
52
|
+
* Validate absence of one or more specified properties.
|
|
53
|
+
*
|
|
54
|
+
* A model should not include a property to be considered valid; fails when validated field is not blank.
|
|
55
|
+
*
|
|
56
|
+
* For example, validate absence of reserved
|
|
57
|
+
* ```
|
|
58
|
+
* Post.validatesAbsenceOf('reserved', { unless: 'special' });
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @param {String} propertyName One or more property names.
|
|
62
|
+
* @options {Object} options Configuration parameters; see below.
|
|
63
|
+
* @property {String} message Error message to use instead of default.
|
|
64
|
+
* @property {String} if Validate only if `if` exists.
|
|
65
|
+
* @property {String} unless Validate only if `unless` exists.
|
|
66
|
+
*/
|
|
67
|
+
Validatable.validatesAbsenceOf = getConfigurator("absence");
|
|
68
|
+
/**
|
|
69
|
+
* Validate length.
|
|
70
|
+
*
|
|
71
|
+
* Require a property length to be within a specified range.
|
|
72
|
+
*
|
|
73
|
+
* There are three kinds of validations: min, max, is.
|
|
74
|
+
*
|
|
75
|
+
* Default error messages:
|
|
76
|
+
*
|
|
77
|
+
* - min: too short
|
|
78
|
+
* - max: too long
|
|
79
|
+
* - is: length is wrong
|
|
80
|
+
*
|
|
81
|
+
* Example: length validations
|
|
82
|
+
* ```
|
|
83
|
+
* User.validatesLengthOf('password', {min: 7});
|
|
84
|
+
* User.validatesLengthOf('email', {max: 100});
|
|
85
|
+
* User.validatesLengthOf('state', {is: 2});
|
|
86
|
+
* User.validatesLengthOf('nick', {min: 3, max: 15});
|
|
87
|
+
* ```
|
|
88
|
+
* Example: length validations with custom error messages
|
|
89
|
+
* ```
|
|
90
|
+
* User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}});
|
|
91
|
+
* User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}});
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @param {String} propertyName Property name to validate.
|
|
95
|
+
* @options {Object} options Configuration parameters; see below.
|
|
96
|
+
* @property {Number} is Value that property must equal to validate.
|
|
97
|
+
* @property {Number} min Value that property must be less than to be valid.
|
|
98
|
+
* @property {Number} max Value that property must be less than to be valid.
|
|
99
|
+
* @property {Object} message Optional object with string properties for custom error message for each validation: is, min, or max.
|
|
100
|
+
*/
|
|
101
|
+
Validatable.validatesLengthOf = getConfigurator("length");
|
|
102
|
+
/**
|
|
103
|
+
* Validate numericality.
|
|
104
|
+
*
|
|
105
|
+
* Requires a value for property to be either an integer or number.
|
|
106
|
+
*
|
|
107
|
+
* Example
|
|
108
|
+
* ```
|
|
109
|
+
* User.validatesNumericalityOf('age', { message: { number: 'is not a number' }});
|
|
110
|
+
* User.validatesNumericalityOf('age', {int: true, message: { int: 'is not an integer' }});
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @param {String} propertyName Property name to validate.
|
|
114
|
+
* @options {Object} options Configuration parameters; see below.
|
|
115
|
+
* @property {Boolean} int If true, then property must be an integer to be valid.
|
|
116
|
+
* @property {Boolean} allowBlank Allow property to be blank.
|
|
117
|
+
* @property {Boolean} allowNull Allow property to be null.
|
|
118
|
+
* @property {Object} message Optional object with string properties for 'int' for integer validation. Default error messages:
|
|
119
|
+
* - number: is not a number
|
|
120
|
+
* - int: is not an integer
|
|
121
|
+
*/
|
|
122
|
+
Validatable.validatesNumericalityOf = getConfigurator("numericality");
|
|
123
|
+
/**
|
|
124
|
+
* Validate inclusion in set.
|
|
125
|
+
*
|
|
126
|
+
* Require a value for property to be in the specified array.
|
|
127
|
+
*
|
|
128
|
+
* Example:
|
|
129
|
+
* ```
|
|
130
|
+
* User.validatesInclusionOf('gender', {in: ['male', 'female']});
|
|
131
|
+
* User.validatesInclusionOf('role', {
|
|
132
|
+
* in: ['admin', 'moderator', 'user'], message: 'is not allowed'
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @param {String} propertyName Property name to validate.
|
|
137
|
+
* @options {Object} options Configuration parameters; see below.
|
|
138
|
+
* @property {Array} in Property must match one of the values in the array to be valid.
|
|
139
|
+
* @property {String} message Optional error message if property is not valid.
|
|
140
|
+
* Default error message: "is not included in the list".
|
|
141
|
+
* @property {Boolean} allowNull Whether null values are allowed.
|
|
142
|
+
*/
|
|
143
|
+
Validatable.validatesInclusionOf = getConfigurator("inclusion");
|
|
144
|
+
/**
|
|
145
|
+
* Validate exclusion in a set.
|
|
146
|
+
*
|
|
147
|
+
* Require a property value not be in the specified array.
|
|
148
|
+
*
|
|
149
|
+
* Example: `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});`
|
|
150
|
+
*
|
|
151
|
+
* @param {String} propertyName Property name to validate.
|
|
152
|
+
* @options {Object} options Configuration parameters; see below.
|
|
153
|
+
* @property {Array} in Property must not match any of the values in the array to be valid.
|
|
154
|
+
* @property {String} message Optional error message if property is not valid. Default error message: "is reserved".
|
|
155
|
+
* @property {Boolean} allowNull Whether null values are allowed.
|
|
156
|
+
*/
|
|
157
|
+
Validatable.validatesExclusionOf = getConfigurator("exclusion");
|
|
158
|
+
/**
|
|
159
|
+
* Validate format.
|
|
160
|
+
*
|
|
161
|
+
* Require a model to include a property that matches the given format.
|
|
162
|
+
*
|
|
163
|
+
* Example: `User.validatesFormatOf('name', {with: /\w+/});`
|
|
164
|
+
*
|
|
165
|
+
* @param {String} propertyName Property name to validate.
|
|
166
|
+
* @options {Object} options Configuration parameters; see below.
|
|
167
|
+
* @property {RegExp} with Regular expression to validate format.
|
|
168
|
+
* @property {String} message Optional error message if property is not valid. Default error message: " is invalid".
|
|
169
|
+
* @property {Boolean} allowNull Whether null values are allowed.
|
|
170
|
+
*/
|
|
171
|
+
Validatable.validatesFormatOf = getConfigurator("format");
|
|
172
|
+
/**
|
|
173
|
+
* Validate using custom validation function.
|
|
174
|
+
*
|
|
175
|
+
* Example:
|
|
176
|
+
*```javascript
|
|
177
|
+
* User.validate('name', customValidator, {message: 'Bad name'});
|
|
178
|
+
* function customValidator(err) {
|
|
179
|
+
* if (this.name === 'bad') err();
|
|
180
|
+
* });
|
|
181
|
+
* var user = new User({name: 'Peter'});
|
|
182
|
+
* user.isValid(); // true
|
|
183
|
+
* user.name = 'bad';
|
|
184
|
+
* user.isValid(); // false
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* @param {String} propertyName Property name to validate.
|
|
188
|
+
* @param {Function} validatorFn Custom validation function.
|
|
189
|
+
* @options {Object} options Configuration parameters; see below.
|
|
190
|
+
* @property {String} message Optional error message if property is not valid. Default error message: " is invalid".
|
|
191
|
+
* @property {Boolean} allowNull Whether null values are allowed.
|
|
192
|
+
*/
|
|
193
|
+
Validatable.validate = getConfigurator("custom");
|
|
194
|
+
/**
|
|
195
|
+
* Validate using custom asynchronous validation function.
|
|
196
|
+
*
|
|
197
|
+
* Example:
|
|
198
|
+
*```js
|
|
199
|
+
* User.validateAsync('name', customValidator, {message: 'Bad name'});
|
|
200
|
+
* function customValidator(err, done) {
|
|
201
|
+
* process.nextTick(function () {
|
|
202
|
+
* if (this.name === 'bad') err();
|
|
203
|
+
* done();
|
|
204
|
+
* });
|
|
205
|
+
* });
|
|
206
|
+
* var user = new User({name: 'Peter'});
|
|
207
|
+
* user.isValid(); // false (because async validation setup)
|
|
208
|
+
* user.isValid(function (isValid) {
|
|
209
|
+
* isValid; // true
|
|
210
|
+
* })
|
|
211
|
+
* user.name = 'bad';
|
|
212
|
+
* user.isValid(); // false
|
|
213
|
+
* user.isValid(function (isValid) {
|
|
214
|
+
* isValid; // false
|
|
215
|
+
* })
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* @param {String} propertyName Property name to validate.
|
|
219
|
+
* @param {Function} validatorFn Custom validation function.
|
|
220
|
+
* @options {Object} options Configuration parameters; see below.
|
|
221
|
+
* @property {String} message Optional error message if property is not valid. Default error message: " is invalid".
|
|
222
|
+
* @property {Boolean} allowNull Whether null values are allowed.
|
|
223
|
+
*/
|
|
224
|
+
Validatable.validateAsync = getConfigurator("custom", { async: true });
|
|
225
|
+
/**
|
|
226
|
+
* Validate uniqueness of the value for a property in the collection of models.
|
|
227
|
+
*
|
|
228
|
+
* Not available for all connectors. Currently supported with these connectors:
|
|
229
|
+
* - In Memory
|
|
230
|
+
* - Oracle
|
|
231
|
+
* - MongoDB
|
|
232
|
+
*
|
|
233
|
+
* ```
|
|
234
|
+
* // The login must be unique across all User instances.
|
|
235
|
+
* User.validatesUniquenessOf('login');
|
|
236
|
+
*
|
|
237
|
+
* // Assuming SiteUser.belongsTo(Site)
|
|
238
|
+
* // The login must be unique within each Site.
|
|
239
|
+
* SiteUser.validateUniquenessOf('login', { scopedTo: ['siteId'] });
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @param {String} propertyName Property name to validate.
|
|
243
|
+
* @options {Object} options Configuration parameters; see below.
|
|
244
|
+
* @property {RegExp} with Regular expression to validate format.
|
|
245
|
+
* @property {Array.<String>} scopedTo List of properties defining the scope.
|
|
246
|
+
* @property {String} message Optional error message if property is not valid. Default error message: "is not unique".
|
|
247
|
+
* @property {Boolean} allowNull Whether null values are allowed.
|
|
248
|
+
* @property {String} ignoreCase Make the validation case insensitive.
|
|
249
|
+
* @property {String} if Validate only if `if` exists.
|
|
250
|
+
* @property {String} unless Validate only if `unless` exists.
|
|
251
|
+
*/
|
|
252
|
+
Validatable.validatesUniquenessOf = getConfigurator("uniqueness", { async: true });
|
|
253
|
+
/**
|
|
254
|
+
* Validate if a value for a property is a Date.
|
|
255
|
+
*
|
|
256
|
+
* Example
|
|
257
|
+
* ```
|
|
258
|
+
* User.validatesDateOf('today', {message: 'today is not a date!'});
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* @param {String} propertyName Property name to validate.
|
|
262
|
+
* @options {Object} options Configuration parameters; see below.
|
|
263
|
+
* @property {String} message Error message to use instead of default.
|
|
264
|
+
*/
|
|
265
|
+
Validatable.validatesDateOf = getConfigurator("date");
|
|
266
|
+
/*!
|
|
267
|
+
* Presence validator
|
|
268
|
+
*/
|
|
269
|
+
function validatePresence(attr, conf, err, _options) {
|
|
270
|
+
if (blank(this[attr])) err();
|
|
271
|
+
}
|
|
272
|
+
/*!
|
|
273
|
+
* Absence validator
|
|
274
|
+
*/
|
|
275
|
+
function validateAbsence(attr, conf, err, _options) {
|
|
276
|
+
if (!blank(this[attr])) err();
|
|
277
|
+
}
|
|
278
|
+
/*!
|
|
279
|
+
* Length validator
|
|
280
|
+
*/
|
|
281
|
+
function validateLength(attr, conf, err, _options) {
|
|
282
|
+
if (nullCheck.call(this, attr, conf, err)) return;
|
|
283
|
+
const len = this[attr].length;
|
|
284
|
+
if (conf.min && len < conf.min) err("min");
|
|
285
|
+
if (conf.max && len > conf.max) err("max");
|
|
286
|
+
if (conf.is && len !== conf.is) err("is");
|
|
287
|
+
}
|
|
288
|
+
/*!
|
|
289
|
+
* Numericality validator
|
|
290
|
+
*/
|
|
291
|
+
function validateNumericality(attr, conf, err, _options) {
|
|
292
|
+
if (nullCheck.call(this, attr, conf, err)) return;
|
|
293
|
+
if (typeof this[attr] !== "number" || isNaN(this[attr])) return err("number");
|
|
294
|
+
if (conf.int && this[attr] !== Math.round(this[attr])) return err("int");
|
|
295
|
+
}
|
|
296
|
+
/*!
|
|
297
|
+
* Inclusion validator
|
|
298
|
+
*/
|
|
299
|
+
function validateInclusion(attr, conf, err, _options) {
|
|
300
|
+
if (nullCheck.call(this, attr, conf, err)) return;
|
|
301
|
+
if (!~conf.in.indexOf(this[attr])) err();
|
|
302
|
+
}
|
|
303
|
+
/*!
|
|
304
|
+
* Exclusion validator
|
|
305
|
+
*/
|
|
306
|
+
function validateExclusion(attr, conf, err, _options) {
|
|
307
|
+
if (nullCheck.call(this, attr, conf, err)) return;
|
|
308
|
+
if (~conf.in.indexOf(this[attr])) err();
|
|
309
|
+
}
|
|
310
|
+
/*!
|
|
311
|
+
* Format validator
|
|
312
|
+
*/
|
|
313
|
+
function validateFormat(attr, conf, err, _options) {
|
|
314
|
+
if (nullCheck.call(this, attr, conf, err)) return;
|
|
315
|
+
if (typeof this[attr] === "string" || typeof this[attr] === "number") {
|
|
316
|
+
if (!new RegExp(conf["with"]).test(this[attr])) err();
|
|
317
|
+
} else err();
|
|
318
|
+
}
|
|
319
|
+
/*!
|
|
320
|
+
* Custom validator
|
|
321
|
+
*/
|
|
322
|
+
function validateCustom(attr, conf, err, options, done) {
|
|
323
|
+
if (typeof options === "function") {
|
|
324
|
+
done = options;
|
|
325
|
+
options = {};
|
|
326
|
+
}
|
|
327
|
+
if (!done) conf.customValidator.call(this, err, options);
|
|
328
|
+
else if (conf.customValidator.length === 3) conf.customValidator.call(this, err, options, done);
|
|
329
|
+
else conf.customValidator.call(this, err, done);
|
|
330
|
+
}
|
|
331
|
+
function escapeStringRegexp(str) {
|
|
332
|
+
if (typeof str !== "string") throw new TypeError("Expected a string");
|
|
333
|
+
return str.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
334
|
+
}
|
|
335
|
+
/*!
|
|
336
|
+
* Uniqueness validator
|
|
337
|
+
*/
|
|
338
|
+
function validateUniqueness(attr, conf, err, options, done) {
|
|
339
|
+
if (typeof options === "function") {
|
|
340
|
+
done = options;
|
|
341
|
+
options = {};
|
|
342
|
+
}
|
|
343
|
+
if (blank(this[attr])) return process.nextTick(done);
|
|
344
|
+
const cond = { where: {} };
|
|
345
|
+
if (conf && conf.ignoreCase) cond.where[attr] = new RegExp("^" + escapeStringRegexp(this[attr]) + "$", "i");
|
|
346
|
+
else cond.where[attr] = this[attr];
|
|
347
|
+
if (conf && conf.scopedTo) conf.scopedTo.forEach(function(k) {
|
|
348
|
+
if (this[k] !== void 0) cond.where[k] = this[k];
|
|
349
|
+
}, this);
|
|
350
|
+
const idName = this.constructor.definition.idName();
|
|
351
|
+
const isNewRecord = this.isNewRecord();
|
|
352
|
+
this.constructor.find(cond, options, function(error, found) {
|
|
353
|
+
if (error) err(error);
|
|
354
|
+
else if (found.length > 1) err();
|
|
355
|
+
else if (found.length === 1 && idName === attr && isNewRecord) err();
|
|
356
|
+
else if (found.length === 1 && (!this.id || !found[0].id || found[0].id.toString() != this.id.toString())) err();
|
|
357
|
+
done();
|
|
358
|
+
}.bind(this));
|
|
359
|
+
}
|
|
360
|
+
/*!
|
|
361
|
+
* Date validator
|
|
362
|
+
*/
|
|
363
|
+
function validateDate(attr, conf, err) {
|
|
364
|
+
if (this[attr] === null || this[attr] === void 0) return;
|
|
365
|
+
const date = new Date(this[attr]);
|
|
366
|
+
if (isNaN(date.getTime())) return err();
|
|
367
|
+
}
|
|
368
|
+
const validators = {
|
|
369
|
+
presence: validatePresence,
|
|
370
|
+
absence: validateAbsence,
|
|
371
|
+
length: validateLength,
|
|
372
|
+
numericality: validateNumericality,
|
|
373
|
+
inclusion: validateInclusion,
|
|
374
|
+
exclusion: validateExclusion,
|
|
375
|
+
format: validateFormat,
|
|
376
|
+
custom: validateCustom,
|
|
377
|
+
uniqueness: validateUniqueness,
|
|
378
|
+
date: validateDate
|
|
379
|
+
};
|
|
380
|
+
function getConfigurator(name, opts) {
|
|
381
|
+
return function() {
|
|
382
|
+
const args = Array.prototype.slice.call(arguments);
|
|
383
|
+
args[1] = args[1] || {};
|
|
384
|
+
configure(this, name, args, opts);
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* This method performs validation and triggers validation hooks.
|
|
389
|
+
* Before validation the `obj.errors` collection is cleaned.
|
|
390
|
+
* Each validation can add errors to `obj.errors` collection.
|
|
391
|
+
* If collection is not blank, validation failed.
|
|
392
|
+
*
|
|
393
|
+
* NOTE: This method can be called as synchronous only when no asynchronous validation is
|
|
394
|
+
* configured. It's strongly recommended to run all validations as asyncronous.
|
|
395
|
+
*
|
|
396
|
+
* Example: ExpressJS controller - render user if valid, show flash otherwise
|
|
397
|
+
* ```javascript
|
|
398
|
+
* user.isValid(function (valid) {
|
|
399
|
+
* if (valid) res.render({user: user});
|
|
400
|
+
* else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users');
|
|
401
|
+
* });
|
|
402
|
+
* ```
|
|
403
|
+
* Another example:
|
|
404
|
+
* ```javascript
|
|
405
|
+
* user.isValid(function (valid) {
|
|
406
|
+
* if (!valid) {
|
|
407
|
+
* console.log(user.errors);
|
|
408
|
+
* // => hash of errors
|
|
409
|
+
* // => {
|
|
410
|
+
* // => username: [errmessage, errmessage, ...],
|
|
411
|
+
* // => email: ...
|
|
412
|
+
* // => }
|
|
413
|
+
* }
|
|
414
|
+
* });
|
|
415
|
+
* ```
|
|
416
|
+
* @callback {Function} callback Called with (valid).
|
|
417
|
+
* @param {Object} data Data to be validated.
|
|
418
|
+
* @param {Object} options Options to be specified upon validation.
|
|
419
|
+
* @returns {Boolean} True if no asynchronous validation is configured and all properties pass validation.
|
|
420
|
+
*/
|
|
421
|
+
Validatable.prototype.isValid = function(callback, data, options) {
|
|
422
|
+
options = options || {};
|
|
423
|
+
let valid = true, wait = 0, async = false;
|
|
424
|
+
const inst = this;
|
|
425
|
+
const validations = this.constructor.validations;
|
|
426
|
+
const reportDiscardedProperties = this.__strict && this.__unknownProperties && this.__unknownProperties.length;
|
|
427
|
+
if (typeof validations !== "object" && !reportDiscardedProperties) {
|
|
428
|
+
cleanErrors(this);
|
|
429
|
+
if (callback) this.trigger("validate", function(validationsDone) {
|
|
430
|
+
validationsDone.call(inst, function() {
|
|
431
|
+
callback(valid);
|
|
432
|
+
});
|
|
433
|
+
}, data, callback);
|
|
434
|
+
return valid;
|
|
435
|
+
}
|
|
436
|
+
Object.defineProperty(this, "errors", {
|
|
437
|
+
enumerable: false,
|
|
438
|
+
configurable: true,
|
|
439
|
+
value: new Errors()
|
|
440
|
+
});
|
|
441
|
+
this.trigger("validate", function(validationsDone) {
|
|
442
|
+
const inst = this;
|
|
443
|
+
let asyncFail = false;
|
|
444
|
+
Object.keys(validations || {}).forEach(function(attr) {
|
|
445
|
+
(validations[attr] || []).forEach(function(v) {
|
|
446
|
+
if (v.options && v.options.async) {
|
|
447
|
+
async = true;
|
|
448
|
+
wait += 1;
|
|
449
|
+
process.nextTick(function() {
|
|
450
|
+
validationFailed(inst, attr, v, options, done);
|
|
451
|
+
});
|
|
452
|
+
} else if (validationFailed(inst, attr, v, options)) valid = false;
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
if (reportDiscardedProperties) for (const ix in inst.__unknownProperties) {
|
|
456
|
+
const key = inst.__unknownProperties[ix];
|
|
457
|
+
const code = "unknown-property";
|
|
458
|
+
const msg = defaultMessages[code];
|
|
459
|
+
inst.errors.add(key, msg, code);
|
|
460
|
+
valid = false;
|
|
461
|
+
}
|
|
462
|
+
if (!async) validationsDone.call(inst, function() {
|
|
463
|
+
if (valid) cleanErrors(inst);
|
|
464
|
+
if (callback) callback(valid);
|
|
465
|
+
});
|
|
466
|
+
function done(fail) {
|
|
467
|
+
asyncFail = asyncFail || fail;
|
|
468
|
+
if (--wait === 0) validationsDone.call(inst, function() {
|
|
469
|
+
if (valid && !asyncFail) cleanErrors(inst);
|
|
470
|
+
if (callback) callback(valid && !asyncFail);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}, data, callback);
|
|
474
|
+
if (async) return;
|
|
475
|
+
else return valid;
|
|
476
|
+
};
|
|
477
|
+
function cleanErrors(inst) {
|
|
478
|
+
Object.defineProperty(inst, "errors", {
|
|
479
|
+
enumerable: false,
|
|
480
|
+
configurable: true,
|
|
481
|
+
value: false
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
function validationFailed(inst, attr, conf, options, cb) {
|
|
485
|
+
conf.options;
|
|
486
|
+
if (typeof options === "function") {
|
|
487
|
+
cb = options;
|
|
488
|
+
options = {};
|
|
489
|
+
}
|
|
490
|
+
if (typeof attr !== "string") return false;
|
|
491
|
+
if (skipValidation(inst, conf, "if") || skipValidation(inst, conf, "unless")) {
|
|
492
|
+
if (cb) cb(false);
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
let fail = false;
|
|
496
|
+
const validator = validators[conf.validation];
|
|
497
|
+
const validatorArguments = [];
|
|
498
|
+
validatorArguments.push(attr);
|
|
499
|
+
validatorArguments.push(conf);
|
|
500
|
+
validatorArguments.push(function onerror(kind) {
|
|
501
|
+
let message, code = conf.code || conf.validation;
|
|
502
|
+
if (conf.message) message = conf.message;
|
|
503
|
+
if (!message && defaultMessages[conf.validation]) message = defaultMessages[conf.validation];
|
|
504
|
+
if (!message) message = "is invalid";
|
|
505
|
+
if (kind) {
|
|
506
|
+
code += "." + kind;
|
|
507
|
+
if (message[kind]) message = message[kind];
|
|
508
|
+
else if (defaultMessages.common[kind]) message = defaultMessages.common[kind];
|
|
509
|
+
else message = "is invalid";
|
|
510
|
+
}
|
|
511
|
+
if (kind !== false) inst.errors.add(attr, message, code);
|
|
512
|
+
fail = true;
|
|
513
|
+
});
|
|
514
|
+
validatorArguments.push(options);
|
|
515
|
+
if (cb) validatorArguments.push(function() {
|
|
516
|
+
cb(fail);
|
|
517
|
+
});
|
|
518
|
+
validator.apply(inst, validatorArguments);
|
|
519
|
+
return fail;
|
|
520
|
+
}
|
|
521
|
+
function skipValidation(inst, conf, kind) {
|
|
522
|
+
let doValidate = true;
|
|
523
|
+
if (typeof conf[kind] === "function") {
|
|
524
|
+
doValidate = conf[kind].call(inst);
|
|
525
|
+
if (kind === "unless") doValidate = !doValidate;
|
|
526
|
+
} else if (typeof conf[kind] === "string") if (typeof inst[conf[kind]] === "function") {
|
|
527
|
+
doValidate = inst[conf[kind]].call(inst);
|
|
528
|
+
if (kind === "unless") doValidate = !doValidate;
|
|
529
|
+
} else if (inst.__data.hasOwnProperty(conf[kind])) {
|
|
530
|
+
doValidate = inst[conf[kind]];
|
|
531
|
+
if (kind === "unless") doValidate = !doValidate;
|
|
532
|
+
} else doValidate = kind === "if";
|
|
533
|
+
return !doValidate;
|
|
534
|
+
}
|
|
535
|
+
const defaultMessages = {
|
|
536
|
+
presence: "can't be blank",
|
|
537
|
+
absence: "can't be set",
|
|
538
|
+
"unknown-property": "is not defined in the model",
|
|
539
|
+
length: {
|
|
540
|
+
min: "too short",
|
|
541
|
+
max: "too long",
|
|
542
|
+
is: "length is wrong"
|
|
543
|
+
},
|
|
544
|
+
common: {
|
|
545
|
+
blank: "is blank",
|
|
546
|
+
null: "is null"
|
|
547
|
+
},
|
|
548
|
+
numericality: {
|
|
549
|
+
int: "is not an integer",
|
|
550
|
+
number: "is not a number"
|
|
551
|
+
},
|
|
552
|
+
inclusion: "is not included in the list",
|
|
553
|
+
exclusion: "is reserved",
|
|
554
|
+
uniqueness: "is not unique",
|
|
555
|
+
date: "is not a valid date"
|
|
556
|
+
};
|
|
557
|
+
/**
|
|
558
|
+
* Checks if attribute is undefined or null. Calls err function with 'blank' or 'null'.
|
|
559
|
+
* See defaultMessages. You can affect this behaviour with conf.allowBlank and conf.allowNull.
|
|
560
|
+
* @private
|
|
561
|
+
* @param {String} attr Property name of attribute
|
|
562
|
+
* @param {Object} conf conf object for validator
|
|
563
|
+
* @param {Function} err
|
|
564
|
+
* @return {Boolean} returns true if attribute is null or blank
|
|
565
|
+
*/
|
|
566
|
+
function nullCheck(attr, conf, err) {
|
|
567
|
+
if (typeof this[attr] === "undefined" || this[attr] === "") {
|
|
568
|
+
if (!conf.allowBlank) err("blank");
|
|
569
|
+
return true;
|
|
570
|
+
} else if (this[attr] === null) {
|
|
571
|
+
if (!conf.allowNull) err("null");
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
/*!
|
|
577
|
+
* Return true when v is undefined, blank array, null or empty string
|
|
578
|
+
* otherwise returns false
|
|
579
|
+
*
|
|
580
|
+
* @param {Mix} v
|
|
581
|
+
* Returns true if `v` is blank.
|
|
582
|
+
*/
|
|
583
|
+
function blank(v) {
|
|
584
|
+
if (typeof v === "undefined") return true;
|
|
585
|
+
if (v instanceof Array && v.length === 0) return true;
|
|
586
|
+
if (v === null) return true;
|
|
587
|
+
if (typeof v === "number" && isNaN(v)) return true;
|
|
588
|
+
if (typeof v == "string" && v === "") return true;
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
function configure(cls, validation, args, opts) {
|
|
592
|
+
if (!cls.validations) Object.defineProperty(cls, "validations", {
|
|
593
|
+
writable: true,
|
|
594
|
+
configurable: true,
|
|
595
|
+
enumerable: false,
|
|
596
|
+
value: {}
|
|
597
|
+
});
|
|
598
|
+
args = [].slice.call(args);
|
|
599
|
+
let conf;
|
|
600
|
+
if (typeof args[args.length - 1] === "object") conf = args.pop();
|
|
601
|
+
else conf = {};
|
|
602
|
+
if (validation === "custom" && typeof args[args.length - 1] === "function") conf.customValidator = args.pop();
|
|
603
|
+
conf.validation = validation;
|
|
604
|
+
args.forEach(function(attr) {
|
|
605
|
+
if (typeof attr === "string") {
|
|
606
|
+
const validation = Object.assign({}, conf);
|
|
607
|
+
validation.options = opts || {};
|
|
608
|
+
cls.validations[attr] = cls.validations[attr] || [];
|
|
609
|
+
cls.validations[attr].push(validation);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
function Errors() {
|
|
614
|
+
Object.defineProperty(this, "codes", {
|
|
615
|
+
enumerable: false,
|
|
616
|
+
configurable: true,
|
|
617
|
+
value: {}
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
Errors.prototype.add = function(field, message, code) {
|
|
621
|
+
code = code || "invalid";
|
|
622
|
+
if (!this[field]) {
|
|
623
|
+
this[field] = [];
|
|
624
|
+
this.codes[field] = [];
|
|
625
|
+
}
|
|
626
|
+
this[field].push(message);
|
|
627
|
+
this.codes[field].push(code);
|
|
628
|
+
};
|
|
629
|
+
/**
|
|
630
|
+
* ValidationError is raised when the application attempts to save an invalid model instance.
|
|
631
|
+
* Example:
|
|
632
|
+
* ```
|
|
633
|
+
* {
|
|
634
|
+
* "name": "ValidationError",
|
|
635
|
+
* "status": 422,
|
|
636
|
+
* "message": "The Model instance is not valid. \
|
|
637
|
+
* See `details` property of the error object for more info.",
|
|
638
|
+
* "statusCode": 422,
|
|
639
|
+
* "details": {
|
|
640
|
+
* "context": "user",
|
|
641
|
+
* "codes": {
|
|
642
|
+
* "password": [
|
|
643
|
+
* "presence"
|
|
644
|
+
* ],
|
|
645
|
+
* "email": [
|
|
646
|
+
* "uniqueness"
|
|
647
|
+
* ]
|
|
648
|
+
* },
|
|
649
|
+
* "messages": {
|
|
650
|
+
* "password": [
|
|
651
|
+
* "can't be blank"
|
|
652
|
+
* ],
|
|
653
|
+
* "email": [
|
|
654
|
+
* "Email already exists"
|
|
655
|
+
* ]
|
|
656
|
+
* }
|
|
657
|
+
* },
|
|
658
|
+
* }
|
|
659
|
+
* ```
|
|
660
|
+
* You might run into situations where you need to raise a validation error yourself, for example in a "before" hook or a
|
|
661
|
+
* custom model method.
|
|
662
|
+
* ```
|
|
663
|
+
* MyModel.prototype.preflight = function(changes, callback) {
|
|
664
|
+
* // Update properties, do not save to db
|
|
665
|
+
* for (var key in changes) {
|
|
666
|
+
* model[key] = changes[key];
|
|
667
|
+
* }
|
|
668
|
+
*
|
|
669
|
+
* if (model.isValid()) {
|
|
670
|
+
* return callback(null, { success: true });
|
|
671
|
+
* }
|
|
672
|
+
*
|
|
673
|
+
* // This line shows how to create a ValidationError
|
|
674
|
+
* var err = new MyModel.ValidationError(model);
|
|
675
|
+
* callback(err);
|
|
676
|
+
* }
|
|
677
|
+
* ```
|
|
678
|
+
*
|
|
679
|
+
* @private
|
|
680
|
+
*/
|
|
681
|
+
function ValidationError(obj) {
|
|
682
|
+
if (!(this instanceof ValidationError)) return new ValidationError(obj);
|
|
683
|
+
this.name = "ValidationError";
|
|
684
|
+
const context = obj && obj.constructor && obj.constructor.modelName;
|
|
685
|
+
this.message = g.f("The %s instance is not valid. Details: %s.", context ? "`" + context + "`" : "model", formatErrors(obj.errors, obj.toJSON()) || "(unknown)");
|
|
686
|
+
this.statusCode = 422;
|
|
687
|
+
this.details = {
|
|
688
|
+
context,
|
|
689
|
+
codes: obj.errors && obj.errors.codes,
|
|
690
|
+
messages: obj.errors
|
|
691
|
+
};
|
|
692
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
693
|
+
else if (errorHasStackProperty) this.stack = (/* @__PURE__ */ new Error()).stack;
|
|
694
|
+
}
|
|
695
|
+
util.inherits(ValidationError, Error);
|
|
696
|
+
const errorHasStackProperty = !!(/* @__PURE__ */ new Error()).stack;
|
|
697
|
+
ValidationError.maxPropertyStringLength = 32;
|
|
698
|
+
function formatErrors(errors, propertyValues) {
|
|
699
|
+
const DELIM = "; ";
|
|
700
|
+
errors = errors || {};
|
|
701
|
+
return Object.getOwnPropertyNames(errors).filter(function(propertyName) {
|
|
702
|
+
return Array.isArray(errors[propertyName]);
|
|
703
|
+
}).map(function(propertyName) {
|
|
704
|
+
const messages = errors[propertyName];
|
|
705
|
+
const propertyValue = propertyValues[propertyName];
|
|
706
|
+
return messages.map(function(msg) {
|
|
707
|
+
return formatPropertyError(propertyName, propertyValue, msg);
|
|
708
|
+
}).join(DELIM);
|
|
709
|
+
}).join(DELIM);
|
|
710
|
+
}
|
|
711
|
+
function formatPropertyError(propertyName, propertyValue, errorMessage) {
|
|
712
|
+
let formattedValue;
|
|
713
|
+
const valueType = typeof propertyValue;
|
|
714
|
+
if (valueType === "string") formattedValue = JSON.stringify(truncatePropertyString(propertyValue));
|
|
715
|
+
else if (propertyValue instanceof Date) formattedValue = isNaN(propertyValue.getTime()) ? propertyValue.toString() : propertyValue.toISOString();
|
|
716
|
+
else if (valueType === "object") {
|
|
717
|
+
formattedValue = util.inspect(propertyValue, {
|
|
718
|
+
showHidden: false,
|
|
719
|
+
color: false,
|
|
720
|
+
depth: Array.isArray(propertyValue) ? 1 : 0
|
|
721
|
+
});
|
|
722
|
+
formattedValue = truncatePropertyString(formattedValue);
|
|
723
|
+
} else formattedValue = truncatePropertyString("" + propertyValue);
|
|
724
|
+
return "`" + propertyName + "` " + errorMessage + " (value: " + formattedValue + ")";
|
|
725
|
+
}
|
|
726
|
+
function truncatePropertyString(value) {
|
|
727
|
+
let len = ValidationError.maxPropertyStringLength;
|
|
728
|
+
if (value.length <= len) return value;
|
|
729
|
+
let tail;
|
|
730
|
+
const m = value.match(/([ \t})\]]+)$/);
|
|
731
|
+
if (m) {
|
|
732
|
+
tail = m[1].slice(-3);
|
|
733
|
+
len -= tail.length;
|
|
734
|
+
} else {
|
|
735
|
+
tail = value.slice(-3);
|
|
736
|
+
len -= 3;
|
|
737
|
+
}
|
|
738
|
+
return value.slice(0, len - 4) + "..." + tail;
|
|
739
|
+
}
|
|
740
|
+
}));
|
|
741
|
+
//#endregion
|
|
742
|
+
module.exports = require_validations();
|