@tremho/mist-lift 2.2.9 → 2.4.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/README.md +9 -1
- package/build/commands/actions/updateDeployedPermissions.js +109 -0
- package/build/commands/actions/updateDeployedPermissions.js.map +1 -0
- package/build/commands/builtin/ApiDocMaker.js +18 -10
- package/build/commands/builtin/ApiDocMaker.js.map +1 -1
- package/build/commands/builtin/BuiltInHandler.js +6 -3
- package/build/commands/builtin/BuiltInHandler.js.map +1 -1
- package/build/commands/builtin/ExportWebroot.js +242 -0
- package/build/commands/builtin/ExportWebroot.js.map +1 -0
- package/build/commands/builtin/StageWebrootZip.js +10 -6
- package/build/commands/builtin/StageWebrootZip.js.map +1 -1
- package/build/commands/builtin/prebuilt-zips/API.zip +0 -0
- package/build/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
- package/build/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
- package/build/commands/builtin/webroot-export/s3webroot.js +117 -0
- package/build/commands/builtin/webroot-export/s3webroot.js.map +1 -0
- package/build/commands/deploy.js +6 -4
- package/build/commands/deploy.js.map +1 -1
- package/build/commands/package.js +31 -1
- package/build/commands/package.js.map +1 -1
- package/build/commands/publish.js +40 -13
- package/build/commands/publish.js.map +1 -1
- package/build/commands/start.js +2 -1
- package/build/commands/start.js.map +1 -1
- package/build/commands/update.js +1 -0
- package/build/commands/update.js.map +1 -1
- package/build/expressRoutes/all.js +1 -0
- package/build/expressRoutes/all.js.map +1 -1
- package/build/expressRoutes/functionBinder.js +159 -17
- package/build/expressRoutes/functionBinder.js.map +1 -1
- package/build/lib/DirectoryUtils.js +2 -1
- package/build/lib/DirectoryUtils.js.map +1 -1
- package/build/lib/IdSrc.js +29 -5
- package/build/lib/IdSrc.js.map +1 -1
- package/build/lib/TypeCheck.js +1204 -0
- package/build/lib/TypeCheck.js.map +1 -0
- package/build/lib/executeCommand.js +1 -1
- package/build/lib/executeCommand.js.map +1 -1
- package/build/lib/openAPI/openApiConstruction.js +238 -54
- package/build/lib/openAPI/openApiConstruction.js.map +1 -1
- package/build/lift.js +1 -1
- package/build/lift.js.map +1 -1
- package/package.json +5 -2
- package/src/commands/actions/updateDeployedPermissions.ts +80 -0
- package/src/commands/builtin/ApiDocMaker.ts +17 -10
- package/src/commands/builtin/BuiltInHandler.ts +7 -2
- package/src/commands/builtin/ExportWebroot.ts +195 -0
- package/src/commands/builtin/StageWebrootZip.ts +13 -5
- package/src/commands/builtin/prebuilt-zips/API.zip +0 -0
- package/src/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
- package/src/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
- package/src/commands/builtin/webroot-export/s3webroot.ts +78 -0
- package/src/commands/deploy.ts +6 -4
- package/src/commands/package.ts +33 -2
- package/src/commands/publish.ts +37 -12
- package/src/commands/start.ts +2 -1
- package/src/commands/update.ts +1 -0
- package/src/expressRoutes/all.ts +1 -0
- package/src/expressRoutes/functionBinder.ts +152 -16
- package/src/lib/DirectoryUtils.ts +2 -1
- package/src/lib/IdSrc.ts +17 -4
- package/src/lib/TypeCheck.ts +1168 -0
- package/src/lib/executeCommand.ts +1 -1
- package/src/lib/openAPI/openApiConstruction.ts +225 -41
- package/src/lift.ts +1 -1
- package/templateData/function-main-ts +8 -1
|
@@ -0,0 +1,1204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Module for Constraint definitions and TypeCheck support
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validate = exports.parseConstraints = exports.parseConstraintsToMap = exports.stringFromValueType = exports.valueTypeFromString = exports.ElementCheckType = exports.TypeConstraint = exports.ValueType = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Enumeration of basic types
|
|
9
|
+
*
|
|
10
|
+
* - see [stringFromValueType](#module_TypeCheck..stringFromValueType)
|
|
11
|
+
* - see [valueTypeFromString](#module_TypeCheck..valueTypeFromString)
|
|
12
|
+
*/
|
|
13
|
+
var ValueType;
|
|
14
|
+
(function (ValueType) {
|
|
15
|
+
ValueType[ValueType["none"] = 0] = "none";
|
|
16
|
+
ValueType[ValueType["number"] = 1] = "number";
|
|
17
|
+
ValueType[ValueType["string"] = 2] = "string";
|
|
18
|
+
ValueType[ValueType["boolean"] = 3] = "boolean";
|
|
19
|
+
ValueType[ValueType["object"] = 4] = "object";
|
|
20
|
+
ValueType[ValueType["array"] = 5] = "array";
|
|
21
|
+
ValueType[ValueType["regex"] = 6] = "regex";
|
|
22
|
+
})(ValueType || (exports.ValueType = ValueType = {}));
|
|
23
|
+
/**
|
|
24
|
+
* Base for all Constraint errors.
|
|
25
|
+
* Defines the identifying class archetype and consistent error message prefix
|
|
26
|
+
*/
|
|
27
|
+
class ConstraintError extends Error {
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this.message = 'Constraint Error: ';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* An error message for when a value fails validation.
|
|
35
|
+
*/
|
|
36
|
+
class ConstraintFail extends ConstraintError {
|
|
37
|
+
constructor(failType, value) {
|
|
38
|
+
super();
|
|
39
|
+
this.message += `Failed ${failType}: ${value}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* An error for when the basic type is wrong
|
|
44
|
+
*/
|
|
45
|
+
class ConstraintBasicTypeError extends ConstraintError {
|
|
46
|
+
constructor(value, expType) {
|
|
47
|
+
super();
|
|
48
|
+
this.message += `Incorrect type ${typeof value}, (${expType} expected) ${value}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* An error for when we expected null or undefined
|
|
53
|
+
*/
|
|
54
|
+
// class NullConstraintError extends ConstraintError {
|
|
55
|
+
// constructor() {
|
|
56
|
+
// super();
|
|
57
|
+
// this.message += 'Expected NULL or undefined'
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
/**
|
|
61
|
+
* An error for when a min/max range has been violated, including what type of range.
|
|
62
|
+
*/
|
|
63
|
+
class RangeConstraintError extends ConstraintError {
|
|
64
|
+
constructor(value, comp, rangeType = 'Number') {
|
|
65
|
+
super();
|
|
66
|
+
// we don't need to test both range ends, because we know we are here because of an error one way
|
|
67
|
+
// or the other.
|
|
68
|
+
if (value < comp) {
|
|
69
|
+
this.message += `${rangeType} ${value} is less than range minimum of ${comp}`;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.message += `${rangeType} ${value} exceeds range maximum of ${comp}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* An error for when an integer was expected
|
|
78
|
+
*/
|
|
79
|
+
class IntegerConstraintError extends ConstraintError {
|
|
80
|
+
constructor(value) {
|
|
81
|
+
super();
|
|
82
|
+
if (value === undefined) {
|
|
83
|
+
this.message += 'Integer expected';
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.message += `Value ${value} is not an integer`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* An error for when a positive value was expected
|
|
92
|
+
*/
|
|
93
|
+
class PositiveConstraintError extends ConstraintError {
|
|
94
|
+
constructor(value) {
|
|
95
|
+
super();
|
|
96
|
+
if (value === undefined) {
|
|
97
|
+
this.message += 'Positive value expected';
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.message += `Value ${value} is not positive`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* An error for when a negative value was expected
|
|
106
|
+
*/
|
|
107
|
+
class NegativeConstraintError extends ConstraintError {
|
|
108
|
+
constructor(value) {
|
|
109
|
+
super();
|
|
110
|
+
if (value === undefined) {
|
|
111
|
+
this.message += 'Positive value expected';
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.message += `Value ${value} is not negative`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* An error for when zero was not expected.
|
|
120
|
+
*/
|
|
121
|
+
class ZeroValueConstraintError extends ConstraintError {
|
|
122
|
+
constructor() {
|
|
123
|
+
super();
|
|
124
|
+
this.message += 'Zero is not an allowable value';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* An error for declaring both ! and not ! variants of the same expression
|
|
129
|
+
*/
|
|
130
|
+
class ConstraintConflictError extends ConstraintError {
|
|
131
|
+
constructor(conflictType) {
|
|
132
|
+
super();
|
|
133
|
+
this.message += `Both ${conflictType} and !${conflictType} declared`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Base form of TypeConstraint.
|
|
138
|
+
* Defines the base type and the test method.
|
|
139
|
+
*/
|
|
140
|
+
class TypeConstraint {
|
|
141
|
+
constructor(typeString = '') {
|
|
142
|
+
this.type = typeString.trim().toLowerCase();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Perform a runtime test of the value
|
|
146
|
+
|
|
147
|
+
* returns without throw if test was okay, otherwise throws a ConstraintError explaining the violation.
|
|
148
|
+
*
|
|
149
|
+
* @param value - value to test against this constraint
|
|
150
|
+
*
|
|
151
|
+
* @throws {ConstraintError} Error is thrown if test fails its constraints
|
|
152
|
+
*/
|
|
153
|
+
test(value) {
|
|
154
|
+
if (typeof value !== this.type) {
|
|
155
|
+
throw new ConstraintBasicTypeError(value, this.type);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Describes the constraint in printable terms (not really used, a bit redundant to describe)
|
|
159
|
+
toString() {
|
|
160
|
+
if (this.badName)
|
|
161
|
+
return `"${this.badName}" is not a recognized constraint for ${this.type}`;
|
|
162
|
+
if (this.note)
|
|
163
|
+
return this.note;
|
|
164
|
+
return '- No Constraint';
|
|
165
|
+
}
|
|
166
|
+
// describe the constraints in human terms.
|
|
167
|
+
describe() {
|
|
168
|
+
if (this.badName)
|
|
169
|
+
return `"${this.badName}" is not a recognized constraint for ${this.type}`;
|
|
170
|
+
if (this.note)
|
|
171
|
+
return this.note;
|
|
172
|
+
return 'No Constraint';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.TypeConstraint = TypeConstraint;
|
|
176
|
+
// /**
|
|
177
|
+
// * Enumeration of recognized status for a parameter or return constraint
|
|
178
|
+
// */
|
|
179
|
+
// enum ConstraintStatus {
|
|
180
|
+
// None = "", // not parsed
|
|
181
|
+
// NotConstraint = "NotConstraint", // doesn't start with '-', treat as description
|
|
182
|
+
// Error = "Error", // parsing error
|
|
183
|
+
// NotProvided = "NotProvided", // no constraint block
|
|
184
|
+
//
|
|
185
|
+
//
|
|
186
|
+
// }
|
|
187
|
+
/**
|
|
188
|
+
* Null only applies to objects.
|
|
189
|
+
*/
|
|
190
|
+
// class NullConstraint extends TypeConstraint {
|
|
191
|
+
// test(value) {
|
|
192
|
+
// if(value || typeof value !== 'object') {
|
|
193
|
+
// throw new NullConstraintError()
|
|
194
|
+
// }
|
|
195
|
+
// }
|
|
196
|
+
// }
|
|
197
|
+
/**
|
|
198
|
+
* Constraints recorded on a number
|
|
199
|
+
* Integer, Positive, Negative, NotZero, min, max
|
|
200
|
+
*/
|
|
201
|
+
class NumberConstraint extends TypeConstraint {
|
|
202
|
+
constructor() {
|
|
203
|
+
super('number');
|
|
204
|
+
this.isInteger = false; // number must be an integer
|
|
205
|
+
this.isPositive = false; // number must be positive
|
|
206
|
+
this.isNegative = false; // number must be negative
|
|
207
|
+
this.notZero = false; // number must not be zero
|
|
208
|
+
}
|
|
209
|
+
test(value) {
|
|
210
|
+
super.test(value);
|
|
211
|
+
if (this.isInteger) {
|
|
212
|
+
if (Math.floor(value) !== value) {
|
|
213
|
+
throw new IntegerConstraintError(value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (this.notZero) {
|
|
217
|
+
if (value === 0) {
|
|
218
|
+
throw new ZeroValueConstraintError();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (this.isPositive && this.isNegative) {
|
|
222
|
+
throw new ConstraintConflictError('positive');
|
|
223
|
+
}
|
|
224
|
+
if (this.isPositive) {
|
|
225
|
+
if (value < 0) {
|
|
226
|
+
throw new PositiveConstraintError(value);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (this.isNegative) {
|
|
230
|
+
if (value < 0) {
|
|
231
|
+
throw new NegativeConstraintError(value);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (this.min !== undefined) {
|
|
235
|
+
if (value < this.min) {
|
|
236
|
+
throw new RangeConstraintError(value, this.min);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (this.max !== undefined) {
|
|
240
|
+
if (value > this.max) {
|
|
241
|
+
throw new RangeConstraintError(value, this.max);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (this.maxx !== undefined) {
|
|
245
|
+
if (value >= this.maxx) {
|
|
246
|
+
throw new RangeConstraintError(value, this.maxx);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
toString() {
|
|
251
|
+
const keys = [];
|
|
252
|
+
if (this.isInteger)
|
|
253
|
+
keys.push('Integer');
|
|
254
|
+
if (this.notZero)
|
|
255
|
+
keys.push('Not Zero');
|
|
256
|
+
if (this.isPositive)
|
|
257
|
+
keys.push('Positive');
|
|
258
|
+
if (this.isNegative)
|
|
259
|
+
keys.push('Negative');
|
|
260
|
+
if (this.min !== undefined)
|
|
261
|
+
keys.push(`Min = ${this.min}`);
|
|
262
|
+
if (this.max !== undefined)
|
|
263
|
+
keys.push(`Max = ${this.max}`);
|
|
264
|
+
if (this.maxx !== undefined)
|
|
265
|
+
keys.push(`Maxx = ${this.maxx}`);
|
|
266
|
+
if (this.note)
|
|
267
|
+
keys.push(this.note);
|
|
268
|
+
return (keys.length > 0) ? '- ' + keys.join(',') : super.toString();
|
|
269
|
+
}
|
|
270
|
+
describe() {
|
|
271
|
+
const keys = [];
|
|
272
|
+
if (this.isInteger)
|
|
273
|
+
keys.push('number must be an integer');
|
|
274
|
+
if (this.notZero)
|
|
275
|
+
keys.push('number must not be zero');
|
|
276
|
+
if (this.isPositive)
|
|
277
|
+
keys.push('number must be positive');
|
|
278
|
+
if (this.isNegative)
|
|
279
|
+
keys.push('number must be negative');
|
|
280
|
+
if (this.min !== undefined)
|
|
281
|
+
keys.push(`Minimum value is ${this.min}`);
|
|
282
|
+
if (this.max !== undefined)
|
|
283
|
+
keys.push(`Maximum value is ${this.max}`);
|
|
284
|
+
if (this.maxx !== undefined)
|
|
285
|
+
keys.push(`Maximum value is less than ${this.maxx}`);
|
|
286
|
+
if (this.note || this.badName)
|
|
287
|
+
keys.push(super.describe());
|
|
288
|
+
return (keys.length > 0) ? keys.join('\n') : super.describe();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Constraints recorded on a string
|
|
293
|
+
* minLength, maxLength, (!)startsWith, (!)endsWith, (!)contains, (!)match
|
|
294
|
+
*/
|
|
295
|
+
class StringConstraint extends TypeConstraint {
|
|
296
|
+
constructor() {
|
|
297
|
+
super('string');
|
|
298
|
+
}
|
|
299
|
+
test(value) {
|
|
300
|
+
super.test(value);
|
|
301
|
+
if (this.minLength) {
|
|
302
|
+
if (value.length < this.minLength) {
|
|
303
|
+
throw new RangeConstraintError(value.length, this.minLength, 'String Length');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (this.maxLength) {
|
|
307
|
+
if (value.length > this.maxLength) {
|
|
308
|
+
throw new RangeConstraintError(value.length, this.maxLength, 'String Length');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (this.startsWith && this.notStartsWith) {
|
|
312
|
+
throw new ConstraintConflictError('startsWith');
|
|
313
|
+
}
|
|
314
|
+
if (this.startsWith || this.notStartsWith) {
|
|
315
|
+
const comp = this.startsWith || this.notStartsWith || '';
|
|
316
|
+
const not = !!this.notStartsWith;
|
|
317
|
+
if (value.substring(0, comp.length) === comp) {
|
|
318
|
+
if (not)
|
|
319
|
+
throw new ConstraintFail('!startsWith', value);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
if (!not)
|
|
323
|
+
throw new ConstraintFail('startsWith', value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (this.endsWith && this.notEndsWith) {
|
|
327
|
+
throw new ConstraintConflictError('endsWith');
|
|
328
|
+
}
|
|
329
|
+
if (this.endsWith || this.notEndsWith) {
|
|
330
|
+
const comp = this.endsWith || this.notEndsWith || '';
|
|
331
|
+
const not = !!this.notEndsWith;
|
|
332
|
+
if (value.substring(value.length - comp.length) === comp) {
|
|
333
|
+
if (not)
|
|
334
|
+
throw new ConstraintFail('!endsWith', value);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
if (!not)
|
|
338
|
+
throw new ConstraintFail('endsWith', value);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (this.contains && this.notContains) {
|
|
342
|
+
throw new ConstraintConflictError('contains');
|
|
343
|
+
}
|
|
344
|
+
if (this.contains || this.notContains) {
|
|
345
|
+
const comp = this.contains || this.notContains;
|
|
346
|
+
const not = !!this.notContains;
|
|
347
|
+
if (value.indexOf(comp) !== -1) {
|
|
348
|
+
if (not)
|
|
349
|
+
throw new ConstraintFail('!contains', value);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
if (!not)
|
|
353
|
+
throw new ConstraintFail('contains', value);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (this.match && this.notMatch) {
|
|
357
|
+
throw new ConstraintConflictError('match');
|
|
358
|
+
}
|
|
359
|
+
if (this.match || this.notMatch) {
|
|
360
|
+
const comp = this.match || this.notMatch;
|
|
361
|
+
const not = !!this.notMatch;
|
|
362
|
+
const re = new RegExp(comp || '');
|
|
363
|
+
if (re.test(value)) {
|
|
364
|
+
if (not)
|
|
365
|
+
throw new ConstraintFail('!match', value);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
if (!not)
|
|
369
|
+
throw new ConstraintFail('match', value);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
toString() {
|
|
374
|
+
const keys = [];
|
|
375
|
+
if (this.minLength)
|
|
376
|
+
keys.push(`Min Length = ${this.minLength}`);
|
|
377
|
+
if (this.maxLength)
|
|
378
|
+
keys.push(`Max Length = ${this.maxLength}`);
|
|
379
|
+
if (this.startsWith)
|
|
380
|
+
keys.push(`Starts With = ${this.startsWith}`);
|
|
381
|
+
if (this.notStartsWith)
|
|
382
|
+
keys.push(`!StartsWith = ${this.startsWith}`);
|
|
383
|
+
if (this.endsWith)
|
|
384
|
+
keys.push(`Ends With = ${this.endsWith}`);
|
|
385
|
+
if (this.notEndsWith)
|
|
386
|
+
keys.push(`!EndsWith = ${this.endsWith}`);
|
|
387
|
+
if (this.contains)
|
|
388
|
+
keys.push(`Contains = ${this.contains}`);
|
|
389
|
+
if (this.notContains)
|
|
390
|
+
keys.push(`!Contains = ${this.notContains}`);
|
|
391
|
+
if (this.match)
|
|
392
|
+
keys.push(`Match = ${this.match}`);
|
|
393
|
+
if (this.notMatch)
|
|
394
|
+
keys.push(`!Match = ${this.notMatch}`);
|
|
395
|
+
if (this.note)
|
|
396
|
+
keys.push(this.note);
|
|
397
|
+
return (keys.length > 0) ? '- ' + keys.join(',') : super.toString();
|
|
398
|
+
}
|
|
399
|
+
describe() {
|
|
400
|
+
const keys = [];
|
|
401
|
+
if (this.minLength)
|
|
402
|
+
keys.push(`string must be at least ${this.minLength} characters long`);
|
|
403
|
+
if (this.maxLength)
|
|
404
|
+
keys.push(`string must consist of less than ${this.maxLength} characters`);
|
|
405
|
+
if (this.startsWith)
|
|
406
|
+
keys.push(`string must start with "${this.startsWith}"`);
|
|
407
|
+
if (this.notStartsWith)
|
|
408
|
+
keys.push(`string must NOT start with "${this.startsWith}"`);
|
|
409
|
+
if (this.endsWith)
|
|
410
|
+
keys.push(`string must end with "${this.endsWith}"`);
|
|
411
|
+
if (this.notEndsWith)
|
|
412
|
+
keys.push(`string must NOT end with "${this.endsWith}"`);
|
|
413
|
+
if (this.contains)
|
|
414
|
+
keys.push(`must contain substring "${this.contains}"`);
|
|
415
|
+
if (this.notContains)
|
|
416
|
+
keys.push(`must NOT contain substring "${this.notContains}"`);
|
|
417
|
+
if (this.match)
|
|
418
|
+
keys.push(`must match Regular Expression "${this.match}"`);
|
|
419
|
+
if (this.notMatch)
|
|
420
|
+
keys.push(`must NOT match RegExp "${this.notMatch}"`);
|
|
421
|
+
if (this.note || this.badName)
|
|
422
|
+
keys.push(super.describe());
|
|
423
|
+
return (keys.length > 0) ? keys.join('\n') : super.describe();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Constraints recorded on an object
|
|
428
|
+
* (!)empty, (!)hasProperties, notNested, noPrototype, canSerialize, noUndefinedProps
|
|
429
|
+
*/
|
|
430
|
+
class ObjectConstraint extends TypeConstraint {
|
|
431
|
+
constructor() {
|
|
432
|
+
super('object');
|
|
433
|
+
}
|
|
434
|
+
test(value) {
|
|
435
|
+
super.test(value);
|
|
436
|
+
if (this.empty && this.notEmpty) {
|
|
437
|
+
throw new ConstraintConflictError('empty');
|
|
438
|
+
}
|
|
439
|
+
if ((this.hasProperties != null) && (this.notHasProperties != null)) {
|
|
440
|
+
const collisions = [];
|
|
441
|
+
for (const has of this.hasProperties) {
|
|
442
|
+
if (this.notHasProperties.includes(has)) {
|
|
443
|
+
collisions.push(has);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (collisions.length > 0) {
|
|
447
|
+
throw new ConstraintConflictError('hasProperties "' + collisions.join(',') + '"');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (this.empty) {
|
|
451
|
+
if (Object.getOwnPropertyNames(value).length > 0) {
|
|
452
|
+
throw new ConstraintFail('empty', 'object contains ' + Object.getOwnPropertyNames(value).length + ' props');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (this.notEmpty) {
|
|
456
|
+
if (Object.getOwnPropertyNames(value).length === 0) {
|
|
457
|
+
throw new ConstraintFail('!empty', value);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (this.hasProperties != null) {
|
|
461
|
+
for (const has of this.hasProperties) {
|
|
462
|
+
if (!value.hasOwnProperty(has)) {
|
|
463
|
+
throw new ConstraintFail('hasProperties', has);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (this.notHasProperties != null) {
|
|
468
|
+
for (const hasnot of this.notHasProperties) {
|
|
469
|
+
if (value.hasOwnProperty(hasnot)) {
|
|
470
|
+
throw new ConstraintFail('!hasProperties', hasnot);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (this.notNested) {
|
|
475
|
+
for (const p of Object.getOwnPropertyNames(value)) {
|
|
476
|
+
const v = value[p];
|
|
477
|
+
if (typeof v === 'object') {
|
|
478
|
+
if (!Array.isArray(v)) {
|
|
479
|
+
throw new ConstraintFail('notNested', p);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (this.noPrototype) {
|
|
485
|
+
const prot = Object.getPrototypeOf(value);
|
|
486
|
+
const name = prot && prot.constructor.name;
|
|
487
|
+
if (name && name !== 'Object') {
|
|
488
|
+
throw new ConstraintFail('noPrototype', value);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (this.canSerialize) {
|
|
492
|
+
let json;
|
|
493
|
+
try {
|
|
494
|
+
json = JSON.stringify(value);
|
|
495
|
+
}
|
|
496
|
+
catch (e) {
|
|
497
|
+
}
|
|
498
|
+
if (!json) {
|
|
499
|
+
throw new ConstraintFail('canSerialize', value);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (this.noFalseyProps) {
|
|
503
|
+
for (const p of Object.getOwnPropertyNames(value)) {
|
|
504
|
+
const v = value[p];
|
|
505
|
+
if (!v) {
|
|
506
|
+
throw new ConstraintFail('noFalseyProps', p);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (this.noTruthyProps) {
|
|
511
|
+
for (const p of Object.getOwnPropertyNames(value)) {
|
|
512
|
+
const v = value[p];
|
|
513
|
+
if (v) {
|
|
514
|
+
throw new ConstraintFail('noTruthyProps', p);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (this.instanceOf) {
|
|
519
|
+
if (value.constructor.name !== this.instanceOf) {
|
|
520
|
+
throw new ConstraintFail('instanceOf (' + this.instanceOf + ')', value.constructor.name);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (this.notInstanceOf) {
|
|
524
|
+
if (value.constructor.name === this.notInstanceOf) {
|
|
525
|
+
throw new ConstraintFail('!instanceOf', this.notInstanceOf);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
toString() {
|
|
530
|
+
const keys = [];
|
|
531
|
+
if (this.empty)
|
|
532
|
+
keys.push('Empty');
|
|
533
|
+
if (this.notEmpty)
|
|
534
|
+
keys.push('!Empty');
|
|
535
|
+
if (this.hasProperties != null)
|
|
536
|
+
keys.push(`Has Properties =${this.hasProperties.join(',')}`);
|
|
537
|
+
if (this.notHasProperties != null)
|
|
538
|
+
keys.push(`!Has Properties =${this.notHasProperties}`);
|
|
539
|
+
if (this.notNested)
|
|
540
|
+
keys.push('Not Nested');
|
|
541
|
+
if (this.noPrototype)
|
|
542
|
+
keys.push('No Prototype');
|
|
543
|
+
if (this.canSerialize)
|
|
544
|
+
keys.push('Can Serialize');
|
|
545
|
+
if (this.noFalseyProps)
|
|
546
|
+
keys.push('No Falsey Props');
|
|
547
|
+
if (this.noTruthyProps)
|
|
548
|
+
keys.push('No Truthy Props');
|
|
549
|
+
if (this.instanceOf)
|
|
550
|
+
keys.push(`Instance Of = ${this.instanceOf}`);
|
|
551
|
+
if (this.notInstanceOf)
|
|
552
|
+
keys.push(`Not an instance of ${this.notInstanceOf}`);
|
|
553
|
+
if (this.note)
|
|
554
|
+
keys.push(this.note);
|
|
555
|
+
return (keys.length > 0) ? '- ' + keys.join(',') : super.toString();
|
|
556
|
+
}
|
|
557
|
+
describe() {
|
|
558
|
+
const keys = [];
|
|
559
|
+
if (this.empty)
|
|
560
|
+
keys.push('object must be empty');
|
|
561
|
+
if (this.notEmpty)
|
|
562
|
+
keys.push('object must not be empty');
|
|
563
|
+
if (this.hasProperties != null)
|
|
564
|
+
keys.push(`object must contain properties "${this.hasProperties.join(',')}"`);
|
|
565
|
+
if (this.notHasProperties != null)
|
|
566
|
+
keys.push(`object must not contain properties "${this.notHasProperties.join(',')}"`);
|
|
567
|
+
if (this.notNested)
|
|
568
|
+
keys.push('object must not contain nested objects');
|
|
569
|
+
if (this.noPrototype)
|
|
570
|
+
keys.push('object must not derive from a prototype');
|
|
571
|
+
if (this.canSerialize)
|
|
572
|
+
keys.push('object can be serialized');
|
|
573
|
+
if (this.noFalseyProps)
|
|
574
|
+
keys.push('object can contain no properties that evaluate as false');
|
|
575
|
+
if (this.noTruthyProps)
|
|
576
|
+
keys.push('object can contain no properties that evaluate as true');
|
|
577
|
+
if (this.instanceOf)
|
|
578
|
+
keys.push(`object must be an instance of "${this.instanceOf}"`);
|
|
579
|
+
if (this.notInstanceOf)
|
|
580
|
+
keys.push(`object must not be an instance of "${this.notInstanceOf}"`);
|
|
581
|
+
if (this.note || this.badName)
|
|
582
|
+
keys.push(super.describe());
|
|
583
|
+
return (keys.length > 0) ? keys.join('\n') : super.describe();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Enumeration of checkType parsed results.
|
|
588
|
+
*
|
|
589
|
+
* parameters (p1, p2) are parsed at same time, and meaning does vary per checkType.
|
|
590
|
+
*/
|
|
591
|
+
var ElementCheckType;
|
|
592
|
+
(function (ElementCheckType) {
|
|
593
|
+
ElementCheckType[ElementCheckType["none"] = 0] = "none";
|
|
594
|
+
ElementCheckType[ElementCheckType["all"] = 1] = "all";
|
|
595
|
+
ElementCheckType[ElementCheckType["random"] = 2] = "random";
|
|
596
|
+
ElementCheckType[ElementCheckType["step"] = 3] = "step";
|
|
597
|
+
ElementCheckType[ElementCheckType["first"] = 4] = "first";
|
|
598
|
+
ElementCheckType[ElementCheckType["last"] = 5] = "last";
|
|
599
|
+
ElementCheckType[ElementCheckType["firstThenLast"] = 6] = "firstThenLast";
|
|
600
|
+
ElementCheckType[ElementCheckType["firstThenStep"] = 7] = "firstThenStep";
|
|
601
|
+
ElementCheckType[ElementCheckType["firstThenRandom"] = 8] = "firstThenRandom"; // test all up to (p1) elements, then up to (p2) of the remaining, chosen at random
|
|
602
|
+
})(ElementCheckType || (exports.ElementCheckType = ElementCheckType = {}));
|
|
603
|
+
/**
|
|
604
|
+
* Constraints recorded on an array
|
|
605
|
+
* minLength, maxLength, (!)contains, checkType, each
|
|
606
|
+
*/
|
|
607
|
+
class ArrayConstraint extends TypeConstraint {
|
|
608
|
+
constructor() {
|
|
609
|
+
super('array');
|
|
610
|
+
this.elementConstraints = []; // elements are tested for compliance under these rules
|
|
611
|
+
this.elementCheckType = ElementCheckType.none; // defines the extent of runtime coverage on elements
|
|
612
|
+
this.elementCheckParameter = 0; // defined by elementCheckType
|
|
613
|
+
this.elementCheckParameter2 = 0; // defined by elementCheckType
|
|
614
|
+
}
|
|
615
|
+
test(value) {
|
|
616
|
+
if (!Array.isArray(value)) {
|
|
617
|
+
throw new ConstraintBasicTypeError(value, 'array');
|
|
618
|
+
}
|
|
619
|
+
const length = value.length;
|
|
620
|
+
if (this.minLength) {
|
|
621
|
+
if (length < this.minLength) {
|
|
622
|
+
throw new RangeConstraintError(length, this.minLength, 'Array Length');
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (this.maxLength) {
|
|
626
|
+
if (length > this.maxLength) {
|
|
627
|
+
throw new RangeConstraintError(length, this.maxLength, 'Array Length');
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (this.contains && this.notContains) {
|
|
631
|
+
throw new ConstraintConflictError('contains');
|
|
632
|
+
}
|
|
633
|
+
if (this.contains || this.notContains) {
|
|
634
|
+
const comp = this.contains || this.notContains;
|
|
635
|
+
const not = !!this.notContains;
|
|
636
|
+
if (value.includes(comp)) {
|
|
637
|
+
if (not)
|
|
638
|
+
throw new ConstraintFail('!contains', this.notContains);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
if (!not)
|
|
642
|
+
throw new ConstraintFail('contains', this.contains);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (this.elementConstraints || this.elementCheckType) {
|
|
646
|
+
const checkType = this.elementCheckType === undefined ? ElementCheckType.all : this.elementCheckType;
|
|
647
|
+
let i = 0;
|
|
648
|
+
let count = 0;
|
|
649
|
+
let step = 1;
|
|
650
|
+
let firstCount = 0;
|
|
651
|
+
let thenCount = 0;
|
|
652
|
+
let counting = false;
|
|
653
|
+
const tested = {};
|
|
654
|
+
switch (checkType) {
|
|
655
|
+
case ElementCheckType.none:
|
|
656
|
+
firstCount = 0;
|
|
657
|
+
thenCount = 0;
|
|
658
|
+
counting = false;
|
|
659
|
+
break;
|
|
660
|
+
case ElementCheckType.all:
|
|
661
|
+
firstCount = length;
|
|
662
|
+
step = 1;
|
|
663
|
+
thenCount = 0;
|
|
664
|
+
counting = true;
|
|
665
|
+
break;
|
|
666
|
+
case ElementCheckType.first:
|
|
667
|
+
firstCount = parseInt('' + this.elementCheckParameter);
|
|
668
|
+
step = 1;
|
|
669
|
+
thenCount = 0;
|
|
670
|
+
counting = true;
|
|
671
|
+
break;
|
|
672
|
+
case ElementCheckType.last:
|
|
673
|
+
firstCount = 0;
|
|
674
|
+
step = 1;
|
|
675
|
+
thenCount = length - this.elementCheckParameter;
|
|
676
|
+
counting = false;
|
|
677
|
+
break;
|
|
678
|
+
case ElementCheckType.firstThenLast:
|
|
679
|
+
firstCount = parseInt('' + this.elementCheckParameter);
|
|
680
|
+
thenCount = length - this.elementCheckParameter;
|
|
681
|
+
if (thenCount < 0)
|
|
682
|
+
thenCount = length;
|
|
683
|
+
step = 1;
|
|
684
|
+
counting = true;
|
|
685
|
+
break;
|
|
686
|
+
case ElementCheckType.step:
|
|
687
|
+
firstCount = length;
|
|
688
|
+
thenCount = 0;
|
|
689
|
+
counting = true;
|
|
690
|
+
step = parseInt('' + this.elementCheckParameter);
|
|
691
|
+
break;
|
|
692
|
+
case ElementCheckType.random:
|
|
693
|
+
firstCount = 0;
|
|
694
|
+
thenCount = parseInt('' + this.elementCheckParameter);
|
|
695
|
+
step = 0;
|
|
696
|
+
counting = true;
|
|
697
|
+
break;
|
|
698
|
+
case ElementCheckType.firstThenStep:
|
|
699
|
+
firstCount = parseInt('' + this.elementCheckParameter);
|
|
700
|
+
thenCount = length - firstCount;
|
|
701
|
+
step = parseInt('' + this.elementCheckParameter2);
|
|
702
|
+
counting = true;
|
|
703
|
+
break;
|
|
704
|
+
case ElementCheckType.firstThenRandom:
|
|
705
|
+
firstCount = parseInt('' + this.elementCheckParameter);
|
|
706
|
+
thenCount = parseInt('' + this.elementCheckParameter2);
|
|
707
|
+
step = 0;
|
|
708
|
+
counting = true;
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
while (i < length) {
|
|
712
|
+
if (counting) {
|
|
713
|
+
const ev = value[i];
|
|
714
|
+
let t = typeof ev;
|
|
715
|
+
if (Array.isArray(ev))
|
|
716
|
+
t = 'array';
|
|
717
|
+
const m = this.elementConstraints;
|
|
718
|
+
const c = m && m.get(t);
|
|
719
|
+
const tc = (c != null) || parseConstraints(t, '');
|
|
720
|
+
if (tc !== true && (tc === null || tc === void 0 ? void 0 : tc.test) != undefined)
|
|
721
|
+
tc.test(ev);
|
|
722
|
+
count++;
|
|
723
|
+
}
|
|
724
|
+
if ((checkType === ElementCheckType.last || checkType === ElementCheckType.firstThenLast) && i === thenCount) {
|
|
725
|
+
counting = true;
|
|
726
|
+
}
|
|
727
|
+
if (checkType === ElementCheckType.firstThenLast && i === firstCount) {
|
|
728
|
+
counting = false;
|
|
729
|
+
}
|
|
730
|
+
if (count >= firstCount) {
|
|
731
|
+
if (count >= firstCount + thenCount) {
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (step) {
|
|
736
|
+
i += step;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
while (true) {
|
|
740
|
+
const rr = Math.floor(Math.random() * (length - count));
|
|
741
|
+
i = count + rr;
|
|
742
|
+
if (i < length && !tested[i]) {
|
|
743
|
+
tested[i] = true;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
toString() {
|
|
752
|
+
const keys = [];
|
|
753
|
+
if (this.minLength)
|
|
754
|
+
keys.push(`Min Length = ${this.minLength}`);
|
|
755
|
+
if (this.maxLength)
|
|
756
|
+
keys.push(`Max Length = ${this.maxLength}`);
|
|
757
|
+
if (this.contains)
|
|
758
|
+
keys.push(`Contains = ${this.contains}`);
|
|
759
|
+
if (this.notContains)
|
|
760
|
+
keys.push(`!Contains = ${this.notContains}`);
|
|
761
|
+
if (this.elementConstraints)
|
|
762
|
+
keys.push(`each element of the array has the following constraints by type ${listEachConstraints(this.elementConstraints)}`);
|
|
763
|
+
if (this.elementCheckType)
|
|
764
|
+
keys.push(`(elements will be tested using the ${checkTypeToString(this.elementCheckType, this.elementCheckParameter, this.elementCheckParameter2)} method)`);
|
|
765
|
+
if (this.note)
|
|
766
|
+
keys.push(this.note);
|
|
767
|
+
return (keys.length > 0) ? '- ' + keys.join(',') : super.toString();
|
|
768
|
+
}
|
|
769
|
+
describe() {
|
|
770
|
+
const keys = [];
|
|
771
|
+
if (this.minLength)
|
|
772
|
+
keys.push(`array must contain at least ${this.minLength} elements`);
|
|
773
|
+
if (this.maxLength)
|
|
774
|
+
keys.push(`array must contain no more than ${this.maxLength} elements`);
|
|
775
|
+
if (this.contains)
|
|
776
|
+
keys.push(`array must contain element value "${this.contains}"`);
|
|
777
|
+
if (this.notContains)
|
|
778
|
+
keys.push(`array must not contain an element value "${this.notContains}"`);
|
|
779
|
+
if (this.elementConstraints)
|
|
780
|
+
keys.push(`each element of the array has the following constraints by type ${listEachConstraints(this.elementConstraints)}`);
|
|
781
|
+
if (this.elementCheckType)
|
|
782
|
+
keys.push(`(elements will be tested using the ${checkTypeToString(this.elementCheckType, this.elementCheckParameter, this.elementCheckParameter2)} method)`);
|
|
783
|
+
if (this.note || this.badName)
|
|
784
|
+
keys.push(super.describe());
|
|
785
|
+
return (keys.length > 0) ? keys.join('\n') : super.describe();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function listEachConstraints(cmap) {
|
|
789
|
+
let out = '';
|
|
790
|
+
const types = cmap.keys();
|
|
791
|
+
const entries = cmap.entries();
|
|
792
|
+
let entry;
|
|
793
|
+
while ((entry = entries.next().value)) {
|
|
794
|
+
out += '<br/><b>' + entry[0] + ' elements:</b><br/> -';
|
|
795
|
+
out += entry[1].describe().replace(/\n/g, '<br/> - ');
|
|
796
|
+
}
|
|
797
|
+
return out;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Translates a type string (number, string, boolean, object, array, regex) into the corresponding ValueType enum
|
|
801
|
+
* Note that strings beside none, array, and regex are synonymous with the `typeof` operator value
|
|
802
|
+
* @param str
|
|
803
|
+
*/
|
|
804
|
+
function valueTypeFromString(str) {
|
|
805
|
+
switch (str.trim().toLowerCase()) {
|
|
806
|
+
default: return str.trim().length ? str.includes('[]') ? ValueType.array : ValueType.object : ValueType.none;
|
|
807
|
+
case 'number': return ValueType.number;
|
|
808
|
+
case 'string': return ValueType.string;
|
|
809
|
+
case 'boolean': return ValueType.boolean;
|
|
810
|
+
case 'object': return ValueType.object;
|
|
811
|
+
case 'array': return ValueType.array;
|
|
812
|
+
case 'regex': return ValueType.regex;
|
|
813
|
+
case 'regexp': return ValueType.regex;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
exports.valueTypeFromString = valueTypeFromString;
|
|
817
|
+
/**
|
|
818
|
+
* Translates a ValueType enum value into the corresponding string.
|
|
819
|
+
* Note that strings beside none, array, and regex are synonymous with the `typeof` operator value
|
|
820
|
+
* @param vt
|
|
821
|
+
*/
|
|
822
|
+
function stringFromValueType(vt) {
|
|
823
|
+
switch (vt) {
|
|
824
|
+
case ValueType.none: return '';
|
|
825
|
+
case ValueType.number: return 'number';
|
|
826
|
+
case ValueType.string: return 'string';
|
|
827
|
+
case ValueType.boolean: return 'boolean';
|
|
828
|
+
case ValueType.object: return 'object';
|
|
829
|
+
case ValueType.array: return 'array';
|
|
830
|
+
case ValueType.regex: return 'regex';
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
exports.stringFromValueType = stringFromValueType;
|
|
834
|
+
/**
|
|
835
|
+
* Read either a value or a list from an expression value
|
|
836
|
+
* @param str
|
|
837
|
+
*/
|
|
838
|
+
function constraintListParse(str = '') {
|
|
839
|
+
str.trim();
|
|
840
|
+
if (str.charAt(0) === '"' || str.charAt(0) === "'") {
|
|
841
|
+
str = str.substring(1, str.length - 1);
|
|
842
|
+
}
|
|
843
|
+
if (str.includes(',')) {
|
|
844
|
+
return str.split(','); // return the split array
|
|
845
|
+
}
|
|
846
|
+
if (isFinite(Number(str))) {
|
|
847
|
+
return Number(str);
|
|
848
|
+
}
|
|
849
|
+
return str; // return the unquoted string value
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Used to parse the type+constraints blocks from an "each" directive list
|
|
853
|
+
* @param str
|
|
854
|
+
*/
|
|
855
|
+
function eachListParse(str = '') {
|
|
856
|
+
const map = new Map();
|
|
857
|
+
const esplit = str.split('|');
|
|
858
|
+
for (const tblock of esplit) {
|
|
859
|
+
const ci = tblock.indexOf(',');
|
|
860
|
+
if (ci !== -1) {
|
|
861
|
+
const type = tblock.substring(0, ci).trim();
|
|
862
|
+
const cdef = tblock.substring(ci + 1);
|
|
863
|
+
const constraint = (parseConstraints(type, cdef) != null) || new TypeConstraint();
|
|
864
|
+
if (constraint !== undefined && constraint !== true) {
|
|
865
|
+
map.set(type, constraint);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return map;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Parse out the checkType and return the resulting type name and the parsed parameters in a structure.
|
|
873
|
+
* @param ctStr
|
|
874
|
+
* @return {{string}name,{number}[p1],{number}[p2]}
|
|
875
|
+
*/
|
|
876
|
+
function parseCheckType(ctStr = '') {
|
|
877
|
+
let opi = ctStr.indexOf('(');
|
|
878
|
+
if (opi === -1)
|
|
879
|
+
opi = ctStr.length;
|
|
880
|
+
const name = ctStr.substring(0, opi);
|
|
881
|
+
let cpi = ctStr.indexOf(')', opi);
|
|
882
|
+
if (cpi === -1)
|
|
883
|
+
cpi = ctStr.length;
|
|
884
|
+
const p = ctStr.substring(opi + 1, cpi).split(',');
|
|
885
|
+
let p1, p2;
|
|
886
|
+
try {
|
|
887
|
+
p1 = p[0] && parseInt(p[0]);
|
|
888
|
+
p2 = p[1] && parseInt(p[1]);
|
|
889
|
+
}
|
|
890
|
+
catch (e) { }
|
|
891
|
+
return { name, p1, p2 };
|
|
892
|
+
}
|
|
893
|
+
function checkTypeToString(ct, p1, p2) {
|
|
894
|
+
switch (ct) {
|
|
895
|
+
case ElementCheckType.random:
|
|
896
|
+
return `random(${p1})`;
|
|
897
|
+
case ElementCheckType.step:
|
|
898
|
+
return `step(${p1})`;
|
|
899
|
+
case ElementCheckType.first:
|
|
900
|
+
return `first(${p1})`;
|
|
901
|
+
case ElementCheckType.last:
|
|
902
|
+
return `last(${p1})`;
|
|
903
|
+
case ElementCheckType.firstThenLast:
|
|
904
|
+
return `firstThenLast(${p1},${p2})`;
|
|
905
|
+
case ElementCheckType.firstThenStep:
|
|
906
|
+
return `firstThenStep(${p1},${p2})`;
|
|
907
|
+
case ElementCheckType.firstThenRandom:
|
|
908
|
+
return `firstThenRandom(${p1},${p2})`;
|
|
909
|
+
case ElementCheckType.none:
|
|
910
|
+
return 'none';
|
|
911
|
+
default:
|
|
912
|
+
case ElementCheckType.all:
|
|
913
|
+
return 'all';
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function checkTypeFromString(ctstr) {
|
|
917
|
+
switch (ctstr.trim().toLowerCase()) {
|
|
918
|
+
case 'random': return ElementCheckType.random;
|
|
919
|
+
case 'step': return ElementCheckType.step;
|
|
920
|
+
case 'first': return ElementCheckType.first;
|
|
921
|
+
case 'last': return ElementCheckType.last;
|
|
922
|
+
case 'firstthenlast': return ElementCheckType.firstThenLast;
|
|
923
|
+
case 'firstthenstep': return ElementCheckType.firstThenStep;
|
|
924
|
+
case 'firstthenrandom': return ElementCheckType.firstThenRandom;
|
|
925
|
+
case 'none': return ElementCheckType.none;
|
|
926
|
+
default:
|
|
927
|
+
case 'all': return ElementCheckType.all;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// parse constraints from what may be more than one type (e.g. string|number)
|
|
931
|
+
function parseConstraintsToMap(typeString, blockSet = '') {
|
|
932
|
+
const map = new Map();
|
|
933
|
+
const types = typeString.split('|');
|
|
934
|
+
const blocks = blockSet.split(',');
|
|
935
|
+
for (let type of types) {
|
|
936
|
+
type = (type || '').trim();
|
|
937
|
+
const constraint = (parseConstraints(type, blockSet) != null) || new TypeConstraint();
|
|
938
|
+
if (constraint !== undefined && constraint !== true) {
|
|
939
|
+
map.set(type, constraint);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return map;
|
|
943
|
+
}
|
|
944
|
+
exports.parseConstraintsToMap = parseConstraintsToMap;
|
|
945
|
+
/**
|
|
946
|
+
* Given a block of text, parse as constraints and return the set if this is a constraint declaration
|
|
947
|
+
* otherwise, return ConstraintStatus.NotConstraint to signify this is a description block and not a constraint declaration
|
|
948
|
+
* @param type - the type parsed from the param or return declaration
|
|
949
|
+
* @param block - the block of text to evaluate
|
|
950
|
+
* @param delim - the split delimiter (defaults to ',')
|
|
951
|
+
*/
|
|
952
|
+
function parseConstraints(type, block, delim = ',') {
|
|
953
|
+
let constraint;
|
|
954
|
+
if (!block || !type)
|
|
955
|
+
return;
|
|
956
|
+
const valueType = valueTypeFromString(type);
|
|
957
|
+
let cblock = block.trim();
|
|
958
|
+
// get any constraint parameters
|
|
959
|
+
let fpi = cblock.indexOf('(');
|
|
960
|
+
while (fpi !== -1) {
|
|
961
|
+
let cpi = cblock.indexOf(')', fpi);
|
|
962
|
+
if (cpi === -1)
|
|
963
|
+
cpi = cblock.length;
|
|
964
|
+
const swap = cblock.substring(fpi, cpi).replace(/,/g, ';;');
|
|
965
|
+
cblock = cblock.substring(0, fpi) + swap + cblock.substring(cpi);
|
|
966
|
+
fpi = cblock.indexOf('(', cpi);
|
|
967
|
+
}
|
|
968
|
+
const expressions = cblock.split(delim);
|
|
969
|
+
for (let expr of expressions) {
|
|
970
|
+
let expVal;
|
|
971
|
+
let params;
|
|
972
|
+
let not = false;
|
|
973
|
+
expr = expr.trim();
|
|
974
|
+
if (!expr.startsWith('match') && !expr.startsWith('!match')) {
|
|
975
|
+
const cpi = expr.indexOf('(');
|
|
976
|
+
if (cpi !== -1) {
|
|
977
|
+
params = expr.substring(cpi).replace(/;;/g, ',').trim();
|
|
978
|
+
if (params.charAt(0) === '(')
|
|
979
|
+
params = params.substring(1);
|
|
980
|
+
if (params.charAt(params.length - 1) === ')')
|
|
981
|
+
params = params.substring(0, params.length - 1);
|
|
982
|
+
expr = expr.substring(0, cpi).trim();
|
|
983
|
+
if (expr === 'each') {
|
|
984
|
+
expVal = eachListParse(params);
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
expVal = constraintListParse(params);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (expr.charAt(0) === '!') {
|
|
992
|
+
not = true;
|
|
993
|
+
expr = expr.substring(1);
|
|
994
|
+
}
|
|
995
|
+
if (expr.includes('=')) {
|
|
996
|
+
const p = expr.split('=');
|
|
997
|
+
if (p.length > 2) {
|
|
998
|
+
p[1] = p.slice(1).join('=');
|
|
999
|
+
}
|
|
1000
|
+
expr = p[0].trim();
|
|
1001
|
+
if (expr === 'each') {
|
|
1002
|
+
expVal = eachListParse(p[1]);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
expVal = constraintListParse(p[1]);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
expr = expr.trim().toLowerCase();
|
|
1009
|
+
switch (valueType) {
|
|
1010
|
+
case ValueType.number:
|
|
1011
|
+
constraint = constraint || new NumberConstraint();
|
|
1012
|
+
switch (expr) {
|
|
1013
|
+
case 'noconstraint':
|
|
1014
|
+
case 'no constraint':
|
|
1015
|
+
return constraint; // early exit if we encounter "- No Constraint"
|
|
1016
|
+
/* Integer, Positive, Negative, NotZero, min, max */
|
|
1017
|
+
case 'integer':
|
|
1018
|
+
constraint.isInteger = true;
|
|
1019
|
+
break;
|
|
1020
|
+
case 'positive':
|
|
1021
|
+
constraint.isPositive = true;
|
|
1022
|
+
break;
|
|
1023
|
+
case 'negative':
|
|
1024
|
+
constraint.isNegative = true;
|
|
1025
|
+
break;
|
|
1026
|
+
case 'notzero':
|
|
1027
|
+
case 'not zero':
|
|
1028
|
+
case 'nonzero':
|
|
1029
|
+
constraint.notZero = true;
|
|
1030
|
+
break;
|
|
1031
|
+
case 'min':
|
|
1032
|
+
constraint.min = expVal;
|
|
1033
|
+
break;
|
|
1034
|
+
case 'max':
|
|
1035
|
+
constraint.max = expVal;
|
|
1036
|
+
break;
|
|
1037
|
+
case 'maxx':
|
|
1038
|
+
constraint.maxx = expVal;
|
|
1039
|
+
break;
|
|
1040
|
+
case 'note':
|
|
1041
|
+
constraint.note = expVal;
|
|
1042
|
+
break;
|
|
1043
|
+
default:
|
|
1044
|
+
constraint.badName = expr;
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
break;
|
|
1048
|
+
case ValueType.string:
|
|
1049
|
+
// minLength, maxLength, (!)startsWith, (!)endsWith, (!)contains, (!)match
|
|
1050
|
+
constraint = constraint || new StringConstraint();
|
|
1051
|
+
switch (expr) {
|
|
1052
|
+
case 'noconstraint':
|
|
1053
|
+
case 'no constraint':
|
|
1054
|
+
return constraint; // early exit if we encounter "- No Constraint"
|
|
1055
|
+
case 'minlength':
|
|
1056
|
+
constraint.minLength = expVal;
|
|
1057
|
+
break;
|
|
1058
|
+
case 'maxlength':
|
|
1059
|
+
constraint.maxLength = expVal;
|
|
1060
|
+
break;
|
|
1061
|
+
case 'startswith':
|
|
1062
|
+
not ? constraint.notStartsWith = expVal : constraint.startsWith = expVal;
|
|
1063
|
+
break;
|
|
1064
|
+
case 'endswith':
|
|
1065
|
+
not ? constraint.notEndsWith = expVal : constraint.endsWith = expVal;
|
|
1066
|
+
break;
|
|
1067
|
+
case 'contains':
|
|
1068
|
+
not ? constraint.notContains = expVal : constraint.contains = expVal;
|
|
1069
|
+
break;
|
|
1070
|
+
case 'match':
|
|
1071
|
+
not ? constraint.notMatch = expVal : constraint.match = expVal;
|
|
1072
|
+
break;
|
|
1073
|
+
case 'note':
|
|
1074
|
+
constraint.note = expVal;
|
|
1075
|
+
break;
|
|
1076
|
+
default:
|
|
1077
|
+
constraint.badName = expr;
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
1081
|
+
case ValueType.object:
|
|
1082
|
+
// (!)empty, (!)hasProperties, notNested, noPrototype, canSerialize, noUndefinedProps
|
|
1083
|
+
constraint = constraint || new ObjectConstraint();
|
|
1084
|
+
switch (expr) {
|
|
1085
|
+
case 'noconstraint':
|
|
1086
|
+
case 'no constraint':
|
|
1087
|
+
return constraint; // early exit if we encounter "- No Constraint"
|
|
1088
|
+
case 'empty':
|
|
1089
|
+
constraint.empty = !not;
|
|
1090
|
+
constraint.notEmpty = not;
|
|
1091
|
+
break;
|
|
1092
|
+
case 'hasproperties':
|
|
1093
|
+
case 'has properties':
|
|
1094
|
+
if (typeof expVal === 'string')
|
|
1095
|
+
expVal = [expVal];
|
|
1096
|
+
not ? constraint.notHasProperties = expVal : constraint.hasProperties = expVal;
|
|
1097
|
+
break;
|
|
1098
|
+
case 'notnested':
|
|
1099
|
+
case 'not nested':
|
|
1100
|
+
constraint.notNested = true;
|
|
1101
|
+
break;
|
|
1102
|
+
case 'noprototype':
|
|
1103
|
+
case 'no prototype':
|
|
1104
|
+
constraint.noPrototype = true;
|
|
1105
|
+
break;
|
|
1106
|
+
case 'canserialize':
|
|
1107
|
+
case 'can serialize':
|
|
1108
|
+
constraint.canSerialize = true;
|
|
1109
|
+
break;
|
|
1110
|
+
case 'notruthyprops':
|
|
1111
|
+
case 'no truthy props':
|
|
1112
|
+
constraint.noTruthyProps = true;
|
|
1113
|
+
break;
|
|
1114
|
+
case 'nofalseyprops':
|
|
1115
|
+
case 'no falsey props':
|
|
1116
|
+
constraint.noFalseyProps = true;
|
|
1117
|
+
break;
|
|
1118
|
+
case 'instanceof':
|
|
1119
|
+
case 'instance of':
|
|
1120
|
+
if (not)
|
|
1121
|
+
constraint.notInstanceOf = expVal;
|
|
1122
|
+
else
|
|
1123
|
+
constraint.instanceOf = expVal;
|
|
1124
|
+
break;
|
|
1125
|
+
case 'note':
|
|
1126
|
+
constraint.note = expVal;
|
|
1127
|
+
break;
|
|
1128
|
+
default:
|
|
1129
|
+
constraint.badName = expr;
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
break;
|
|
1133
|
+
case ValueType.array:
|
|
1134
|
+
// minLength, maxLength, (!)contains, each:
|
|
1135
|
+
constraint = constraint || new ArrayConstraint();
|
|
1136
|
+
switch (expr) {
|
|
1137
|
+
case 'noconstraint':
|
|
1138
|
+
case 'no constraint':
|
|
1139
|
+
return constraint; // early exit if we encounter "- No Constraint"
|
|
1140
|
+
case 'minlength':
|
|
1141
|
+
case 'min length':
|
|
1142
|
+
constraint.minLength = expVal;
|
|
1143
|
+
break;
|
|
1144
|
+
case 'maxlength':
|
|
1145
|
+
case 'max length':
|
|
1146
|
+
constraint.maxLength = expVal;
|
|
1147
|
+
break;
|
|
1148
|
+
case 'contains':
|
|
1149
|
+
not ? constraint.notContains = expVal : constraint.contains = expVal;
|
|
1150
|
+
break;
|
|
1151
|
+
case 'checktype':
|
|
1152
|
+
case 'check type':
|
|
1153
|
+
const psplit = (params || '').split(',');
|
|
1154
|
+
const pct = parseCheckType('' + expVal);
|
|
1155
|
+
constraint.elementCheckType = checkTypeFromString(pct.name);
|
|
1156
|
+
constraint.elementCheckParameter = (psplit[0] || pct.p1);
|
|
1157
|
+
constraint.elementCheckParameter2 = (psplit[1] || pct.p2);
|
|
1158
|
+
break;
|
|
1159
|
+
case 'each':
|
|
1160
|
+
const type = 'any';
|
|
1161
|
+
constraint.elementConstraints = expVal;
|
|
1162
|
+
break;
|
|
1163
|
+
case 'note':
|
|
1164
|
+
constraint.note = expVal;
|
|
1165
|
+
break;
|
|
1166
|
+
default:
|
|
1167
|
+
constraint.badName = expr;
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
break;
|
|
1171
|
+
default: // none, boolean, regex
|
|
1172
|
+
if (expr === 'no constraint')
|
|
1173
|
+
return;
|
|
1174
|
+
constraint = new TypeConstraint(stringFromValueType(valueType));
|
|
1175
|
+
break;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return constraint;
|
|
1179
|
+
}
|
|
1180
|
+
exports.parseConstraints = parseConstraints;
|
|
1181
|
+
/**
|
|
1182
|
+
* Simple test to see if a value adheres to a set of constraints
|
|
1183
|
+
*/
|
|
1184
|
+
function validate(value, // The value to test for constraints. Must be one of the basic types supported by contraints
|
|
1185
|
+
constraintString // the constraints to test it against. Constraints listed must match the type being tested. Do not include < > brackets.
|
|
1186
|
+
) {
|
|
1187
|
+
let type = typeof value;
|
|
1188
|
+
if (type === 'object') {
|
|
1189
|
+
if (Array.isArray(value))
|
|
1190
|
+
type = 'array';
|
|
1191
|
+
}
|
|
1192
|
+
const tc = parseConstraints(type, constraintString || '');
|
|
1193
|
+
let ok = '';
|
|
1194
|
+
try {
|
|
1195
|
+
if (tc != null)
|
|
1196
|
+
tc.test(value);
|
|
1197
|
+
}
|
|
1198
|
+
catch (e) {
|
|
1199
|
+
ok = e.message || e.toString();
|
|
1200
|
+
}
|
|
1201
|
+
return ok;
|
|
1202
|
+
}
|
|
1203
|
+
exports.validate = validate;
|
|
1204
|
+
//# sourceMappingURL=TypeCheck.js.map
|