@e22m4u/js-repository 0.8.2 → 0.8.4

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.
@@ -0,0 +1,95 @@
1
+ import {DataType} from './data-type.js';
2
+ import {Service} from '@e22m4u/js-service';
3
+ import {BlankValuesService} from '@e22m4u/js-empty-values';
4
+ import {InvalidArgumentError} from '../../../errors/index.js';
5
+ import {ModelDefinitionUtils} from '../model-definition-utils.js';
6
+
7
+ /**
8
+ * Required property validator.
9
+ */
10
+ export class RequiredPropertyValidator extends Service {
11
+ /**
12
+ * Validate.
13
+ *
14
+ * @param {string} modelName
15
+ * @param {object} modelData
16
+ * @param {boolean} [isPartial]
17
+ */
18
+ validate(modelName, modelData, isPartial = false) {
19
+ if (!modelName || typeof modelName !== 'string') {
20
+ throw new InvalidArgumentError(
21
+ 'Parameter "modelName" must be a non-empty String, but %v was given.',
22
+ modelName,
23
+ );
24
+ }
25
+ if (
26
+ !modelData ||
27
+ typeof modelData !== 'object' ||
28
+ Array.isArray(modelData)
29
+ ) {
30
+ throw new InvalidArgumentError(
31
+ 'Data of the model %v should be an Object, but %v was given.',
32
+ modelName,
33
+ modelData,
34
+ );
35
+ }
36
+ if (typeof isPartial !== 'boolean') {
37
+ throw new InvalidArgumentError(
38
+ 'Parameter "isPartial" must be a Boolean, but %v was given.',
39
+ isPartial,
40
+ );
41
+ }
42
+ const propDefs =
43
+ this.getService(
44
+ ModelDefinitionUtils,
45
+ ).getPropertiesDefinitionInBaseModelHierarchy(modelName);
46
+ const propNames = Object.keys(isPartial ? modelData : propDefs);
47
+ const blankValuesService = this.getService(BlankValuesService);
48
+ for (const propName of propNames) {
49
+ const propDef = propDefs[propName];
50
+ if (!propDef || typeof propDef !== 'object') {
51
+ continue;
52
+ }
53
+ // проверка основного значения
54
+ const propValue = modelData[propName];
55
+ if (propDef.required) {
56
+ const propType = propDef.type || DataType.ANY;
57
+ if (blankValuesService.isBlankOf(propType, propValue)) {
58
+ throw new InvalidArgumentError(
59
+ 'Property %v of the model %v is required, but %v was given.',
60
+ propName,
61
+ modelName,
62
+ propValue,
63
+ );
64
+ }
65
+ }
66
+ // проверка вложенного объекта
67
+ if (
68
+ propDef.type === DataType.OBJECT &&
69
+ propDef.model &&
70
+ propValue !== null &&
71
+ typeof propValue === 'object' &&
72
+ !Array.isArray(propValue)
73
+ ) {
74
+ this.validate(propDef.model, propValue);
75
+ }
76
+ // проверка массива объектов
77
+ else if (
78
+ propDef.type === DataType.ARRAY &&
79
+ propDef.itemType === DataType.OBJECT &&
80
+ propDef.itemModel &&
81
+ Array.isArray(propValue)
82
+ ) {
83
+ propValue.forEach(itemData => {
84
+ if (
85
+ itemData !== null &&
86
+ typeof itemData === 'object' &&
87
+ !Array.isArray(itemData)
88
+ ) {
89
+ this.validate(propDef.itemModel, itemData);
90
+ }
91
+ });
92
+ }
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,583 @@
1
+ import {expect} from 'chai';
2
+ import {format} from '@e22m4u/js-format';
3
+ import {DatabaseSchema} from '../../../database-schema.js';
4
+ import {RequiredPropertyValidator} from './required-property-validator.js';
5
+ import {DataType} from './data-type.js';
6
+ import {BlankValuesService} from '@e22m4u/js-empty-values';
7
+
8
+ describe('RequiredPropertyValidator', function () {
9
+ describe('validate', function () {
10
+ it('should require the parameter "modelName" to be a non-empty String', function () {
11
+ const dbs = new DatabaseSchema();
12
+ const S = dbs.getService(RequiredPropertyValidator);
13
+ dbs.defineModel({name: 'model'});
14
+ const throwable = v => () => S.validate(v, {});
15
+ const error = s =>
16
+ format(
17
+ 'Parameter "modelName" must be a non-empty String, but %s was given.',
18
+ s,
19
+ );
20
+ expect(throwable('')).to.throw(error('""'));
21
+ expect(throwable(10)).to.throw(error('10'));
22
+ expect(throwable(0)).to.throw(error('0'));
23
+ expect(throwable(true)).to.throw(error('true'));
24
+ expect(throwable(false)).to.throw(error('false'));
25
+ expect(throwable([])).to.throw(error('Array'));
26
+ expect(throwable({})).to.throw(error('Object'));
27
+ expect(throwable(undefined)).to.throw(error('undefined'));
28
+ expect(throwable(null)).to.throw(error('null'));
29
+ throwable('model')();
30
+ });
31
+
32
+ it('should require the parameter "modelData" to be an Object', function () {
33
+ const dbs = new DatabaseSchema();
34
+ const S = dbs.getService(RequiredPropertyValidator);
35
+ dbs.defineModel({name: 'model'});
36
+ const throwable = v => () => S.validate('model', v);
37
+ const error = s =>
38
+ format(
39
+ 'Data of the model "model" should be an Object, but %s was given.',
40
+ s,
41
+ );
42
+ expect(throwable('str')).to.throw(error('"str"'));
43
+ expect(throwable('')).to.throw(error('""'));
44
+ expect(throwable(10)).to.throw(error('10'));
45
+ expect(throwable(0)).to.throw(error('0'));
46
+ expect(throwable(true)).to.throw(error('true'));
47
+ expect(throwable(false)).to.throw(error('false'));
48
+ expect(throwable([])).to.throw(error('Array'));
49
+ expect(throwable(undefined)).to.throw(error('undefined'));
50
+ expect(throwable(null)).to.throw(error('null'));
51
+ throwable({})();
52
+ });
53
+
54
+ it('should require the parameter "isPartial" to be an Object', function () {
55
+ const dbs = new DatabaseSchema();
56
+ const S = dbs.getService(RequiredPropertyValidator);
57
+ dbs.defineModel({name: 'model'});
58
+ const throwable = v => () => S.validate('model', {}, v);
59
+ const error = s =>
60
+ format('Parameter "isPartial" must be a Boolean, but %s was given.', s);
61
+ expect(throwable('str')).to.throw(error('"str"'));
62
+ expect(throwable('')).to.throw(error('""'));
63
+ expect(throwable(10)).to.throw(error('10'));
64
+ expect(throwable(0)).to.throw(error('0'));
65
+ expect(throwable([])).to.throw(error('Array'));
66
+ expect(throwable({})).to.throw(error('Object'));
67
+ expect(throwable(null)).to.throw(error('null'));
68
+ throwable(true)();
69
+ throwable(false)();
70
+ throwable(undefined)();
71
+ });
72
+
73
+ it('should not throw an error if no properties in the model definition', function () {
74
+ const dbs = new DatabaseSchema();
75
+ const S = dbs.getService(RequiredPropertyValidator);
76
+ dbs.defineModel({name: 'model'});
77
+ S.validate('model', {foo: 'bar', baz: undefined});
78
+ });
79
+
80
+ it('should not throw an error if the property definition in short form', function () {
81
+ const dbs = new DatabaseSchema();
82
+ const S = dbs.getService(RequiredPropertyValidator);
83
+ dbs.defineModel({name: 'model', properties: {foo: DataType.STRING}});
84
+ S.validate('model', {foo: 'bar'});
85
+ });
86
+
87
+ it('should not throw an error if a required property is not blank', function () {
88
+ const dbs = new DatabaseSchema();
89
+ const S = dbs.getService(RequiredPropertyValidator);
90
+ const blankValues = S.getService(BlankValuesService);
91
+ blankValues.setBlankValues([undefined]);
92
+ dbs.defineModel({
93
+ name: 'model',
94
+ properties: {
95
+ foo: {
96
+ type: DataType.STRING,
97
+ required: true,
98
+ },
99
+ },
100
+ });
101
+ S.validate('model', {foo: 'bar'});
102
+ });
103
+
104
+ it('should throw an error if a required property is blank', function () {
105
+ const dbs = new DatabaseSchema();
106
+ const S = dbs.getService(RequiredPropertyValidator);
107
+ const blankValues = S.getService(BlankValuesService);
108
+ blankValues.setBlankValues([undefined]);
109
+ dbs.defineModel({
110
+ name: 'model',
111
+ properties: {
112
+ foo: {
113
+ type: DataType.STRING,
114
+ required: true,
115
+ },
116
+ },
117
+ });
118
+ const throwable = () => S.validate('model', {foo: undefined});
119
+ expect(throwable).to.throw(
120
+ 'Property "foo" of the model "model" is required, ' +
121
+ 'but undefined was given.',
122
+ );
123
+ });
124
+
125
+ describe('embedded model', function () {
126
+ it('should not throw an error if no data is provided for an embedded model', function () {
127
+ const dbs = new DatabaseSchema();
128
+ const S = dbs.getService(RequiredPropertyValidator);
129
+ const blankValues = S.getService(BlankValuesService);
130
+ blankValues.setBlankValues([undefined]);
131
+ dbs.defineModel({
132
+ name: 'modelA',
133
+ properties: {
134
+ embedded: {
135
+ type: DataType.OBJECT,
136
+ model: 'modelB',
137
+ },
138
+ },
139
+ });
140
+ dbs.defineModel({
141
+ name: 'modelB',
142
+ properties: {
143
+ foo: {
144
+ type: DataType.STRING,
145
+ },
146
+ },
147
+ });
148
+ S.validate('modelA', {embedded: undefined});
149
+ });
150
+
151
+ it('should throw an error if an embedded model is required but not provided', function () {
152
+ const dbs = new DatabaseSchema();
153
+ const S = dbs.getService(RequiredPropertyValidator);
154
+ const blankValues = S.getService(BlankValuesService);
155
+ blankValues.setBlankValues([undefined]);
156
+ dbs.defineModel({
157
+ name: 'modelA',
158
+ properties: {
159
+ embedded: {
160
+ type: DataType.OBJECT,
161
+ model: 'modelB',
162
+ required: true,
163
+ },
164
+ },
165
+ });
166
+ dbs.defineModel({
167
+ name: 'modelB',
168
+ properties: {
169
+ foo: {
170
+ type: DataType.STRING,
171
+ },
172
+ },
173
+ });
174
+ const throwable = () => S.validate('modelA', {embedded: undefined});
175
+ expect(throwable).to.throw(
176
+ 'Property "embedded" of the model "modelA" is required, ' +
177
+ 'but undefined was given.',
178
+ );
179
+ });
180
+
181
+ it('should allow a model data to have properties without a specified schema', function () {
182
+ const dbs = new DatabaseSchema();
183
+ const S = dbs.getService(RequiredPropertyValidator);
184
+ const blankValues = S.getService(BlankValuesService);
185
+ blankValues.setBlankValues([undefined]);
186
+ dbs.defineModel({
187
+ name: 'modelA',
188
+ properties: {
189
+ embedded: {
190
+ type: DataType.OBJECT,
191
+ model: 'modelB',
192
+ },
193
+ },
194
+ });
195
+ dbs.defineModel({
196
+ name: 'modelB',
197
+ properties: {
198
+ foo: {
199
+ type: DataType.STRING,
200
+ },
201
+ },
202
+ });
203
+ S.validate('modelA', {embedded: {bar: 'baz', qux: undefined}});
204
+ });
205
+
206
+ it('should allow omit a model data when its model has a required property', function () {
207
+ const dbs = new DatabaseSchema();
208
+ const S = dbs.getService(RequiredPropertyValidator);
209
+ const blankValues = S.getService(BlankValuesService);
210
+ blankValues.setBlankValues([undefined]);
211
+ dbs.defineModel({
212
+ name: 'modelA',
213
+ properties: {
214
+ embedded: {
215
+ type: DataType.OBJECT,
216
+ model: 'modelB',
217
+ },
218
+ },
219
+ });
220
+ dbs.defineModel({
221
+ name: 'modelB',
222
+ properties: {
223
+ foo: {
224
+ type: DataType.STRING,
225
+ required: true,
226
+ },
227
+ },
228
+ });
229
+ S.validate('modelA', {embedded: undefined});
230
+ });
231
+
232
+ it('should allow omit an optional property for an embedded model', function () {
233
+ const dbs = new DatabaseSchema();
234
+ const S = dbs.getService(RequiredPropertyValidator);
235
+ const blankValues = S.getService(BlankValuesService);
236
+ blankValues.setBlankValues([undefined]);
237
+ dbs.defineModel({
238
+ name: 'modelA',
239
+ properties: {
240
+ embedded: {
241
+ type: DataType.OBJECT,
242
+ model: 'modelB',
243
+ },
244
+ },
245
+ });
246
+ dbs.defineModel({
247
+ name: 'modelB',
248
+ properties: {
249
+ foo: {
250
+ type: DataType.STRING,
251
+ },
252
+ },
253
+ });
254
+ S.validate('modelA', {embedded: {}});
255
+ });
256
+
257
+ it('should throw an error if a required property is not provided', function () {
258
+ const dbs = new DatabaseSchema();
259
+ const S = dbs.getService(RequiredPropertyValidator);
260
+ const blankValues = S.getService(BlankValuesService);
261
+ blankValues.setBlankValues([undefined]);
262
+ dbs.defineModel({
263
+ name: 'modelA',
264
+ properties: {
265
+ embedded: {
266
+ type: DataType.OBJECT,
267
+ model: 'modelB',
268
+ },
269
+ },
270
+ });
271
+ dbs.defineModel({
272
+ name: 'modelB',
273
+ properties: {
274
+ foo: {
275
+ type: DataType.STRING,
276
+ required: true,
277
+ },
278
+ },
279
+ });
280
+ const throwable = () => S.validate('modelA', {embedded: {}});
281
+ expect(throwable).to.throw(
282
+ 'Property "foo" of the model "modelB" is required, ' +
283
+ 'but undefined was given.',
284
+ );
285
+ });
286
+ });
287
+
288
+ describe('object array', function () {
289
+ it('should allow omit an optional array', function () {
290
+ const dbs = new DatabaseSchema();
291
+ const S = dbs.getService(RequiredPropertyValidator);
292
+ const blankValues = S.getService(BlankValuesService);
293
+ blankValues.setBlankValues([undefined]);
294
+ dbs.defineModel({
295
+ name: 'modelA',
296
+ properties: {
297
+ array: {
298
+ type: DataType.ARRAY,
299
+ itemType: DataType.OBJECT,
300
+ itemModel: 'modelB',
301
+ },
302
+ },
303
+ });
304
+ dbs.defineModel({
305
+ name: 'modelB',
306
+ properties: {
307
+ foo: {
308
+ type: DataType.STRING,
309
+ },
310
+ },
311
+ });
312
+ S.validate('modelA', {});
313
+ });
314
+
315
+ it('should allow a required array to be empty', function () {
316
+ const dbs = new DatabaseSchema();
317
+ const S = dbs.getService(RequiredPropertyValidator);
318
+ const blankValues = S.getService(BlankValuesService);
319
+ blankValues.setBlankValues([undefined]);
320
+ dbs.defineModel({
321
+ name: 'modelA',
322
+ properties: {
323
+ array: {
324
+ type: DataType.ARRAY,
325
+ itemType: DataType.OBJECT,
326
+ itemModel: 'modelB',
327
+ required: true,
328
+ },
329
+ },
330
+ });
331
+ dbs.defineModel({
332
+ name: 'modelB',
333
+ properties: {
334
+ foo: {
335
+ type: DataType.STRING,
336
+ },
337
+ },
338
+ });
339
+ S.validate('modelA', {array: []});
340
+ });
341
+
342
+ it('should allow omit an optional array even if the item model has a required property', function () {
343
+ const dbs = new DatabaseSchema();
344
+ const S = dbs.getService(RequiredPropertyValidator);
345
+ const blankValues = S.getService(BlankValuesService);
346
+ blankValues.setBlankValues([undefined]);
347
+ dbs.defineModel({
348
+ name: 'modelA',
349
+ properties: {
350
+ array: {
351
+ type: DataType.ARRAY,
352
+ itemType: DataType.OBJECT,
353
+ itemModel: 'modelB',
354
+ },
355
+ },
356
+ });
357
+ dbs.defineModel({
358
+ name: 'modelB',
359
+ properties: {
360
+ foo: {
361
+ type: DataType.STRING,
362
+ required: true,
363
+ },
364
+ },
365
+ });
366
+ S.validate('modelA', {});
367
+ });
368
+
369
+ it('should allow an empty array even if the item model has a required property', function () {
370
+ const dbs = new DatabaseSchema();
371
+ const S = dbs.getService(RequiredPropertyValidator);
372
+ const blankValues = S.getService(BlankValuesService);
373
+ blankValues.setBlankValues([undefined]);
374
+ dbs.defineModel({
375
+ name: 'modelA',
376
+ properties: {
377
+ array: {
378
+ type: DataType.ARRAY,
379
+ itemType: DataType.OBJECT,
380
+ itemModel: 'modelB',
381
+ },
382
+ },
383
+ });
384
+ dbs.defineModel({
385
+ name: 'modelB',
386
+ properties: {
387
+ foo: {
388
+ type: DataType.STRING,
389
+ required: true,
390
+ },
391
+ },
392
+ });
393
+ S.validate('modelA', {});
394
+ });
395
+
396
+ it('should throw an error when a required array is not provided', function () {
397
+ const dbs = new DatabaseSchema();
398
+ const S = dbs.getService(RequiredPropertyValidator);
399
+ const blankValues = S.getService(BlankValuesService);
400
+ blankValues.setBlankValues([undefined]);
401
+ dbs.defineModel({
402
+ name: 'modelA',
403
+ properties: {
404
+ array: {
405
+ type: DataType.ARRAY,
406
+ itemType: DataType.OBJECT,
407
+ itemModel: 'modelB',
408
+ required: true,
409
+ },
410
+ },
411
+ });
412
+ dbs.defineModel({
413
+ name: 'modelB',
414
+ properties: {
415
+ foo: {
416
+ type: DataType.STRING,
417
+ },
418
+ },
419
+ });
420
+ const throwable = () => S.validate('modelA', {});
421
+ expect(throwable).to.throw(
422
+ 'Property "array" of the model "modelA" is required, ' +
423
+ 'but undefined was given.',
424
+ );
425
+ });
426
+
427
+ it('should allow omit an optional property of the item model', function () {
428
+ const dbs = new DatabaseSchema();
429
+ const S = dbs.getService(RequiredPropertyValidator);
430
+ const blankValues = S.getService(BlankValuesService);
431
+ blankValues.setBlankValues([undefined]);
432
+ dbs.defineModel({
433
+ name: 'modelA',
434
+ properties: {
435
+ array: {
436
+ type: DataType.ARRAY,
437
+ itemType: DataType.OBJECT,
438
+ itemModel: 'modelB',
439
+ required: true,
440
+ },
441
+ },
442
+ });
443
+ dbs.defineModel({
444
+ name: 'modelB',
445
+ properties: {
446
+ foo: {
447
+ type: DataType.STRING,
448
+ },
449
+ },
450
+ });
451
+ S.validate('modelA', {array: [{}]});
452
+ });
453
+
454
+ it('should allow an item date to have properties without a specified schema', function () {
455
+ const dbs = new DatabaseSchema();
456
+ const S = dbs.getService(RequiredPropertyValidator);
457
+ const blankValues = S.getService(BlankValuesService);
458
+ blankValues.setBlankValues([undefined]);
459
+ dbs.defineModel({
460
+ name: 'modelA',
461
+ properties: {
462
+ array: {
463
+ type: DataType.ARRAY,
464
+ itemType: DataType.OBJECT,
465
+ itemModel: 'modelB',
466
+ required: true,
467
+ },
468
+ },
469
+ });
470
+ dbs.defineModel({
471
+ name: 'modelB',
472
+ properties: {
473
+ foo: {
474
+ type: DataType.STRING,
475
+ },
476
+ },
477
+ });
478
+ S.validate('modelA', {array: [{bar: 'baz', qux: undefined}]});
479
+ });
480
+ });
481
+
482
+ describe('isPartial', function () {
483
+ it('should throw an error if a required property is blank', function () {
484
+ const dbs = new DatabaseSchema();
485
+ const S = dbs.getService(RequiredPropertyValidator);
486
+ const blankValues = S.getService(BlankValuesService);
487
+ blankValues.setBlankValues([undefined]);
488
+ dbs.defineModel({
489
+ name: 'model',
490
+ properties: {
491
+ foo: {
492
+ type: DataType.STRING,
493
+ required: true,
494
+ },
495
+ },
496
+ });
497
+ const throwable = () => S.validate('model', {foo: undefined}, true);
498
+ expect(throwable).to.throw(
499
+ 'Property "foo" of the model "model" is required, ' +
500
+ 'but undefined was given.',
501
+ );
502
+ });
503
+
504
+ it('should not validate a required but not provided properties', function () {
505
+ const dbs = new DatabaseSchema();
506
+ const S = dbs.getService(RequiredPropertyValidator);
507
+ const blankValues = S.getService(BlankValuesService);
508
+ blankValues.setBlankValues([undefined]);
509
+ dbs.defineModel({
510
+ name: 'model',
511
+ properties: {
512
+ foo: {
513
+ type: DataType.STRING,
514
+ required: true,
515
+ },
516
+ },
517
+ });
518
+ S.validate('model', {}, true);
519
+ });
520
+
521
+ it('should validate not provided properties of an embedded model', function () {
522
+ const dbs = new DatabaseSchema();
523
+ const S = dbs.getService(RequiredPropertyValidator);
524
+ const blankValues = S.getService(BlankValuesService);
525
+ blankValues.setBlankValues([undefined]);
526
+ dbs.defineModel({
527
+ name: 'modelA',
528
+ properties: {
529
+ embedded: {
530
+ type: DataType.OBJECT,
531
+ model: 'modelB',
532
+ },
533
+ },
534
+ });
535
+ dbs.defineModel({
536
+ name: 'modelB',
537
+ properties: {
538
+ foo: {
539
+ type: DataType.STRING,
540
+ required: true,
541
+ },
542
+ },
543
+ });
544
+ const throwable = () => S.validate('modelA', {embedded: {}}, true);
545
+ expect(throwable).to.throw(
546
+ 'Property "foo" of the model "modelB" is required, ' +
547
+ 'but undefined was given.',
548
+ );
549
+ });
550
+
551
+ it('should validate not provided properties of an item model', function () {
552
+ const dbs = new DatabaseSchema();
553
+ const S = dbs.getService(RequiredPropertyValidator);
554
+ const blankValues = S.getService(BlankValuesService);
555
+ blankValues.setBlankValues([undefined]);
556
+ dbs.defineModel({
557
+ name: 'modelA',
558
+ properties: {
559
+ array: {
560
+ type: DataType.ARRAY,
561
+ itemType: DataType.OBJECT,
562
+ itemModel: 'modelB',
563
+ },
564
+ },
565
+ });
566
+ dbs.defineModel({
567
+ name: 'modelB',
568
+ properties: {
569
+ foo: {
570
+ type: DataType.STRING,
571
+ required: true,
572
+ },
573
+ },
574
+ });
575
+ const throwable = () => S.validate('modelA', {array: [{}]}, true);
576
+ expect(throwable).to.throw(
577
+ 'Property "foo" of the model "modelB" is required, ' +
578
+ 'but undefined was given.',
579
+ );
580
+ });
581
+ });
582
+ });
583
+ });