schema-markdown 1.2.13 → 1.3.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 +8 -2
- package/lib/parser.js +16 -5
- package/lib/schema.js +71 -22
- package/lib/schemaUtil.js +16 -16
- package/lib/typeModel.js +4 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Validation fails if the object does not match the schema:
|
|
|
63
63
|
try {
|
|
64
64
|
validateType(modelTypes, 'Aggregation', {'numbers': [1, 2, 'asdf', 4]});
|
|
65
65
|
} catch ({message}) {
|
|
66
|
-
console.assert(message ===
|
|
66
|
+
console.assert(message === 'Invalid value "asdf" (type "string") for member "numbers.2", expected type "int"', message);
|
|
67
67
|
}
|
|
68
68
|
~~~
|
|
69
69
|
|
|
@@ -73,10 +73,16 @@ Validation also fails if a member constraint is violated:
|
|
|
73
73
|
try {
|
|
74
74
|
validateType(modelTypes, 'Aggregation', {'numbers': []});
|
|
75
75
|
} catch ({message}) {
|
|
76
|
-
console.assert(message ===
|
|
76
|
+
console.assert(message === 'Invalid value [] (type "object") for member "numbers", expected type "array" [len > 0]', message);
|
|
77
77
|
}
|
|
78
78
|
~~~
|
|
79
79
|
|
|
80
|
+
`validateType` validates JSON-like values. In this JavaScript implementation, date values are
|
|
81
|
+
`Date` objects (time-of-day cleared), datetime values are `Date` objects, and uuid strings remain
|
|
82
|
+
strings. A `Date` input is left as a `Date` (date values have time-of-day cleared). See
|
|
83
|
+
[The Schema Markdown Language - Built-in Types](https://craigahobbs.github.io/schema-markdown-js/language/#built-in-types)
|
|
84
|
+
for accepted JSON input formats and host-runtime behavior.
|
|
85
|
+
|
|
80
86
|
|
|
81
87
|
## Schema Documentation
|
|
82
88
|
|
package/lib/parser.js
CHANGED
|
@@ -24,7 +24,7 @@ const rFindAttrs = new RegExp(`${rPartAttr}(?:\\s*,\\s*|\\s*$)`, 'g');
|
|
|
24
24
|
const rLineCont = /\\\s*$/;
|
|
25
25
|
const rComment = /^\s*(?:#-.*|#(?<doc>.*))?$/;
|
|
26
26
|
const rGroup = /^group(?:\s+"(?<group>.+?)")?\s*$/;
|
|
27
|
-
const rAction = new RegExp(`^action\\s+(?<id>${rPartId})
|
|
27
|
+
const rAction = new RegExp(`^action\\s+(?<id>${rPartId})\\s*$`);
|
|
28
28
|
const rPartBaseIds = `(?:\\s*\\(\\s*(?<baseIds>${rPartId}(?:\\s*,\\s*${rPartId})*)\\s*\\)\\s*)`;
|
|
29
29
|
const rBaseIdsSplit = /\s*,\s*/;
|
|
30
30
|
const rDefinition = new RegExp(`^(?<type>struct|union|enum)\\s+(?<id>${rPartId})${rPartBaseIds}?\\s*$`);
|
|
@@ -311,8 +311,12 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
311
311
|
// Clear parser state
|
|
312
312
|
userType = null;
|
|
313
313
|
|
|
314
|
-
// Update the parser state
|
|
314
|
+
// Update the parser state - attach urls immediately so a second empty
|
|
315
|
+
// "urls" section is detected as a redefinition (even with no URL lines)
|
|
315
316
|
urls = [];
|
|
317
|
+
if (!(sectionString in action)) {
|
|
318
|
+
action[sectionString] = urls;
|
|
319
|
+
}
|
|
316
320
|
|
|
317
321
|
// Enum value?
|
|
318
322
|
} else if (matchName === 'value') {
|
|
@@ -383,9 +387,6 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
383
387
|
}
|
|
384
388
|
|
|
385
389
|
// Add the URL
|
|
386
|
-
if (!('urls' in action)) {
|
|
387
|
-
action.urls = urls;
|
|
388
|
-
}
|
|
389
390
|
urls.push(actionUrl);
|
|
390
391
|
|
|
391
392
|
// Typedef?
|
|
@@ -429,6 +430,16 @@ export function parseSchemaMarkdown(text, {types = {}, filename = '', validate =
|
|
|
429
430
|
}
|
|
430
431
|
}
|
|
431
432
|
|
|
433
|
+
// Drop empty action urls arrays (type model requires len > 0 when present)
|
|
434
|
+
for (const userTypeIter of Object.values(types)) {
|
|
435
|
+
if ('action' in userTypeIter) {
|
|
436
|
+
const actionType = userTypeIter.action;
|
|
437
|
+
if ('urls' in actionType && actionType.urls.length === 0) {
|
|
438
|
+
delete actionType.urls;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
432
443
|
// Validate the type model, if requested
|
|
433
444
|
if (validate) {
|
|
434
445
|
for (const [typeName, memberName, errorMsg] of validateTypeModelErrors(types)) {
|
package/lib/schema.js
CHANGED
|
@@ -120,6 +120,12 @@ export class ValidationError extends Error {
|
|
|
120
120
|
* Type-validate a value using a user type model. Container values are duplicated since some member types are
|
|
121
121
|
* transformed during validation.
|
|
122
122
|
*
|
|
123
|
+
* Validates JSON-like values. In this implementation, date values are `Date` objects (time-of-day
|
|
124
|
+
* cleared), datetime values are `Date` objects, and uuid strings remain strings. A `Date` input is
|
|
125
|
+
* left as a `Date` (date values have time-of-day cleared). See the language documentation on
|
|
126
|
+
* [built-in types]{@link https://craigahobbs.github.io/schema-markdown-js/language/#built-in-types}
|
|
127
|
+
* for accepted JSON input formats and host-runtime behavior.
|
|
128
|
+
*
|
|
123
129
|
* @param {Object} types - The [type model]{@link https://craigahobbs.github.io/bare-script/model/#var.vURL=''&var.vName='Types'}
|
|
124
130
|
* @param {string} typeName - The type name
|
|
125
131
|
* @param {Object} value - The value object to validate
|
|
@@ -129,7 +135,7 @@ export class ValidationError extends Error {
|
|
|
129
135
|
*/
|
|
130
136
|
export function validateType(types, typeName, value, memberFqn = null) {
|
|
131
137
|
if (!(typeName in types)) {
|
|
132
|
-
throw new ValidationError(`Unknown type
|
|
138
|
+
throw new ValidationError(`Unknown type "${typeName}"`);
|
|
133
139
|
}
|
|
134
140
|
return validateTypeHelper(types, {'user': typeName}, value, memberFqn);
|
|
135
141
|
}
|
|
@@ -137,7 +143,8 @@ export function validateType(types, typeName, value, memberFqn = null) {
|
|
|
137
143
|
|
|
138
144
|
// Regular expressions used by validateTypeHelper
|
|
139
145
|
const rDate = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/;
|
|
140
|
-
const rDatetime =
|
|
146
|
+
const rDatetime = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
147
|
+
const rUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
141
148
|
|
|
142
149
|
|
|
143
150
|
function validateTypeHelper(types, type, value, memberFqn) {
|
|
@@ -147,15 +154,19 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
147
154
|
if ('builtin' in type) {
|
|
148
155
|
const {builtin} = type;
|
|
149
156
|
|
|
150
|
-
// string
|
|
151
|
-
if (builtin === 'string'
|
|
157
|
+
// string?
|
|
158
|
+
if (builtin === 'string') {
|
|
152
159
|
// Not a string?
|
|
153
160
|
if (typeof value !== 'string') {
|
|
154
161
|
throwMemberError(type, value, memberFqn);
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
// uuid?
|
|
165
|
+
} else if (builtin === 'uuid') {
|
|
166
|
+
if (typeof value !== 'string') {
|
|
167
|
+
throwMemberError(type, value, memberFqn);
|
|
168
|
+
}
|
|
169
|
+
if (!rUuid.test(value)) {
|
|
159
170
|
throwMemberError(type, value, memberFqn);
|
|
160
171
|
}
|
|
161
172
|
|
|
@@ -195,31 +206,52 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
195
206
|
throwMemberError(type, value, memberFqn);
|
|
196
207
|
}
|
|
197
208
|
|
|
198
|
-
// date
|
|
199
|
-
} else if (builtin === 'date'
|
|
209
|
+
// date?
|
|
210
|
+
} else if (builtin === 'date') {
|
|
200
211
|
// Convert string?
|
|
201
212
|
if (typeof value === 'string') {
|
|
202
|
-
//
|
|
213
|
+
// Date-only?
|
|
203
214
|
const mDate = value.match(rDate);
|
|
204
215
|
if (mDate !== null) {
|
|
205
216
|
const year = Number.parseInt(mDate.groups.year, 10);
|
|
206
217
|
const month = Number.parseInt(mDate.groups.month, 10);
|
|
207
218
|
const day = Number.parseInt(mDate.groups.day, 10);
|
|
208
219
|
valueNew = new Date(year, month - 1, day);
|
|
209
|
-
|
|
210
|
-
valueNew
|
|
220
|
+
// new Date rolls an out-of-range month/day over (e.g. Feb 30 -> Mar 1); reject that
|
|
221
|
+
if (valueNew.getMonth() !== month - 1) {
|
|
222
|
+
throwMemberError(type, value, memberFqn);
|
|
223
|
+
}
|
|
211
224
|
} else {
|
|
212
225
|
throwMemberError(type, value, memberFqn);
|
|
213
226
|
}
|
|
214
227
|
|
|
215
|
-
// Not a date?
|
|
216
|
-
} else if (!(value instanceof Date)
|
|
228
|
+
// Not a date, or a date with a time-of-day?
|
|
229
|
+
} else if (!(value instanceof Date) ||
|
|
230
|
+
value.getHours() + value.getMinutes() + value.getSeconds() + value.getMilliseconds() !== 0) {
|
|
217
231
|
throwMemberError(type, value, memberFqn);
|
|
218
232
|
}
|
|
219
233
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
234
|
+
// datetime?
|
|
235
|
+
} else if (builtin === 'datetime') {
|
|
236
|
+
// Convert string?
|
|
237
|
+
if (typeof value === 'string') {
|
|
238
|
+
const mDatetime = value.match(rDatetime);
|
|
239
|
+
if (mDatetime !== null) {
|
|
240
|
+
valueNew = new Date(value);
|
|
241
|
+
const year = Number.parseInt(mDatetime.groups.year, 10);
|
|
242
|
+
const month = Number.parseInt(mDatetime.groups.month, 10);
|
|
243
|
+
const day = Number.parseInt(mDatetime.groups.day, 10);
|
|
244
|
+
// new Date rolls an out-of-range day over (e.g. Feb 30 -> Mar 1); reject that
|
|
245
|
+
if (Number.isNaN(valueNew.getTime()) || new Date(year, month - 1, day).getMonth() !== month - 1) {
|
|
246
|
+
throwMemberError(type, value, memberFqn);
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
throwMemberError(type, value, memberFqn);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Not a datetime?
|
|
253
|
+
} else if (!(value instanceof Date)) {
|
|
254
|
+
throwMemberError(type, value, memberFqn);
|
|
223
255
|
}
|
|
224
256
|
}
|
|
225
257
|
|
|
@@ -263,7 +295,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
263
295
|
const dictKeyAttr = 'keyAttr' in dict ? dict.keyAttr : null;
|
|
264
296
|
if (value === '') {
|
|
265
297
|
valueNew = {};
|
|
266
|
-
} else if (value === null || typeof value !== 'object') {
|
|
298
|
+
} else if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
267
299
|
throwMemberError(type, value, memberFqn);
|
|
268
300
|
}
|
|
269
301
|
|
|
@@ -340,7 +372,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
340
372
|
// Valid value type?
|
|
341
373
|
if (value === '') {
|
|
342
374
|
valueNew = {};
|
|
343
|
-
} else if (value === null || typeof value !== 'object') {
|
|
375
|
+
} else if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
344
376
|
throwMemberError({'user': struct.name}, value, memberFqn);
|
|
345
377
|
}
|
|
346
378
|
|
|
@@ -363,7 +395,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
363
395
|
// Missing non-optional member?
|
|
364
396
|
if (!(valueNew instanceof Map ? valueNew.has(memberName) : memberName in valueNew)) {
|
|
365
397
|
if (!memberOptional && !isUnion) {
|
|
366
|
-
throw new ValidationError(`Required member
|
|
398
|
+
throw new ValidationError(`Required member "${memberFqnMember}" missing`);
|
|
367
399
|
}
|
|
368
400
|
} else {
|
|
369
401
|
// Validate the member value
|
|
@@ -391,7 +423,7 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
391
423
|
const memberSet = new Set(getStructMembers(types, struct).map((member) => member.name));
|
|
392
424
|
const [unknownKey] = valueNewKeys.filter((key) => !memberSet.has(key));
|
|
393
425
|
const unknownFqn = memberFqn !== null ? `${memberFqn}.${unknownKey}` : `${unknownKey}`;
|
|
394
|
-
throw new ValidationError(`Unknown member
|
|
426
|
+
throw new ValidationError(`Unknown member "${unknownFqn.slice(0, 100)}"`);
|
|
395
427
|
}
|
|
396
428
|
|
|
397
429
|
// Return the validated, transformed copy
|
|
@@ -404,11 +436,11 @@ function validateTypeHelper(types, type, value, memberFqn) {
|
|
|
404
436
|
|
|
405
437
|
|
|
406
438
|
function throwMemberError(type, value, memberFqn, attr = null) {
|
|
407
|
-
const memberPart = memberFqn !== null ? ` for member
|
|
439
|
+
const memberPart = memberFqn !== null ? ` for member "${memberFqn}"` : '';
|
|
408
440
|
const typeName = 'builtin' in type ? type.builtin : ('array' in type ? 'array' : ('dict' in type ? 'dict' : type.user));
|
|
409
441
|
const attrPart = attr !== null ? ` [${attr}]` : '';
|
|
410
442
|
const valueStr = `${JSON.stringify(value)}`;
|
|
411
|
-
const msg = `Invalid value ${valueStr.slice(0, 100)} (type
|
|
443
|
+
const msg = `Invalid value ${valueStr.slice(0, 100)} (type "${typeof value}")${memberPart}, expected type "${typeName}"${attrPart}`;
|
|
412
444
|
throw new ValidationError(msg, memberFqn);
|
|
413
445
|
}
|
|
414
446
|
|
|
@@ -532,6 +564,23 @@ export function validateTypeModel(types) {
|
|
|
532
564
|
// Do additional type model validation
|
|
533
565
|
const errors = validateTypeModelErrors(validatedTypes);
|
|
534
566
|
if (errors.length) {
|
|
567
|
+
// Sort by (typeName, memberName, message); null memberName as ''
|
|
568
|
+
errors.sort((errorA, errorB) => {
|
|
569
|
+
let result = 0;
|
|
570
|
+
for (let ixPart = 0; ixPart < 3; ixPart++) {
|
|
571
|
+
const partA = errorA[ixPart] ?? '';
|
|
572
|
+
const partB = errorB[ixPart] ?? '';
|
|
573
|
+
if (partA < partB) {
|
|
574
|
+
result = -1;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
if (partA > partB) {
|
|
578
|
+
result = 1;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return result;
|
|
583
|
+
});
|
|
535
584
|
throw new ValidationError(errors.map(([,, message]) => message).join('\n'));
|
|
536
585
|
}
|
|
537
586
|
|
package/lib/schemaUtil.js
CHANGED
|
@@ -21,7 +21,7 @@ export function validateTypeModelErrors(types) {
|
|
|
21
21
|
|
|
22
22
|
// Inconsistent type name?
|
|
23
23
|
if (typeName !== struct.name) {
|
|
24
|
-
errors.push([typeName, null, `Inconsistent type name
|
|
24
|
+
errors.push([typeName, null, `Inconsistent type name "${struct.name}" for "${typeName}"`]);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Check base types
|
|
@@ -36,7 +36,7 @@ export function validateTypeModelErrors(types) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
if (invalidBase) {
|
|
39
|
-
errors.push([typeName, null, `Invalid struct base type
|
|
39
|
+
errors.push([typeName, null, `Invalid struct base type "${baseName}"`]);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -51,14 +51,14 @@ export function validateTypeModelErrors(types) {
|
|
|
51
51
|
if (!(memberName in members)) {
|
|
52
52
|
members[memberName] = true;
|
|
53
53
|
} else {
|
|
54
|
-
errors.push([typeName, memberName, `Redefinition of
|
|
54
|
+
errors.push([typeName, memberName, `Redefinition of "${typeName}" member "${memberName}"`]);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Check member type and attributes
|
|
58
58
|
validateTypeModelType(errors, types, member.type, 'attr' in member ? member.attr : null, struct.name, member.name);
|
|
59
59
|
}
|
|
60
60
|
} catch {
|
|
61
|
-
errors.push([typeName, null, `Circular base type detected for type
|
|
61
|
+
errors.push([typeName, null, `Circular base type detected for type "${typeName}"`]);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Enum?
|
|
@@ -67,7 +67,7 @@ export function validateTypeModelErrors(types) {
|
|
|
67
67
|
|
|
68
68
|
// Inconsistent type name?
|
|
69
69
|
if (typeName !== enum_.name) {
|
|
70
|
-
errors.push([typeName, null, `Inconsistent type name
|
|
70
|
+
errors.push([typeName, null, `Inconsistent type name "${enum_.name}" for "${typeName}"`]);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// Check base types
|
|
@@ -75,7 +75,7 @@ export function validateTypeModelErrors(types) {
|
|
|
75
75
|
for (const baseName of enum_.bases) {
|
|
76
76
|
const baseUserType = getEffectiveUserType(types, baseName);
|
|
77
77
|
if (baseUserType === null || !('enum' in baseUserType)) {
|
|
78
|
-
errors.push([typeName, null, `Invalid enum base type
|
|
78
|
+
errors.push([typeName, null, `Invalid enum base type "${baseName}"`]);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -90,11 +90,11 @@ export function validateTypeModelErrors(types) {
|
|
|
90
90
|
if (!(valueName in values)) {
|
|
91
91
|
values[valueName] = true;
|
|
92
92
|
} else {
|
|
93
|
-
errors.push([typeName, valueName, `Redefinition of
|
|
93
|
+
errors.push([typeName, valueName, `Redefinition of "${typeName}" value "${valueName}"`]);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
} catch {
|
|
97
|
-
errors.push([typeName, null, `Circular base type detected for type
|
|
97
|
+
errors.push([typeName, null, `Circular base type detected for type "${typeName}"`]);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Typedef?
|
|
@@ -103,7 +103,7 @@ export function validateTypeModelErrors(types) {
|
|
|
103
103
|
|
|
104
104
|
// Inconsistent type name?
|
|
105
105
|
if (typeName !== typedef.name) {
|
|
106
|
-
errors.push([typeName, null, `Inconsistent type name
|
|
106
|
+
errors.push([typeName, null, `Inconsistent type name "${typedef.name}" for "${typeName}"`]);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Check the type and its attributes
|
|
@@ -115,7 +115,7 @@ export function validateTypeModelErrors(types) {
|
|
|
115
115
|
|
|
116
116
|
// Inconsistent type name?
|
|
117
117
|
if (typeName !== action.name) {
|
|
118
|
-
errors.push([typeName, null, `Inconsistent type name
|
|
118
|
+
errors.push([typeName, null, `Inconsistent type name "${action.name}" for "${typeName}"`]);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Check action section types
|
|
@@ -159,7 +159,7 @@ export function validateTypeModelErrors(types) {
|
|
|
159
159
|
for (const [memberName, memberSectionNames] of Object.entries(memberSections)) {
|
|
160
160
|
if (memberSectionNames.length > 1) {
|
|
161
161
|
for (const sectionType of memberSectionNames.sort()) {
|
|
162
|
-
errors.push([sectionType, memberName, `Redefinition of
|
|
162
|
+
errors.push([sectionType, memberName, `Redefinition of "${sectionType}" member "${memberName}"`]);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
}
|
|
@@ -256,9 +256,9 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
|
|
|
256
256
|
// Helper function to push an error tuple
|
|
257
257
|
const error = (message) => {
|
|
258
258
|
if (memberName !== null) {
|
|
259
|
-
errors.push([typeName, memberName, `${message} from
|
|
259
|
+
errors.push([typeName, memberName, `${message} from "${typeName}" member "${memberName}"`]);
|
|
260
260
|
} else {
|
|
261
|
-
errors.push([typeName, null, `${message} from
|
|
261
|
+
errors.push([typeName, null, `${message} from "${typeName}"`]);
|
|
262
262
|
}
|
|
263
263
|
};
|
|
264
264
|
|
|
@@ -296,13 +296,13 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
|
|
|
296
296
|
|
|
297
297
|
// Unknown user type?
|
|
298
298
|
if (!(userTypeName in types)) {
|
|
299
|
-
error(`Unknown type
|
|
299
|
+
error(`Unknown type "${userTypeName}"`);
|
|
300
300
|
} else {
|
|
301
301
|
const userType = types[userTypeName];
|
|
302
302
|
|
|
303
303
|
// Action type references not allowed
|
|
304
304
|
if ('action' in userType) {
|
|
305
|
-
error(`Invalid reference to action
|
|
305
|
+
error(`Invalid reference to action "${userTypeName}"`);
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
}
|
|
@@ -324,7 +324,7 @@ function validateTypeModelType(errors, types, type, attr, typeName, memberName)
|
|
|
324
324
|
for (const attrKey of Object.keys(disallowedAttr)) {
|
|
325
325
|
const attrValue = `${attr[attrKey]}`;
|
|
326
326
|
const attrText = `${attrToText[attrKey]} ${attrValue}`;
|
|
327
|
-
error(`Invalid attribute
|
|
327
|
+
error(`Invalid attribute "${attrText}"`);
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
}
|
package/lib/typeModel.js
CHANGED
|
@@ -108,16 +108,16 @@ struct Array
|
|
|
108
108
|
# A dictionary type
|
|
109
109
|
struct Dict
|
|
110
110
|
|
|
111
|
-
# The contained
|
|
111
|
+
# The contained value type
|
|
112
112
|
Type type
|
|
113
113
|
|
|
114
|
-
# The contained
|
|
114
|
+
# The contained value type's attributes
|
|
115
115
|
optional Attributes attr
|
|
116
116
|
|
|
117
|
-
# The contained
|
|
117
|
+
# The contained key type
|
|
118
118
|
optional Type keyType
|
|
119
119
|
|
|
120
|
-
# The contained
|
|
120
|
+
# The contained key type's attributes
|
|
121
121
|
optional Attributes keyAttr
|
|
122
122
|
|
|
123
123
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "schema-markdown",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"description": "A schema definition and validation library",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"schema",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@eslint/js": "~10.0",
|
|
28
28
|
"c8": "~11.0",
|
|
29
|
-
"eslint": "~10.
|
|
30
|
-
"globals": "~17.
|
|
29
|
+
"eslint": "~10.6",
|
|
30
|
+
"globals": "~17.7",
|
|
31
31
|
"jsdoc": "~4.0"
|
|
32
32
|
}
|
|
33
33
|
}
|