schema-markdown 1.3.0 → 1.3.1
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/lib/encode.js +8 -3
- package/lib/parser.js +20 -17
- package/lib/schema.js +123 -66
- package/lib/schemaUtil.js +22 -22
- package/package.json +1 -1
package/lib/encode.js
CHANGED
|
@@ -108,9 +108,14 @@ export function decodeQueryString(paramStr) {
|
|
|
108
108
|
parent[keyParent] = obj;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
// Create the index for this key
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
// Create the index for this key - check own properties only, and define "__proto__"
|
|
112
|
+
// directly since assignment would invoke the prototype setter
|
|
113
|
+
if (!Object.hasOwn(obj, key)) {
|
|
114
|
+
if (key === '__proto__') {
|
|
115
|
+
Object.defineProperty(obj, key, {'value': null, 'enumerable': true, 'writable': true, 'configurable': true});
|
|
116
|
+
} else {
|
|
117
|
+
obj[key] = null;
|
|
118
|
+
}
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
|
package/lib/parser.js
CHANGED
|
@@ -49,6 +49,7 @@ const rValue = new RegExp(`^\\s+(?<id>${rPartId})\\s*$`);
|
|
|
49
49
|
const rValueQuoted = /^\s+"(?<id>.*?)"\s*$/;
|
|
50
50
|
const rURL = /^\s+(?<method>[A-Za-z]+|\*)(?:\s+(?<path>\/\S*))?/;
|
|
51
51
|
const rLineSplit = /\r?\n/;
|
|
52
|
+
const rIndent = /^\s/;
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
/**
|
|
@@ -107,7 +108,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
107
108
|
linenum += 1;
|
|
108
109
|
|
|
109
110
|
// Line continuation?
|
|
110
|
-
const linePartNoContinuation = linePart.replace(rLineCont, '');
|
|
111
|
+
const linePartNoContinuation = linePart.includes('\\') ? linePart.replace(rLineCont, '') : linePart;
|
|
111
112
|
if (lineContinuation.length || linePartNoContinuation !== linePart) {
|
|
112
113
|
lineContinuation.push(linePartNoContinuation);
|
|
113
114
|
}
|
|
@@ -122,47 +123,49 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
122
123
|
line = linePart;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
// Match syntax
|
|
126
|
+
// Match syntax - each regex is guarded by a cheap string test that only skips it when it cannot match
|
|
127
|
+
const lineIndented = rIndent.test(line);
|
|
128
|
+
const lineTrimmed = line.trim();
|
|
126
129
|
let matchName = 'comment';
|
|
127
|
-
let match = line.match(rComment);
|
|
130
|
+
let match = (lineTrimmed === '' || lineTrimmed.startsWith('#')) ? line.match(rComment) : null;
|
|
128
131
|
if (match === null) {
|
|
129
132
|
matchName = 'group';
|
|
130
|
-
match = line.match(rGroup);
|
|
133
|
+
match = line.startsWith('group') ? line.match(rGroup) : null;
|
|
131
134
|
}
|
|
132
135
|
if (match === null) {
|
|
133
136
|
matchName = 'action';
|
|
134
|
-
match = line.match(rAction);
|
|
137
|
+
match = line.startsWith('action') ? line.match(rAction) : null;
|
|
135
138
|
}
|
|
136
139
|
if (match === null) {
|
|
137
140
|
matchName = 'definition';
|
|
138
|
-
match = line.match(rDefinition);
|
|
141
|
+
match = (line.startsWith('struct') || line.startsWith('union') || line.startsWith('enum')) ? line.match(rDefinition) : null;
|
|
139
142
|
}
|
|
140
|
-
if (match === null && action !== null) {
|
|
143
|
+
if (match === null && action !== null && lineIndented) {
|
|
141
144
|
matchName = 'section';
|
|
142
145
|
match = line.match(rSection);
|
|
143
146
|
}
|
|
144
|
-
if (match === null && action !== null) {
|
|
147
|
+
if (match === null && action !== null && lineIndented) {
|
|
145
148
|
matchName = 'sectionPlain';
|
|
146
149
|
match = line.match(rSectionPlain);
|
|
147
150
|
}
|
|
148
|
-
if (match === null && userType !== null && 'enum' in userType) {
|
|
151
|
+
if (match === null && userType !== null && 'enum' in userType && lineIndented) {
|
|
149
152
|
matchName = 'value';
|
|
150
153
|
match = line.match(rValue);
|
|
151
154
|
if (match === null) {
|
|
152
155
|
match = line.match(rValueQuoted);
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
|
-
if (match === null && userType !== null && 'struct' in userType) {
|
|
158
|
+
if (match === null && userType !== null && 'struct' in userType && lineIndented) {
|
|
156
159
|
matchName = 'member';
|
|
157
160
|
match = line.match(rMember);
|
|
158
161
|
}
|
|
159
|
-
if (match === null && urls !== null) {
|
|
162
|
+
if (match === null && urls !== null && lineIndented) {
|
|
160
163
|
matchName = 'urls';
|
|
161
164
|
match = line.match(rURL);
|
|
162
165
|
}
|
|
163
166
|
if (match === null) {
|
|
164
167
|
matchName = 'typedef';
|
|
165
|
-
match = line.match(rTypedef);
|
|
168
|
+
match = line.startsWith('typedef') ? line.match(rTypedef) : null;
|
|
166
169
|
}
|
|
167
170
|
if (match === null) {
|
|
168
171
|
matchName = null;
|
|
@@ -189,7 +192,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
189
192
|
const actionId = match.groups.id;
|
|
190
193
|
|
|
191
194
|
// Action already defined?
|
|
192
|
-
if (actionId
|
|
195
|
+
if (Object.hasOwn(types, actionId)) {
|
|
193
196
|
addError(`Redefinition of action '${actionId}'`, filename, linenum);
|
|
194
197
|
}
|
|
195
198
|
|
|
@@ -215,7 +218,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
215
218
|
const definitionBaseIds = match.groups.baseIds;
|
|
216
219
|
|
|
217
220
|
// Type already defined?
|
|
218
|
-
if (builtinTypes.has(definitionId) ||
|
|
221
|
+
if (builtinTypes.has(definitionId) || Object.hasOwn(builtinDeprecated, definitionId) || Object.hasOwn(types, definitionId)) {
|
|
219
222
|
addError(`Redefinition of type '${definitionId}'`, filename, linenum);
|
|
220
223
|
}
|
|
221
224
|
|
|
@@ -394,7 +397,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
394
397
|
const definitionId = match.groups.id;
|
|
395
398
|
|
|
396
399
|
// Type already defined?
|
|
397
|
-
if (builtinTypes.has(definitionId) ||
|
|
400
|
+
if (builtinTypes.has(definitionId) || Object.hasOwn(builtinDeprecated, definitionId) || Object.hasOwn(types, definitionId)) {
|
|
398
401
|
addError(`Redefinition of type '${definitionId}'`, filename, linenum);
|
|
399
402
|
}
|
|
400
403
|
|
|
@@ -449,7 +452,7 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
449
452
|
errorLinenum = filepos[`${typeName}.${memberName}`] ?? null;
|
|
450
453
|
}
|
|
451
454
|
if (errorLinenum === null) {
|
|
452
|
-
errorLinenum = filepos[typeName]
|
|
455
|
+
errorLinenum = Object.hasOwn(filepos, typeName) ? filepos[typeName] : null;
|
|
453
456
|
}
|
|
454
457
|
if (errorLinenum === null) {
|
|
455
458
|
errorFilename = '';
|
|
@@ -527,7 +530,7 @@ function parseTypedef(matchTypedef) {
|
|
|
527
530
|
|
|
528
531
|
// Helper function to create a type model
|
|
529
532
|
function createType(typeName) {
|
|
530
|
-
if (typeName
|
|
533
|
+
if (Object.hasOwn(builtinDeprecated, typeName)) {
|
|
531
534
|
return {'builtin': builtinDeprecated[typeName]};
|
|
532
535
|
}
|
|
533
536
|
if (builtinTypes.has(typeName)) {
|
package/lib/schema.js
CHANGED
|
@@ -39,9 +39,9 @@ function getReferencedTypesHelper(types, type, referencedTypes) {
|
|
|
39
39
|
const typeName = type.user;
|
|
40
40
|
|
|
41
41
|
// Already encountered?
|
|
42
|
-
if (!(typeName
|
|
43
|
-
const userType = types
|
|
44
|
-
referencedTypes
|
|
42
|
+
if (!Object.hasOwn(referencedTypes, typeName)) {
|
|
43
|
+
const userType = getUserType(types, typeName);
|
|
44
|
+
setValue(referencedTypes, typeName, userType);
|
|
45
45
|
|
|
46
46
|
// Struct?
|
|
47
47
|
if ('struct' in userType) {
|
|
@@ -116,6 +116,27 @@ export class ValidationError extends Error {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
// Helper to look up a user type by name - throws ValidationError for unknown type names
|
|
120
|
+
function getUserType(types, typeName) {
|
|
121
|
+
const userType = Object.hasOwn(types, typeName) ? types[typeName] : null;
|
|
122
|
+
if (userType === null) {
|
|
123
|
+
throw new ValidationError(`Unknown type "${typeName}"`);
|
|
124
|
+
}
|
|
125
|
+
return userType;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// Helper to set an own property on a plain-object copy - the "__proto__" key is defined directly
|
|
130
|
+
// since assignment would invoke the inherited prototype setter instead of creating an own property
|
|
131
|
+
function setValue(obj, key, value) {
|
|
132
|
+
if (key === '__proto__') {
|
|
133
|
+
Object.defineProperty(obj, key, {'value': value, 'enumerable': true, 'writable': true, 'configurable': true});
|
|
134
|
+
} else {
|
|
135
|
+
obj[key] = value;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
119
140
|
/**
|
|
120
141
|
* Type-validate a value using a user type model. Container values are duplicated since some member types are
|
|
121
142
|
* transformed during validation.
|
|
@@ -134,9 +155,6 @@ export class ValidationError extends Error {
|
|
|
134
155
|
* @throws [ValidationError]{@link module:lib/schema.ValidationError}
|
|
135
156
|
*/
|
|
136
157
|
export function validateType(types, typeName, value, memberFqn = null) {
|
|
137
|
-
if (!(typeName in types)) {
|
|
138
|
-
throw new ValidationError(`Unknown type "${typeName}"`);
|
|
139
|
-
}
|
|
140
158
|
return validateTypeHelper(types, {'user': typeName}, value, memberFqn);
|
|
141
159
|
}
|
|
142
160
|
|
|
@@ -147,6 +165,10 @@ const rDatetime = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T\d{2}:\d{2}(?::
|
|
|
147
165
|
const rUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
148
166
|
|
|
149
167
|
|
|
168
|
+
// Default dict key type used by validateTypeHelper
|
|
169
|
+
const defaultDictKeyType = {'builtin': 'string'};
|
|
170
|
+
|
|
171
|
+
|
|
150
172
|
function validateTypeHelper(types, type, value, memberFqn) {
|
|
151
173
|
let valueNew = value;
|
|
152
174
|
|
|
@@ -161,15 +183,6 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
161
183
|
throwMemberError(type, value, memberFqn);
|
|
162
184
|
}
|
|
163
185
|
|
|
164
|
-
// uuid?
|
|
165
|
-
} else if (builtin === 'uuid') {
|
|
166
|
-
if (typeof value !== 'string') {
|
|
167
|
-
throwMemberError(type, value, memberFqn);
|
|
168
|
-
}
|
|
169
|
-
if (!rUuid.test(value)) {
|
|
170
|
-
throwMemberError(type, value, memberFqn);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
186
|
// int or float?
|
|
174
187
|
} else if (builtin === 'int' || builtin === 'float') {
|
|
175
188
|
// Convert string?
|
|
@@ -253,6 +266,15 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
253
266
|
} else if (!(value instanceof Date)) {
|
|
254
267
|
throwMemberError(type, value, memberFqn);
|
|
255
268
|
}
|
|
269
|
+
|
|
270
|
+
// uuid?
|
|
271
|
+
} else if (builtin === 'uuid') {
|
|
272
|
+
if (typeof value !== 'string') {
|
|
273
|
+
throwMemberError(type, value, memberFqn);
|
|
274
|
+
}
|
|
275
|
+
if (!rUuid.test(value)) {
|
|
276
|
+
throwMemberError(type, value, memberFqn);
|
|
277
|
+
}
|
|
256
278
|
}
|
|
257
279
|
|
|
258
280
|
// array?
|
|
@@ -271,13 +293,15 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
271
293
|
const valueCopy = [];
|
|
272
294
|
const arrayValueNullable = arrayAttr !== null && 'nullable' in arrayAttr && arrayAttr.nullable;
|
|
273
295
|
for (let ixArrayValue = 0; ixArrayValue < valueNew.length; ixArrayValue++) {
|
|
274
|
-
const memberFqnValue = memberFqn
|
|
296
|
+
const memberFqnValue = [memberFqn, ixArrayValue];
|
|
275
297
|
let arrayValue = valueNew[ixArrayValue];
|
|
276
298
|
if (arrayValueNullable && (arrayValue === null || arrayValue === 'null')) {
|
|
277
299
|
arrayValue = null;
|
|
278
300
|
} else {
|
|
279
301
|
arrayValue = validateTypeHelper(types, arrayType, arrayValue, memberFqnValue);
|
|
280
|
-
|
|
302
|
+
if (arrayAttr !== null) {
|
|
303
|
+
validateAttr(arrayType, arrayAttr, arrayValue, memberFqnValue);
|
|
304
|
+
}
|
|
281
305
|
}
|
|
282
306
|
valueCopy.push(arrayValue);
|
|
283
307
|
}
|
|
@@ -291,7 +315,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
291
315
|
const {dict} = type;
|
|
292
316
|
const dictType = dict.type;
|
|
293
317
|
const dictAttr = 'attr' in dict ? dict.attr : null;
|
|
294
|
-
const dictKeyType = 'keyType' in dict ? dict.keyType :
|
|
318
|
+
const dictKeyType = 'keyType' in dict ? dict.keyType : defaultDictKeyType;
|
|
295
319
|
const dictKeyAttr = 'keyAttr' in dict ? dict.keyAttr : null;
|
|
296
320
|
if (value === '') {
|
|
297
321
|
valueNew = {};
|
|
@@ -300,18 +324,21 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
300
324
|
}
|
|
301
325
|
|
|
302
326
|
// Validate the dict key/value pairs
|
|
303
|
-
const
|
|
327
|
+
const isMap = valueNew instanceof Map;
|
|
328
|
+
const valueCopy = isMap ? new Map() : {};
|
|
304
329
|
const dictKeyNullable = dictKeyAttr !== null && 'nullable' in dictKeyAttr && dictKeyAttr.nullable;
|
|
305
330
|
const dictValueNullable = dictAttr !== null && 'nullable' in dictAttr && dictAttr.nullable;
|
|
306
|
-
for (let [dictKey, dictValue] of (
|
|
307
|
-
const memberFqnKey = memberFqn
|
|
331
|
+
for (let [dictKey, dictValue] of (isMap ? valueNew.entries() : Object.entries(valueNew))) {
|
|
332
|
+
const memberFqnKey = [memberFqn, dictKey];
|
|
308
333
|
|
|
309
334
|
// Validate the key
|
|
310
335
|
if (dictKeyNullable && (dictKey === null || dictKey === 'null')) {
|
|
311
336
|
dictKey = null;
|
|
312
337
|
} else {
|
|
313
338
|
dictKey = validateTypeHelper(types, dictKeyType, dictKey, memberFqn);
|
|
314
|
-
|
|
339
|
+
if (dictKeyAttr !== null) {
|
|
340
|
+
validateAttr(dictKeyType, dictKeyAttr, dictKey, memberFqn);
|
|
341
|
+
}
|
|
315
342
|
}
|
|
316
343
|
|
|
317
344
|
// Validate the value
|
|
@@ -319,14 +346,16 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
319
346
|
dictValue = null;
|
|
320
347
|
} else {
|
|
321
348
|
dictValue = validateTypeHelper(types, dictType, dictValue, memberFqnKey);
|
|
322
|
-
|
|
349
|
+
if (dictAttr !== null) {
|
|
350
|
+
validateAttr(dictType, dictAttr, dictValue, memberFqnKey);
|
|
351
|
+
}
|
|
323
352
|
}
|
|
324
353
|
|
|
325
354
|
// Copy the key/value
|
|
326
|
-
if (
|
|
355
|
+
if (isMap) {
|
|
327
356
|
valueCopy.set(dictKey, dictValue);
|
|
328
357
|
} else {
|
|
329
|
-
valueCopy
|
|
358
|
+
setValue(valueCopy, dictKey, dictValue);
|
|
330
359
|
}
|
|
331
360
|
}
|
|
332
361
|
|
|
@@ -335,7 +364,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
335
364
|
|
|
336
365
|
// User type?
|
|
337
366
|
} else if ('user' in type) {
|
|
338
|
-
const userType = types
|
|
367
|
+
const userType = getUserType(types, type.user);
|
|
339
368
|
|
|
340
369
|
// action?
|
|
341
370
|
if ('action' in userType) {
|
|
@@ -353,7 +382,9 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
353
382
|
valueNew = null;
|
|
354
383
|
} else {
|
|
355
384
|
valueNew = validateTypeHelper(types, typedef.type, value, memberFqn);
|
|
356
|
-
|
|
385
|
+
if (typedefAttr !== null) {
|
|
386
|
+
validateAttr(type, typedefAttr, valueNew, memberFqn);
|
|
387
|
+
}
|
|
357
388
|
}
|
|
358
389
|
|
|
359
390
|
// enum?
|
|
@@ -377,52 +408,58 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
377
408
|
}
|
|
378
409
|
|
|
379
410
|
// Valid union?
|
|
411
|
+
const isMap = valueNew instanceof Map;
|
|
380
412
|
const isUnion = 'union' in struct ? struct.union : false;
|
|
381
413
|
if (isUnion) {
|
|
382
|
-
if (Object.keys(
|
|
414
|
+
if ((isMap ? valueNew.size : Object.keys(valueNew).length) !== 1) {
|
|
383
415
|
throwMemberError({'user': struct.name}, value, memberFqn);
|
|
384
416
|
}
|
|
385
417
|
}
|
|
386
418
|
|
|
387
419
|
// Validate the struct members
|
|
388
|
-
const valueCopy =
|
|
420
|
+
const valueCopy = isMap ? new Map() : {};
|
|
389
421
|
for (const member of getStructMembers(types, struct)) {
|
|
390
422
|
const memberName = member.name;
|
|
391
|
-
const memberFqnMember = memberFqn
|
|
423
|
+
const memberFqnMember = [memberFqn, memberName];
|
|
392
424
|
const memberOptional = 'optional' in member && member.optional;
|
|
393
|
-
const
|
|
425
|
+
const memberAttr = 'attr' in member ? member.attr : null;
|
|
426
|
+
const memberNullable = memberAttr !== null && 'nullable' in memberAttr && memberAttr.nullable;
|
|
394
427
|
|
|
395
|
-
// Missing non-optional member?
|
|
396
|
-
|
|
428
|
+
// Missing non-optional member? Check own properties only - "in" matches inherited
|
|
429
|
+
// Object.prototype properties for member names like "constructor"
|
|
430
|
+
if (!(isMap ? valueNew.has(memberName) : Object.hasOwn(valueNew, memberName))) {
|
|
397
431
|
if (!memberOptional && !isUnion) {
|
|
398
|
-
throw new ValidationError(`Required member "${memberFqnMember}" missing`);
|
|
432
|
+
throw new ValidationError(`Required member "${memberFqnStr(memberFqnMember)}" missing`);
|
|
399
433
|
}
|
|
400
434
|
} else {
|
|
401
435
|
// Validate the member value
|
|
402
|
-
let memberValue =
|
|
436
|
+
let memberValue = isMap ? valueNew.get(memberName) : valueNew[memberName];
|
|
403
437
|
if (memberNullable && (memberValue === null || memberValue === 'null')) {
|
|
404
438
|
memberValue = null;
|
|
405
439
|
} else {
|
|
406
440
|
memberValue = validateTypeHelper(types, member.type, memberValue, memberFqnMember);
|
|
407
|
-
|
|
441
|
+
if (memberAttr !== null) {
|
|
442
|
+
validateAttr(member.type, memberAttr, memberValue, memberFqnMember);
|
|
443
|
+
}
|
|
408
444
|
}
|
|
409
445
|
|
|
410
446
|
// Copy the validated member
|
|
411
|
-
if (
|
|
447
|
+
if (isMap) {
|
|
412
448
|
valueCopy.set(memberName, memberValue);
|
|
413
449
|
} else {
|
|
414
|
-
valueCopy
|
|
450
|
+
setValue(valueCopy, memberName, memberValue);
|
|
415
451
|
}
|
|
416
452
|
}
|
|
417
453
|
}
|
|
418
454
|
|
|
419
455
|
// Any unknown members?
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
if (
|
|
456
|
+
const valueCopyCount = isMap ? valueCopy.size : Object.keys(valueCopy).length;
|
|
457
|
+
const valueNewCount = isMap ? valueNew.size : Object.keys(valueNew).length;
|
|
458
|
+
if (valueCopyCount !== valueNewCount) {
|
|
423
459
|
const memberSet = new Set(getStructMembers(types, struct).map((member) => member.name));
|
|
460
|
+
const valueNewKeys = isMap ? Array.from(valueNew.keys()) : Object.keys(valueNew);
|
|
424
461
|
const [unknownKey] = valueNewKeys.filter((key) => !memberSet.has(key));
|
|
425
|
-
const unknownFqn = memberFqn
|
|
462
|
+
const unknownFqn = memberFqnStr([memberFqn, unknownKey]);
|
|
426
463
|
throw new ValidationError(`Unknown member "${unknownFqn.slice(0, 100)}"`);
|
|
427
464
|
}
|
|
428
465
|
|
|
@@ -435,36 +472,56 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
435
472
|
}
|
|
436
473
|
|
|
437
474
|
|
|
475
|
+
// Helper to materialize a member's fully-qualified name string. For performance, member FQNs are
|
|
476
|
+
// built lazily as [parent, key] pairs and only converted to strings on error.
|
|
477
|
+
function memberFqnStr(memberFqn) {
|
|
478
|
+
if (memberFqn === null || typeof memberFqn === 'string') {
|
|
479
|
+
return memberFqn;
|
|
480
|
+
}
|
|
481
|
+
const parts = [];
|
|
482
|
+
let fqnPart = memberFqn;
|
|
483
|
+
while (Array.isArray(fqnPart)) {
|
|
484
|
+
parts.push(`${fqnPart[1]}`);
|
|
485
|
+
[fqnPart] = fqnPart;
|
|
486
|
+
}
|
|
487
|
+
if (fqnPart !== null) {
|
|
488
|
+
parts.push(`${fqnPart}`);
|
|
489
|
+
}
|
|
490
|
+
parts.reverse();
|
|
491
|
+
return parts.join('.');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
|
|
438
495
|
function throwMemberError(type, value, memberFqn, attr = null) {
|
|
439
|
-
const
|
|
496
|
+
const memberFqnValue = memberFqnStr(memberFqn);
|
|
497
|
+
const memberPart = memberFqnValue ? ` for member "${memberFqnValue}"` : '';
|
|
440
498
|
const typeName = 'builtin' in type ? type.builtin : ('array' in type ? 'array' : ('dict' in type ? 'dict' : type.user));
|
|
441
499
|
const attrPart = attr !== null ? ` [${attr}]` : '';
|
|
442
500
|
const valueStr = `${JSON.stringify(value)}`;
|
|
443
501
|
const msg = `Invalid value ${valueStr.slice(0, 100)} (type "${typeof value}")${memberPart}, expected type "${typeName}"${attrPart}`;
|
|
444
|
-
throw new ValidationError(msg,
|
|
502
|
+
throw new ValidationError(msg, memberFqnValue);
|
|
445
503
|
}
|
|
446
504
|
|
|
447
505
|
|
|
448
506
|
function validateAttr(type, attr, value, memberFqn) {
|
|
449
|
-
if (attr
|
|
507
|
+
if ('eq' in attr && !(value === attr.eq)) {
|
|
508
|
+
throwMemberError(type, value, memberFqn, `== ${attr.eq}`);
|
|
509
|
+
}
|
|
510
|
+
if ('lt' in attr && !(value < attr.lt)) {
|
|
511
|
+
throwMemberError(type, value, memberFqn, `< ${attr.lt}`);
|
|
512
|
+
}
|
|
513
|
+
if ('lte' in attr && !(value <= attr.lte)) {
|
|
514
|
+
throwMemberError(type, value, memberFqn, `<= ${attr.lte}`);
|
|
515
|
+
}
|
|
516
|
+
if ('gt' in attr && !(value > attr.gt)) {
|
|
517
|
+
throwMemberError(type, value, memberFqn, `> ${attr.gt}`);
|
|
518
|
+
}
|
|
519
|
+
if ('gte' in attr && !(value >= attr.gte)) {
|
|
520
|
+
throwMemberError(type, value, memberFqn, `>= ${attr.gte}`);
|
|
521
|
+
}
|
|
522
|
+
if ('lenEq' in attr || 'lenLT' in attr || 'lenLTE' in attr || 'lenGT' in attr || 'lenGTE' in attr) {
|
|
450
523
|
const length = Array.isArray(value) || typeof value === 'string' ? value.length
|
|
451
|
-
: (typeof value === 'object' ? Object.keys(value).length : null);
|
|
452
|
-
|
|
453
|
-
if ('eq' in attr && !(value === attr.eq)) {
|
|
454
|
-
throwMemberError(type, value, memberFqn, `== ${attr.eq}`);
|
|
455
|
-
}
|
|
456
|
-
if ('lt' in attr && !(value < attr.lt)) {
|
|
457
|
-
throwMemberError(type, value, memberFqn, `< ${attr.lt}`);
|
|
458
|
-
}
|
|
459
|
-
if ('lte' in attr && !(value <= attr.lte)) {
|
|
460
|
-
throwMemberError(type, value, memberFqn, `<= ${attr.lte}`);
|
|
461
|
-
}
|
|
462
|
-
if ('gt' in attr && !(value > attr.gt)) {
|
|
463
|
-
throwMemberError(type, value, memberFqn, `> ${attr.gt}`);
|
|
464
|
-
}
|
|
465
|
-
if ('gte' in attr && !(value >= attr.gte)) {
|
|
466
|
-
throwMemberError(type, value, memberFqn, `>= ${attr.gte}`);
|
|
467
|
-
}
|
|
524
|
+
: (value instanceof Map ? value.size : (typeof value === 'object' ? Object.keys(value).length : null));
|
|
468
525
|
if ('lenEq' in attr && !(length === attr.lenEq)) {
|
|
469
526
|
throwMemberError(type, value, memberFqn, `len == ${attr.lenEq}`);
|
|
470
527
|
}
|
|
@@ -501,9 +558,9 @@ export function getStructMembers(types, struct) {
|
|
|
501
558
|
// Get base struct members
|
|
502
559
|
const members = [];
|
|
503
560
|
for (const base of struct.bases) {
|
|
504
|
-
let baseUserType = types
|
|
561
|
+
let baseUserType = getUserType(types, base);
|
|
505
562
|
while ('typedef' in baseUserType) {
|
|
506
|
-
baseUserType = types
|
|
563
|
+
baseUserType = getUserType(types, baseUserType.typedef.type.user);
|
|
507
564
|
}
|
|
508
565
|
members.push(...getStructMembers(types, baseUserType.struct));
|
|
509
566
|
}
|
|
@@ -534,9 +591,9 @@ export function getEnumValues(types, enum_) {
|
|
|
534
591
|
// Get base enum values
|
|
535
592
|
const values = [];
|
|
536
593
|
for (const base of enum_.bases) {
|
|
537
|
-
let baseUserType = types
|
|
594
|
+
let baseUserType = getUserType(types, base);
|
|
538
595
|
while ('typedef' in baseUserType) {
|
|
539
|
-
baseUserType = types
|
|
596
|
+
baseUserType = getUserType(types, baseUserType.typedef.type.user);
|
|
540
597
|
}
|
|
541
598
|
values.push(...getEnumValues(types, baseUserType.enum));
|
|
542
599
|
}
|
package/lib/schemaUtil.js
CHANGED
|
@@ -43,13 +43,13 @@ export function validateTypeModelErrors(types) {
|
|
|
43
43
|
|
|
44
44
|
// Iterate the members
|
|
45
45
|
try {
|
|
46
|
-
const members =
|
|
46
|
+
const members = new Set();
|
|
47
47
|
for (const member of getStructMembers(types, struct)) {
|
|
48
48
|
const memberName = member.name;
|
|
49
49
|
|
|
50
50
|
// Duplicate member?
|
|
51
|
-
if (!(memberName
|
|
52
|
-
members
|
|
51
|
+
if (!members.has(memberName)) {
|
|
52
|
+
members.add(memberName);
|
|
53
53
|
} else {
|
|
54
54
|
errors.push([typeName, memberName, `Redefinition of "${typeName}" member "${memberName}"`]);
|
|
55
55
|
}
|
|
@@ -82,13 +82,13 @@ export function validateTypeModelErrors(types) {
|
|
|
82
82
|
|
|
83
83
|
// Get the enumeration values
|
|
84
84
|
try {
|
|
85
|
-
const values =
|
|
85
|
+
const values = new Set();
|
|
86
86
|
for (const value of getEnumValues(types, enum_)) {
|
|
87
87
|
const valueName = value.name;
|
|
88
88
|
|
|
89
89
|
// Duplicate value?
|
|
90
|
-
if (!(valueName
|
|
91
|
-
values
|
|
90
|
+
if (!values.has(valueName)) {
|
|
91
|
+
values.add(valueName);
|
|
92
92
|
} else {
|
|
93
93
|
errors.push([typeName, valueName, `Redefinition of "${typeName}" value "${valueName}"`]);
|
|
94
94
|
}
|
|
@@ -129,11 +129,11 @@ export function validateTypeModelErrors(types) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// Compute effective input member counts
|
|
132
|
-
const memberSections =
|
|
132
|
+
const memberSections = new Map();
|
|
133
133
|
for (const section of ['path', 'query', 'input']) {
|
|
134
134
|
if (section in action) {
|
|
135
135
|
const sectionTypeName = action[section];
|
|
136
|
-
if (sectionTypeName
|
|
136
|
+
if (Object.hasOwn(types, sectionTypeName)) {
|
|
137
137
|
const sectionUserType = getEffectiveUserType(types, sectionTypeName);
|
|
138
138
|
if (sectionUserType !== null && 'struct' in sectionUserType) {
|
|
139
139
|
const sectionStruct = sectionUserType.struct;
|
|
@@ -142,10 +142,10 @@ export function validateTypeModelErrors(types) {
|
|
|
142
142
|
try {
|
|
143
143
|
for (const member of getStructMembers(types, sectionStruct)) {
|
|
144
144
|
const memberName = member.name;
|
|
145
|
-
if (!(memberName
|
|
146
|
-
memberSections
|
|
145
|
+
if (!memberSections.has(memberName)) {
|
|
146
|
+
memberSections.set(memberName, []);
|
|
147
147
|
}
|
|
148
|
-
memberSections
|
|
148
|
+
memberSections.get(memberName).push(sectionStruct.name);
|
|
149
149
|
}
|
|
150
150
|
} catch {
|
|
151
151
|
// Do nothing
|
|
@@ -156,7 +156,7 @@ export function validateTypeModelErrors(types) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// Check for duplicate input members
|
|
159
|
-
for (const [memberName, memberSectionNames] of
|
|
159
|
+
for (const [memberName, memberSectionNames] of memberSections.entries()) {
|
|
160
160
|
if (memberSectionNames.length > 1) {
|
|
161
161
|
for (const sectionType of memberSectionNames.sort()) {
|
|
162
162
|
errors.push([sectionType, memberName, `Redefinition of "${sectionType}" member "${memberName}"`]);
|
|
@@ -171,7 +171,7 @@ export function validateTypeModelErrors(types) {
|
|
|
171
171
|
|
|
172
172
|
|
|
173
173
|
function getEffectiveType(types, type) {
|
|
174
|
-
if ('user' in type && type.user
|
|
174
|
+
if ('user' in type && Object.hasOwn(types, type.user)) {
|
|
175
175
|
const userType = types[type.user];
|
|
176
176
|
if ('typedef' in userType) {
|
|
177
177
|
return getEffectiveType(types, userType.typedef.type);
|
|
@@ -182,24 +182,24 @@ function getEffectiveType(types, type) {
|
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
function getEffectiveUserType(types, userTypeName) {
|
|
185
|
-
const userType = userTypeName
|
|
185
|
+
const userType = Object.hasOwn(types, userTypeName) ? types[userTypeName] : null;
|
|
186
186
|
if (userType !== null && 'typedef' in userType) {
|
|
187
187
|
const typeEffective = getEffectiveType(types, userType.typedef.type);
|
|
188
188
|
if (!('user' in typeEffective)) {
|
|
189
189
|
return null;
|
|
190
190
|
}
|
|
191
|
-
return typeEffective.user
|
|
191
|
+
return Object.hasOwn(types, typeEffective.user) ? types[typeEffective.user] : null;
|
|
192
192
|
}
|
|
193
193
|
return userType;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
|
|
197
|
-
function getStructMembers(types, struct, visited =
|
|
197
|
+
function getStructMembers(types, struct, visited = new Set()) {
|
|
198
198
|
return getTypeItems(types, struct, visited, 'struct', 'members');
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
|
|
202
|
-
function getEnumValues(types, enum_, visited =
|
|
202
|
+
function getEnumValues(types, enum_, visited = new Set()) {
|
|
203
203
|
return getTypeItems(types, enum_, visited, 'enum', 'values');
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -211,8 +211,8 @@ function getTypeItems(types, type, visited, defName, memberName) {
|
|
|
211
211
|
const userType = getEffectiveUserType(types, base);
|
|
212
212
|
if (userType !== null && defName in userType) {
|
|
213
213
|
const userTypeName = userType[defName].name;
|
|
214
|
-
if (!(userTypeName
|
|
215
|
-
visited
|
|
214
|
+
if (!visited.has(userTypeName)) {
|
|
215
|
+
visited.add(userTypeName);
|
|
216
216
|
items.push(...getTypeItems(types, userType[defName], visited, defName, memberName));
|
|
217
217
|
} else {
|
|
218
218
|
throw new TypeError();
|
|
@@ -285,7 +285,7 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
|
|
|
285
285
|
|
|
286
286
|
// Valid dict key type (string or enum)
|
|
287
287
|
if (!('builtin' in dictKeyType && dictKeyType.builtin === 'string') &&
|
|
288
|
-
!('user' in dictKeyType && dictKeyType.user
|
|
288
|
+
!('user' in dictKeyType && Object.hasOwn(types, dictKeyType.user) && 'enum' in types[dictKeyType.user])) {
|
|
289
289
|
error('Invalid dictionary key type');
|
|
290
290
|
}
|
|
291
291
|
}
|
|
@@ -295,7 +295,7 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
|
|
|
295
295
|
const userTypeName = type.user;
|
|
296
296
|
|
|
297
297
|
// Unknown user type?
|
|
298
|
-
if (!(userTypeName
|
|
298
|
+
if (!Object.hasOwn(types, userTypeName)) {
|
|
299
299
|
error(`Unknown type "${userTypeName}"`);
|
|
300
300
|
} else {
|
|
301
301
|
const userType = types[userTypeName];
|
|
@@ -312,7 +312,7 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
|
|
|
312
312
|
const typeEffective = getEffectiveType(types, type);
|
|
313
313
|
const [typeKey] = Object.keys(typeEffective);
|
|
314
314
|
const typeAttrKey = typeKey === 'builtin' ? typeEffective[typeKey] : typeKey;
|
|
315
|
-
const allowedAttr = typeAttrKey
|
|
315
|
+
const allowedAttr = Object.hasOwn(typeToAllowedAttr, typeAttrKey) ? typeToAllowedAttr[typeAttrKey] : null;
|
|
316
316
|
const disallowedAttr = Object.fromEntries(Object.keys(attr).map((attrKey) => [attrKey, null]));
|
|
317
317
|
delete disallowedAttr.nullable;
|
|
318
318
|
if (allowedAttr !== null) {
|