@atlaspack/utils 3.1.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/schema.js +144 -143
- package/lib/schema.js +126 -142
- package/package.json +6 -6
- package/src/schema.ts +176 -157
- package/tsconfig.tsbuildinfo +1 -1
package/src/schema.ts
CHANGED
|
@@ -114,14 +114,13 @@ function validateSchema(
|
|
|
114
114
|
data: unknown,
|
|
115
115
|
): Array<SchemaError> {
|
|
116
116
|
function walk(
|
|
117
|
-
|
|
118
|
-
schemaAncestors,
|
|
117
|
+
schemaAncestors: Array<SchemaEntity>,
|
|
119
118
|
dataNode: unknown,
|
|
120
119
|
dataPath: string,
|
|
121
120
|
): SchemaError | null | undefined | Array<SchemaError> {
|
|
122
121
|
let [schemaNode] = schemaAncestors;
|
|
123
122
|
|
|
124
|
-
if (schemaNode.type) {
|
|
123
|
+
if ('type' in schemaNode && schemaNode.type) {
|
|
125
124
|
let type = Array.isArray(dataNode) ? 'array' : typeof dataNode;
|
|
126
125
|
if (schemaNode.type !== type) {
|
|
127
126
|
return {
|
|
@@ -130,18 +129,20 @@ function validateSchema(
|
|
|
130
129
|
dataPath,
|
|
131
130
|
expectedTypes: [schemaNode.type],
|
|
132
131
|
ancestors: schemaAncestors,
|
|
133
|
-
prettyType: schemaNode.__type,
|
|
132
|
+
prettyType: '__type' in schemaNode ? schemaNode.__type : undefined,
|
|
134
133
|
};
|
|
135
134
|
} else {
|
|
136
135
|
switch (schemaNode.type) {
|
|
137
136
|
case 'array': {
|
|
138
|
-
if (
|
|
137
|
+
if (
|
|
138
|
+
'items' in schemaNode &&
|
|
139
|
+
schemaNode.items &&
|
|
140
|
+
Array.isArray(dataNode)
|
|
141
|
+
) {
|
|
139
142
|
let results: Array<SchemaError | Array<SchemaError>> = [];
|
|
140
|
-
// @ts-expect-error TS18046
|
|
141
143
|
for (let i = 0; i < dataNode.length; i++) {
|
|
142
144
|
let result = walk(
|
|
143
145
|
[schemaNode.items].concat(schemaAncestors),
|
|
144
|
-
// @ts-expect-error TS18046
|
|
145
146
|
dataNode[i],
|
|
146
147
|
dataPath + '/' + i,
|
|
147
148
|
);
|
|
@@ -156,145 +157,149 @@ function validateSchema(
|
|
|
156
157
|
break;
|
|
157
158
|
}
|
|
158
159
|
case 'string': {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
160
|
+
if (typeof dataNode === 'string') {
|
|
161
|
+
let value: string = dataNode;
|
|
162
|
+
if ('enum' in schemaNode && schemaNode.enum) {
|
|
163
|
+
if (!schemaNode.enum.includes(value)) {
|
|
164
|
+
return {
|
|
165
|
+
type: 'enum',
|
|
166
|
+
dataType: 'value',
|
|
167
|
+
dataPath,
|
|
168
|
+
expectedValues: schemaNode.enum,
|
|
169
|
+
actualValue: value,
|
|
170
|
+
ancestors: schemaAncestors,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
} else if ('__validate' in schemaNode && schemaNode.__validate) {
|
|
174
|
+
let validationError = schemaNode.__validate(value);
|
|
175
|
+
if (typeof validationError == 'string') {
|
|
176
|
+
return {
|
|
177
|
+
type: 'other',
|
|
178
|
+
dataType: 'value',
|
|
179
|
+
dataPath,
|
|
180
|
+
message: validationError,
|
|
181
|
+
actualValue: value,
|
|
182
|
+
ancestors: schemaAncestors,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
break;
|
|
186
188
|
}
|
|
187
189
|
case 'number': {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
190
|
+
if (typeof dataNode === 'number') {
|
|
191
|
+
let value: number = dataNode;
|
|
192
|
+
if ('enum' in schemaNode && schemaNode.enum) {
|
|
193
|
+
if (!schemaNode.enum.includes(value)) {
|
|
194
|
+
return {
|
|
195
|
+
type: 'enum',
|
|
196
|
+
dataType: 'value',
|
|
197
|
+
dataPath,
|
|
198
|
+
expectedValues: schemaNode.enum,
|
|
199
|
+
actualValue: value,
|
|
200
|
+
ancestors: schemaAncestors,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
200
203
|
}
|
|
201
204
|
}
|
|
202
205
|
break;
|
|
203
206
|
}
|
|
204
207
|
case 'object': {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
invalidProps
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
ancestors: schemaAncestors,
|
|
226
|
-
}) as SchemaError,
|
|
227
|
-
),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
if (schemaNode.required) {
|
|
231
|
-
// @ts-expect-error TS2769
|
|
232
|
-
let keys = Object.keys(dataNode);
|
|
233
|
-
let missingKeys = schemaNode.required.filter(
|
|
234
|
-
// @ts-expect-error TS7006
|
|
235
|
-
(val) => !keys.includes(val),
|
|
236
|
-
);
|
|
237
|
-
results.push(
|
|
238
|
-
...missingKeys.map(
|
|
239
|
-
// @ts-expect-error TS7006
|
|
240
|
-
(k) =>
|
|
241
|
-
({
|
|
242
|
-
type: 'missing-prop',
|
|
243
|
-
dataPath,
|
|
244
|
-
dataType: 'value',
|
|
245
|
-
prop: k,
|
|
246
|
-
expectedProps: schemaNode.required,
|
|
247
|
-
actualProps: keys,
|
|
248
|
-
ancestors: schemaAncestors,
|
|
249
|
-
}) as SchemaError,
|
|
250
|
-
),
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
if (schemaNode.properties) {
|
|
254
|
-
let {additionalProperties = true} = schemaNode;
|
|
255
|
-
// @ts-expect-error TS2407
|
|
256
|
-
for (let k in dataNode) {
|
|
257
|
-
if (invalidProps && invalidProps.includes(k)) {
|
|
258
|
-
// Don't check type on forbidden props
|
|
259
|
-
continue;
|
|
260
|
-
} else if (k in schemaNode.properties) {
|
|
261
|
-
let result = walk(
|
|
262
|
-
[schemaNode.properties[k]].concat(schemaAncestors),
|
|
263
|
-
// @ts-expect-error TS18046
|
|
264
|
-
dataNode[k],
|
|
265
|
-
dataPath + '/' + encodeJSONKeyComponent(k),
|
|
266
|
-
);
|
|
267
|
-
if (result) results.push(result);
|
|
268
|
-
} else {
|
|
269
|
-
if (typeof additionalProperties === 'boolean') {
|
|
270
|
-
if (!additionalProperties) {
|
|
271
|
-
results.push({
|
|
272
|
-
type: 'enum',
|
|
273
|
-
dataType: 'key',
|
|
208
|
+
if (
|
|
209
|
+
typeof dataNode === 'object' &&
|
|
210
|
+
dataNode !== null &&
|
|
211
|
+
!Array.isArray(dataNode)
|
|
212
|
+
) {
|
|
213
|
+
let results: Array<Array<SchemaError> | SchemaError> = [];
|
|
214
|
+
let invalidProps;
|
|
215
|
+
if (
|
|
216
|
+
'__forbiddenProperties' in schemaNode &&
|
|
217
|
+
schemaNode.__forbiddenProperties
|
|
218
|
+
) {
|
|
219
|
+
let keys = Object.keys(dataNode);
|
|
220
|
+
invalidProps = schemaNode.__forbiddenProperties.filter(
|
|
221
|
+
(val: string) => keys.includes(val),
|
|
222
|
+
);
|
|
223
|
+
results.push(
|
|
224
|
+
...invalidProps.map(
|
|
225
|
+
(k: string) =>
|
|
226
|
+
({
|
|
227
|
+
type: 'forbidden-prop',
|
|
274
228
|
dataPath: dataPath + '/' + encodeJSONKeyComponent(k),
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
actualValue: k,
|
|
229
|
+
dataType: 'key',
|
|
230
|
+
prop: k,
|
|
231
|
+
expectedProps: Object.keys(schemaNode.properties),
|
|
232
|
+
actualProps: keys,
|
|
280
233
|
ancestors: schemaAncestors,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
234
|
+
}) as SchemaError,
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if ('required' in schemaNode && schemaNode.required) {
|
|
239
|
+
let keys = Object.keys(dataNode);
|
|
240
|
+
let missingKeys = schemaNode.required.filter(
|
|
241
|
+
(val: string) => !keys.includes(val),
|
|
242
|
+
);
|
|
243
|
+
results.push(
|
|
244
|
+
...missingKeys.map(
|
|
245
|
+
(k: string) =>
|
|
246
|
+
({
|
|
247
|
+
type: 'missing-prop',
|
|
248
|
+
dataPath,
|
|
249
|
+
dataType: 'value',
|
|
250
|
+
prop: k,
|
|
251
|
+
expectedProps: schemaNode.required,
|
|
252
|
+
actualProps: keys,
|
|
253
|
+
ancestors: schemaAncestors,
|
|
254
|
+
}) as SchemaError,
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if ('properties' in schemaNode && schemaNode.properties) {
|
|
259
|
+
let {additionalProperties = true} = schemaNode;
|
|
260
|
+
for (let k in dataNode) {
|
|
261
|
+
if (invalidProps && invalidProps.includes(k)) {
|
|
262
|
+
// Don't check type on forbidden props
|
|
263
|
+
continue;
|
|
264
|
+
} else if (k in schemaNode.properties) {
|
|
285
265
|
let result = walk(
|
|
286
|
-
[
|
|
287
|
-
|
|
288
|
-
dataNode[k],
|
|
266
|
+
[schemaNode.properties[k]].concat(schemaAncestors),
|
|
267
|
+
(dataNode as Record<string, unknown>)[k],
|
|
289
268
|
dataPath + '/' + encodeJSONKeyComponent(k),
|
|
290
269
|
);
|
|
291
270
|
if (result) results.push(result);
|
|
271
|
+
} else {
|
|
272
|
+
if (typeof additionalProperties === 'boolean') {
|
|
273
|
+
if (!additionalProperties) {
|
|
274
|
+
results.push({
|
|
275
|
+
type: 'enum',
|
|
276
|
+
dataType: 'key',
|
|
277
|
+
dataPath: dataPath + '/' + encodeJSONKeyComponent(k),
|
|
278
|
+
expectedValues: Object.keys(
|
|
279
|
+
schemaNode.properties,
|
|
280
|
+
).filter((p) => !(p in dataNode)),
|
|
281
|
+
actualValue: k,
|
|
282
|
+
ancestors: schemaAncestors,
|
|
283
|
+
prettyType: schemaNode.__type,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
let result = walk(
|
|
288
|
+
[additionalProperties].concat(schemaAncestors),
|
|
289
|
+
(dataNode as Record<string, unknown>)[k],
|
|
290
|
+
dataPath + '/' + encodeJSONKeyComponent(k),
|
|
291
|
+
);
|
|
292
|
+
if (result) results.push(result);
|
|
293
|
+
}
|
|
292
294
|
}
|
|
293
295
|
}
|
|
294
296
|
}
|
|
297
|
+
if (results.length)
|
|
298
|
+
return results.reduce<Array<any>>(
|
|
299
|
+
(acc, v) => acc.concat(v),
|
|
300
|
+
[],
|
|
301
|
+
);
|
|
295
302
|
}
|
|
296
|
-
if (results.length)
|
|
297
|
-
return results.reduce<Array<any>>((acc, v) => acc.concat(v), []);
|
|
298
303
|
break;
|
|
299
304
|
}
|
|
300
305
|
case 'boolean':
|
|
@@ -305,7 +310,11 @@ function validateSchema(
|
|
|
305
310
|
}
|
|
306
311
|
}
|
|
307
312
|
} else {
|
|
308
|
-
if (
|
|
313
|
+
if (
|
|
314
|
+
'enum' in schemaNode &&
|
|
315
|
+
schemaNode.enum &&
|
|
316
|
+
!schemaNode.enum.includes(dataNode)
|
|
317
|
+
) {
|
|
309
318
|
return {
|
|
310
319
|
type: 'enum',
|
|
311
320
|
dataType: 'value',
|
|
@@ -316,15 +325,20 @@ function validateSchema(
|
|
|
316
325
|
};
|
|
317
326
|
}
|
|
318
327
|
|
|
319
|
-
if (
|
|
320
|
-
let list =
|
|
328
|
+
if ('oneOf' in schemaNode || 'allOf' in schemaNode) {
|
|
329
|
+
let list =
|
|
330
|
+
'oneOf' in schemaNode
|
|
331
|
+
? schemaNode.oneOf
|
|
332
|
+
: 'allOf' in schemaNode
|
|
333
|
+
? schemaNode.allOf
|
|
334
|
+
: [];
|
|
321
335
|
let results: Array<SchemaError | Array<SchemaError>> = [];
|
|
322
336
|
for (let f of list) {
|
|
323
337
|
let result = walk([f].concat(schemaAncestors), dataNode, dataPath);
|
|
324
338
|
if (result) results.push(result);
|
|
325
339
|
}
|
|
326
340
|
if (
|
|
327
|
-
schemaNode
|
|
341
|
+
'oneOf' in schemaNode
|
|
328
342
|
? results.length == schemaNode.oneOf.length
|
|
329
343
|
: results.length > 0
|
|
330
344
|
) {
|
|
@@ -342,14 +356,13 @@ function validateSchema(
|
|
|
342
356
|
);
|
|
343
357
|
return results[0];
|
|
344
358
|
}
|
|
345
|
-
} else if (schemaNode.not) {
|
|
359
|
+
} else if ('not' in schemaNode && schemaNode.not) {
|
|
346
360
|
let result = walk(
|
|
347
361
|
[schemaNode.not].concat(schemaAncestors),
|
|
348
362
|
dataNode,
|
|
349
363
|
dataPath,
|
|
350
364
|
);
|
|
351
|
-
|
|
352
|
-
if (!result || result.length == 0) {
|
|
365
|
+
if (!result || (Array.isArray(result) && result.length == 0)) {
|
|
353
366
|
return {
|
|
354
367
|
type: 'other',
|
|
355
368
|
dataPath,
|
|
@@ -375,16 +388,16 @@ export function fuzzySearch(
|
|
|
375
388
|
actualValue: string,
|
|
376
389
|
): Array<string> {
|
|
377
390
|
let result = expectedValues
|
|
378
|
-
.map(
|
|
391
|
+
.map(
|
|
392
|
+
(exp) =>
|
|
393
|
+
[exp, levenshtein.distance(exp, actualValue)] as [string, number],
|
|
394
|
+
)
|
|
379
395
|
.filter(
|
|
380
396
|
// Remove if more than half of the string would need to be changed
|
|
381
|
-
|
|
382
|
-
([, d]: [any, any]) => d * 2 < actualValue.length,
|
|
397
|
+
([, d]: [string, number]) => d * 2 < actualValue.length,
|
|
383
398
|
);
|
|
384
|
-
|
|
385
|
-
result.
|
|
386
|
-
// @ts-expect-error TS2345
|
|
387
|
-
return result.map(([v]: [any]) => v);
|
|
399
|
+
result.sort(([, a]: [string, number], [, b]: [string, number]) => a - b);
|
|
400
|
+
return result.map(([v]: [string, number]) => v);
|
|
388
401
|
}
|
|
389
402
|
|
|
390
403
|
validateSchema.diagnostic = function (
|
|
@@ -430,12 +443,18 @@ validateSchema.diagnostic = function (
|
|
|
430
443
|
}
|
|
431
444
|
return loadedSource;
|
|
432
445
|
}
|
|
433
|
-
|
|
434
|
-
let object
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
446
|
+
|
|
447
|
+
let object: unknown;
|
|
448
|
+
if ('map' in data && data.map) {
|
|
449
|
+
object = data.map.data;
|
|
450
|
+
} else if ('data' in data && data.data !== undefined) {
|
|
451
|
+
object = data.data;
|
|
452
|
+
} else if ('source' in data && data.source) {
|
|
453
|
+
object = JSON.parse(loadSource(data.source) || '');
|
|
454
|
+
} else {
|
|
455
|
+
throw new Error('Unable to get object from data');
|
|
456
|
+
}
|
|
457
|
+
|
|
439
458
|
let errors = validateSchema(schema, object);
|
|
440
459
|
if (errors.length) {
|
|
441
460
|
let keys = errors.map((e) => {
|
|
@@ -493,23 +512,24 @@ validateSchema.diagnostic = function (
|
|
|
493
512
|
return {key: e.dataPath, type: e.dataType, message};
|
|
494
513
|
});
|
|
495
514
|
let map, code;
|
|
496
|
-
|
|
497
|
-
if (data.map) {
|
|
498
|
-
// @ts-expect-error TS2339
|
|
515
|
+
if ('map' in data && data.map) {
|
|
499
516
|
map = data.map;
|
|
500
|
-
code = loadSource(data.source);
|
|
517
|
+
code = loadSource(data.source) ?? '';
|
|
501
518
|
} else {
|
|
502
|
-
|
|
503
|
-
loadSource(data.source) ??
|
|
504
|
-
|
|
505
|
-
JSON.stringify(nullthrows(data.data),
|
|
519
|
+
if ('source' in data && data.source) {
|
|
520
|
+
map = loadSource(data.source) ?? '';
|
|
521
|
+
} else if ('data' in data && data.data !== undefined) {
|
|
522
|
+
map = JSON.stringify(nullthrows(data.data), null, '\t');
|
|
523
|
+
} else {
|
|
524
|
+
map = '';
|
|
525
|
+
}
|
|
506
526
|
code = map;
|
|
507
527
|
}
|
|
508
528
|
let codeFrames = [
|
|
509
529
|
{
|
|
510
530
|
filePath: data.filePath ?? undefined,
|
|
511
|
-
language: 'json',
|
|
512
|
-
code,
|
|
531
|
+
language: 'json' as const,
|
|
532
|
+
code: code ?? '',
|
|
513
533
|
codeHighlights: generateJSONCodeHighlights(
|
|
514
534
|
map,
|
|
515
535
|
keys.map(({key, type, message}) => ({
|
|
@@ -525,7 +545,6 @@ validateSchema.diagnostic = function (
|
|
|
525
545
|
diagnostic: {
|
|
526
546
|
message: message,
|
|
527
547
|
origin,
|
|
528
|
-
// @ts-expect-error TS2322
|
|
529
548
|
codeFrames,
|
|
530
549
|
},
|
|
531
550
|
});
|