@izara_project/izara-core-library-lock-update 1.0.1 → 1.0.2

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,607 @@
1
+ /*
2
+ Copyright (C) 2021 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ import errorHandlingLib from '@izara_project/izara-core-library-error-handling';
21
+ import lockUpdateConstants from '../constants/lockUpdateConstants.js';
22
+
23
+ function _getIdentifierByType(objectSchema, identifierType) {
24
+ if (!objectSchema || !Array.isArray(objectSchema.identifiers)) {
25
+ return null;
26
+ }
27
+
28
+ for (const identifier of objectSchema.identifiers) {
29
+ if (identifier && identifier.type === identifierType) {
30
+ return identifier;
31
+ }
32
+ }
33
+
34
+ return null;
35
+ }
36
+
37
+ function _getIdentifierFieldName(identifier) {
38
+ if (!identifier) {
39
+ return null;
40
+ }
41
+
42
+ if (identifier.name) {
43
+ return identifier.name;
44
+ }
45
+
46
+ if (identifier.fieldName) {
47
+ return identifier.fieldName;
48
+ }
49
+
50
+ if (Array.isArray(identifier.fieldNames) && identifier.fieldNames.length > 0) {
51
+ return identifier.fieldNames.join('_');
52
+ }
53
+
54
+ return null;
55
+ }
56
+
57
+ function _validateRequiredFields(_izContext, objectSchema, requiredFieldNames, validationErrors) {
58
+ if (!objectSchema) {
59
+ return;
60
+ }
61
+
62
+ if (!objectSchema.fieldNames) {
63
+ objectSchema.fieldNames = {};
64
+ }
65
+
66
+ for (const fieldName of requiredFieldNames) {
67
+ if (!Object.prototype.hasOwnProperty.call(objectSchema.fieldNames, fieldName)) {
68
+ errorHandlingLib.addThrowLibErrors(
69
+ _izContext,
70
+ `Missing required field '${fieldName}' in schema '${objectSchema.objectType}'`,
71
+ validationErrors
72
+ );
73
+ }
74
+ }
75
+ }
76
+
77
+ function _validateStorageResourceTags(_izContext, objectSchema, fieldName, validationErrors) {
78
+ if (!objectSchema || !objectSchema.fieldNames) {
79
+ return;
80
+ }
81
+
82
+ if (!Object.prototype.hasOwnProperty.call(objectSchema.fieldNames, fieldName)) {
83
+ return;
84
+ }
85
+
86
+ const fieldConfig = objectSchema.fieldNames[fieldName];
87
+
88
+ if (!fieldConfig) {
89
+ errorHandlingLib.addThrowLibErrors(
90
+ _izContext,
91
+ `Field config is missing for field '${fieldName}' in schema '${objectSchema.objectType}'`,
92
+ validationErrors
93
+ );
94
+
95
+ return;
96
+ }
97
+
98
+ if (!Array.isArray(fieldConfig.storageResourceTags)) {
99
+ errorHandlingLib.addThrowLibErrors(
100
+ _izContext,
101
+ `storageResourceTags missing on field '${fieldName}' in schema '${objectSchema.objectType}'`,
102
+ validationErrors
103
+ );
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Validates the main object's lock fields and storage resource based on addOnDataStructure
109
+ *
110
+ * @param {Object} _izContext - Execution context.
111
+ * @param {Object} mainObjectSchema - Main object schema.
112
+ * @returns {Array} Array of validation errors.
113
+ */
114
+ function validateMainObjectSchema(_izContext, mainObjectSchema) {
115
+ const validationErrors = [];
116
+
117
+ if (!mainObjectSchema) {
118
+ errorHandlingLib.addThrowLibErrors(
119
+ _izContext,
120
+ 'Main object schema is required',
121
+ validationErrors
122
+ );
123
+
124
+ return validationErrors;
125
+ }
126
+
127
+ if (!Array.isArray(mainObjectSchema.addOnDataStructure)) {
128
+ return validationErrors;
129
+ }
130
+
131
+ if (!mainObjectSchema.fieldNames) {
132
+ mainObjectSchema.fieldNames = {};
133
+ }
134
+
135
+ if (!mainObjectSchema.storageResources) {
136
+ mainObjectSchema.storageResources = {};
137
+ }
138
+
139
+ for (const addOn of mainObjectSchema.addOnDataStructure) {
140
+ if (!addOn) {
141
+ continue;
142
+ }
143
+
144
+ const addOnType = addOn.type;
145
+
146
+ let lockTag = null;
147
+ let storageResourceTag = null;
148
+
149
+ if (addOnType === 'reserveQuantity') {
150
+ lockTag = addOn.reserveQuantityTag;
151
+ storageResourceTag = addOn.storageResourceTag;
152
+ } else if (addOnType === 'lockUpdate') {
153
+ lockTag = addOn.lockUpdateTag;
154
+ storageResourceTag = addOn.storageResourceTag;
155
+ } else {
156
+ continue;
157
+ }
158
+
159
+ if (!lockTag) {
160
+ errorHandlingLib.addThrowLibErrors(
161
+ _izContext,
162
+ `Missing lock tag for addOn type '${addOnType}' in schema '${mainObjectSchema.objectType}'`,
163
+ validationErrors
164
+ );
165
+
166
+ continue;
167
+ }
168
+
169
+ if (!storageResourceTag) {
170
+ errorHandlingLib.addThrowLibErrors(
171
+ _izContext,
172
+ `Missing storageResourceTag for addOn type '${addOnType}' in schema '${mainObjectSchema.objectType}'`,
173
+ validationErrors
174
+ );
175
+
176
+ continue;
177
+ }
178
+
179
+ const fieldNameLockStatus = lockUpdateConstants.createLockStatusFieldName(lockTag);
180
+ const fieldNameUniqueRequestId = lockUpdateConstants.createUniqueRequestIdFieldName(lockTag);
181
+
182
+ const requiredFieldNames = [
183
+ fieldNameLockStatus,
184
+ fieldNameUniqueRequestId
185
+ ];
186
+
187
+ _validateRequiredFields(
188
+ _izContext,
189
+ mainObjectSchema,
190
+ requiredFieldNames,
191
+ validationErrors
192
+ );
193
+
194
+ for (const fieldName of requiredFieldNames) {
195
+ _validateStorageResourceTags(
196
+ _izContext,
197
+ mainObjectSchema,
198
+ fieldName,
199
+ validationErrors
200
+ );
201
+
202
+ if (Object.prototype.hasOwnProperty.call(mainObjectSchema.fieldNames, fieldName)) {
203
+ const fieldConfig = mainObjectSchema.fieldNames[fieldName];
204
+
205
+ if (Array.isArray(fieldConfig.storageResourceTags) && !fieldConfig.storageResourceTags.includes(storageResourceTag)) {
206
+ errorHandlingLib.addThrowLibErrors(
207
+ _izContext,
208
+ `Field '${fieldName}' must include storageResourceTag '${storageResourceTag}'`,
209
+ validationErrors
210
+ );
211
+ }
212
+ }
213
+ }
214
+
215
+ if (!Object.prototype.hasOwnProperty.call(mainObjectSchema.storageResources, storageResourceTag)) {
216
+ errorHandlingLib.addThrowLibErrors(
217
+ _izContext,
218
+ `Storage resource '${storageResourceTag}' not found in schema '${mainObjectSchema.objectType}'`,
219
+ validationErrors
220
+ );
221
+ } else {
222
+ const storageResource = mainObjectSchema.storageResources[storageResourceTag];
223
+
224
+ if (!storageResource) {
225
+ errorHandlingLib.addThrowLibErrors(
226
+ _izContext,
227
+ `Storage resource config is missing for '${storageResourceTag}' in schema '${mainObjectSchema.objectType}'`,
228
+ validationErrors
229
+ );
230
+ } else if (!storageResource.tableName) {
231
+ errorHandlingLib.addThrowLibErrors(
232
+ _izContext,
233
+ `Missing tableName for storage resource '${storageResourceTag}' in schema '${mainObjectSchema.objectType}'`,
234
+ validationErrors
235
+ );
236
+ }
237
+ }
238
+ }
239
+
240
+ return validationErrors;
241
+ }
242
+
243
+ /**
244
+ * Validates the lock history schema
245
+ *
246
+ * @param {Object} _izContext - Execution context.
247
+ * @param {Object} historyObjectSchema - Lock history schema.
248
+ * @returns {Array} Array of validation errors.
249
+ */
250
+ function validateHistorySchema(_izContext, historyObjectSchema) {
251
+ const validationErrors = [];
252
+
253
+ if (!historyObjectSchema) {
254
+ errorHandlingLib.addThrowLibErrors(
255
+ _izContext,
256
+ 'History object schema is required',
257
+ validationErrors
258
+ );
259
+
260
+ return validationErrors;
261
+ }
262
+
263
+ if (!historyObjectSchema.fieldNames) {
264
+ historyObjectSchema.fieldNames = {};
265
+ }
266
+
267
+ if (!historyObjectSchema.objectType || typeof historyObjectSchema.objectType !== 'string') {
268
+ errorHandlingLib.addThrowLibErrors(
269
+ _izContext,
270
+ 'History object schema must have objectType',
271
+ validationErrors
272
+ );
273
+
274
+ return validationErrors;
275
+ }
276
+
277
+ const historyObjectType = historyObjectSchema.objectType;
278
+
279
+ if (historyObjectType.indexOf('History') === -1) {
280
+ errorHandlingLib.addThrowLibErrors(
281
+ _izContext,
282
+ `History object schema must end with 'History', found '${historyObjectType}'`,
283
+ validationErrors
284
+ );
285
+
286
+ return validationErrors;
287
+ }
288
+
289
+ const lockTag = historyObjectType.replace('History', '');
290
+
291
+ if (!lockTag) {
292
+ errorHandlingLib.addThrowLibErrors(
293
+ _izContext,
294
+ `Invalid lock tag derived from history objectType '${historyObjectType}'`,
295
+ validationErrors
296
+ );
297
+
298
+ return validationErrors;
299
+ }
300
+
301
+ const fieldNameStatus = lockUpdateConstants.createStatusFieldName(lockTag);
302
+
303
+ const requiredFieldNames = [
304
+ 'usedTimestamp',
305
+ 'uniqueRequestId',
306
+ fieldNameStatus
307
+ ];
308
+
309
+ _validateRequiredFields(
310
+ _izContext,
311
+ historyObjectSchema,
312
+ requiredFieldNames,
313
+ validationErrors
314
+ );
315
+
316
+ for (const fieldName of requiredFieldNames) {
317
+ _validateStorageResourceTags(
318
+ _izContext,
319
+ historyObjectSchema,
320
+ fieldName,
321
+ validationErrors
322
+ );
323
+ }
324
+
325
+ const historyPartitionKey = _getIdentifierByType(historyObjectSchema, 'partitionKey');
326
+ const historySortKey = _getIdentifierByType(historyObjectSchema, 'sortKey');
327
+
328
+ if (!historyPartitionKey) {
329
+ errorHandlingLib.addThrowLibErrors(
330
+ _izContext,
331
+ `History schema '${historyObjectSchema.objectType}' is missing partitionKey`,
332
+ validationErrors
333
+ );
334
+ }
335
+
336
+ if (!historySortKey) {
337
+ errorHandlingLib.addThrowLibErrors(
338
+ _izContext,
339
+ `History schema '${historyObjectSchema.objectType}' is missing sortKey`,
340
+ validationErrors
341
+ );
342
+ } else {
343
+ const historySortKeyName = _getIdentifierFieldName(historySortKey);
344
+
345
+ if (historySortKeyName !== 'timestampUniqueId') {
346
+ errorHandlingLib.addThrowLibErrors(
347
+ _izContext,
348
+ `History sortKey must be 'timestampUniqueId', found '${historySortKeyName}'`,
349
+ validationErrors
350
+ );
351
+ }
352
+ }
353
+
354
+ return validationErrors;
355
+ }
356
+
357
+ /**
358
+ * Validates the lock queue schema
359
+ *
360
+ * @param {Object} _izContext - Execution context.
361
+ * @param {Object} queueObjectSchema - Lock queue schema.
362
+ * @returns {Array} Array of validation errors.
363
+ */
364
+ function validateQueueSchema(_izContext, queueObjectSchema) {
365
+ const validationErrors = [];
366
+
367
+ if (!queueObjectSchema) {
368
+ errorHandlingLib.addThrowLibErrors(
369
+ _izContext,
370
+ 'Queue object schema is required',
371
+ validationErrors
372
+ );
373
+
374
+ return validationErrors;
375
+ }
376
+
377
+ if (!queueObjectSchema.fieldNames) {
378
+ queueObjectSchema.fieldNames = {};
379
+ }
380
+
381
+ if (!queueObjectSchema.objectType || typeof queueObjectSchema.objectType !== 'string') {
382
+ errorHandlingLib.addThrowLibErrors(
383
+ _izContext,
384
+ 'Queue object schema must have objectType',
385
+ validationErrors
386
+ );
387
+
388
+ return validationErrors;
389
+ }
390
+
391
+ const queueObjectType = queueObjectSchema.objectType;
392
+
393
+ if (queueObjectType.indexOf('Queue') === -1) {
394
+ errorHandlingLib.addThrowLibErrors(
395
+ _izContext,
396
+ `Queue object schema must end with 'Queue', found '${queueObjectType}'`,
397
+ validationErrors
398
+ );
399
+
400
+ return validationErrors;
401
+ }
402
+
403
+ const requiredFieldNames = [
404
+ 'usedTimestamp',
405
+ 'uniqueRequestId',
406
+ 'flowType',
407
+ 'messageBody'
408
+ ];
409
+
410
+ _validateRequiredFields(
411
+ _izContext,
412
+ queueObjectSchema,
413
+ requiredFieldNames,
414
+ validationErrors
415
+ );
416
+
417
+ for (const fieldName of requiredFieldNames) {
418
+ _validateStorageResourceTags(
419
+ _izContext,
420
+ queueObjectSchema,
421
+ fieldName,
422
+ validationErrors
423
+ );
424
+ }
425
+
426
+ const queuePartitionKey = _getIdentifierByType(queueObjectSchema, 'partitionKey');
427
+ const queueSortKey = _getIdentifierByType(queueObjectSchema, 'sortKey');
428
+
429
+ if (!queuePartitionKey) {
430
+ errorHandlingLib.addThrowLibErrors(
431
+ _izContext,
432
+ `Queue schema '${queueObjectSchema.objectType}' is missing partitionKey`,
433
+ validationErrors
434
+ );
435
+ }
436
+
437
+ if (!queueSortKey) {
438
+ errorHandlingLib.addThrowLibErrors(
439
+ _izContext,
440
+ `Queue schema '${queueObjectSchema.objectType}' is missing sortKey`,
441
+ validationErrors
442
+ );
443
+ } else {
444
+ const queueSortKeyName = _getIdentifierFieldName(queueSortKey);
445
+
446
+ if (queueSortKeyName !== 'timestampUniqueId') {
447
+ errorHandlingLib.addThrowLibErrors(
448
+ _izContext,
449
+ `Queue sortKey must be 'timestampUniqueId', found '${queueSortKeyName}'`,
450
+ validationErrors
451
+ );
452
+ }
453
+ }
454
+
455
+ return validationErrors;
456
+ }
457
+
458
+
459
+ /**
460
+ * Validates the full lock schemas for main object schema
461
+ * - validates main object schema via validateMainObjectSchema
462
+ * - finds <tag>History and <tag>Queue from schemasMap
463
+ * - validates history and queue via validateHistorySchema/validateQueueSchema
464
+ * - validates cross-schema rules (partitionKey must match, sortKey consistency)
465
+ *
466
+ * @param {Object} _izContext - Execution context.
467
+ * @param {Object} mainObjectSchema - Main object schema.
468
+ * @param {Object} schemasMap - Map of objectType -> objectSchema
469
+ * @returns {Array} Array of validation errors.
470
+ */
471
+ function validateLockSchemas(_izContext, mainObjectSchema, schemasMap) {
472
+ const validationErrors = [];
473
+
474
+ if (!mainObjectSchema) {
475
+ errorHandlingLib.addThrowLibErrors(
476
+ _izContext,
477
+ 'Main object schema is required',
478
+ validationErrors
479
+ );
480
+
481
+ return validationErrors;
482
+ }
483
+
484
+ if (!schemasMap || typeof schemasMap !== 'object') {
485
+ errorHandlingLib.addThrowLibErrors(
486
+ _izContext,
487
+ `schemasMap is required for full lock validation of schema '${mainObjectSchema.objectType}'`,
488
+ validationErrors
489
+ );
490
+
491
+ return validationErrors;
492
+ }
493
+
494
+ const mainErrors = validateMainObjectSchema(_izContext, mainObjectSchema);
495
+ for (let index = 0; index < mainErrors.length; index++) {
496
+ validationErrors.push(mainErrors[index]);
497
+ }
498
+
499
+ if (!Array.isArray(mainObjectSchema.addOnDataStructure)) {
500
+ return validationErrors;
501
+ }
502
+
503
+ for (const addOn of mainObjectSchema.addOnDataStructure) {
504
+ if (!addOn) {
505
+ continue;
506
+ }
507
+
508
+ const addOnType = addOn.type;
509
+
510
+ let lockTag = null;
511
+
512
+ if (addOnType === 'reserveQuantity') {
513
+ lockTag = addOn.reserveQuantityTag;
514
+ } else if (addOnType === 'lockUpdate') {
515
+ lockTag = addOn.lockUpdateTag;
516
+ } else {
517
+ continue;
518
+ }
519
+
520
+ if (!lockTag) {
521
+ continue;
522
+ }
523
+
524
+
525
+ const historySchemaKey = lockUpdateConstants.createHistoryObjectName(lockTag);
526
+ const queueSchemaKey = lockUpdateConstants.createQueueObjectName(lockTag);
527
+
528
+ const historyObjectSchema = schemasMap[historySchemaKey];
529
+ const queueObjectSchema = schemasMap[queueSchemaKey];
530
+
531
+ if (historyObjectSchema) {
532
+ const historyErrors = validateHistorySchema(_izContext, historyObjectSchema);
533
+ for (let index = 0; index < historyErrors.length; index++) {
534
+ validationErrors.push(historyErrors[index]);
535
+ }
536
+
537
+ const mainPartitionKey = _getIdentifierByType(mainObjectSchema, 'partitionKey');
538
+ const historyPartitionKey = _getIdentifierByType(historyObjectSchema, 'partitionKey');
539
+
540
+ if (mainPartitionKey && historyPartitionKey) {
541
+ const mainPartitionKeyName = _getIdentifierFieldName(mainPartitionKey);
542
+ const historyPartitionKeyName = _getIdentifierFieldName(historyPartitionKey);
543
+
544
+ if (mainPartitionKeyName && historyPartitionKeyName && mainPartitionKeyName !== historyPartitionKeyName) {
545
+ errorHandlingLib.addThrowLibErrors(
546
+ _izContext,
547
+ `History partitionKey '${historyPartitionKeyName}' must match Main Object partitionKey '${mainPartitionKeyName}' in schema '${mainObjectSchema.objectType}'`,
548
+ validationErrors
549
+ );
550
+ }
551
+ }
552
+ }
553
+
554
+ if (queueObjectSchema) {
555
+ const queueErrors = validateQueueSchema(_izContext, queueObjectSchema);
556
+ for (let index = 0; index < queueErrors.length; index++) {
557
+ validationErrors.push(queueErrors[index]);
558
+ }
559
+
560
+ if (historyObjectSchema) {
561
+ const historyPartitionKey = _getIdentifierByType(historyObjectSchema, 'partitionKey');
562
+ const queuePartitionKey = _getIdentifierByType(queueObjectSchema, 'partitionKey');
563
+
564
+ if (historyPartitionKey && queuePartitionKey) {
565
+ const historyPartitionKeyName = _getIdentifierFieldName(historyPartitionKey);
566
+ const queuePartitionKeyName = _getIdentifierFieldName(queuePartitionKey);
567
+
568
+ if (historyPartitionKeyName && queuePartitionKeyName && historyPartitionKeyName !== queuePartitionKeyName) {
569
+ errorHandlingLib.addThrowLibErrors(
570
+ _izContext,
571
+ `Queue partitionKey '${queuePartitionKeyName}' must match History partitionKey '${historyPartitionKeyName}' in schema '${mainObjectSchema.objectType}'`,
572
+ validationErrors
573
+ );
574
+ }
575
+ }
576
+
577
+ const historySortKey = _getIdentifierByType(historyObjectSchema, 'sortKey');
578
+ const queueSortKey = _getIdentifierByType(queueObjectSchema, 'sortKey');
579
+
580
+ if (historySortKey && queueSortKey) {
581
+ const historySortKeyName = _getIdentifierFieldName(historySortKey);
582
+ const queueSortKeyName = _getIdentifierFieldName(queueSortKey);
583
+
584
+ if (historySortKeyName && queueSortKeyName && historySortKeyName !== historySortKeyName) {
585
+ errorHandlingLib.addThrowLibErrors(
586
+ _izContext,
587
+ `Queue sortKey '${queueSortKeyName}' must match History sortKey '${historySortKeyName}' in schema '${mainObjectSchema.objectType}'`,
588
+ validationErrors
589
+ );
590
+ }
591
+ }
592
+ }
593
+ }
594
+ }
595
+
596
+ return validationErrors;
597
+ }
598
+
599
+
600
+
601
+
602
+ export default {
603
+ validateMainObjectSchema,
604
+ validateHistorySchema,
605
+ validateQueueSchema,
606
+ validateLockSchemas
607
+ };