Skip to content

Commit 9a1225d

Browse files
committed
Track invalid keys, and use more standard JSON path notation
1 parent aec5441 commit 9a1225d

3 files changed

Lines changed: 62 additions & 9 deletions

File tree

lib/entry-points.js

Lines changed: 15 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/json/index.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,22 @@ test("validateSchema - checkSchema reports unknown keys", async (t) => {
121121
t.true(result.valid);
122122
t.deepEqual(
123123
result.unknownKeys.sort(),
124-
["extraKey", "rootKey.nestedExtraKey"].sort(),
124+
[".extraKey", ".rootKey.nestedExtraKey"].sort(),
125+
);
126+
});
127+
128+
test("validateSchema - checkSchema reports invalid keys", async (t) => {
129+
const result = json.checkSchema(checkSchemaTestSchema, {
130+
rootKey: {
131+
objectKey: {
132+
arrayKey: ["foo"],
133+
},
134+
},
135+
});
136+
137+
t.false(result.valid);
138+
t.deepEqual(
139+
result.invalidKeys.sort(),
140+
[".rootKey.objectKey.arrayKey[0]"].sort(),
125141
);
126142
});

src/json/index.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export type Validator<T> = {
5555
function defaultCheck(
5656
validate: (val: unknown) => val is any,
5757
): (arg: unknown) => CheckSchemaResult {
58-
return (arg) => ({ unknownKeys: [], valid: validate(arg) });
58+
return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) });
5959
}
6060

6161
function makeValidator<T>(
@@ -88,20 +88,24 @@ export function array<T>(validator: Validator<T>) {
8888
check: (val: unknown, path: string) => {
8989
const result: CheckSchemaResult = successfulCheckSchema();
9090

91+
// The value must be an array.
9192
if (!isArray(val)) {
9293
result.valid = false;
9394
return result;
9495
}
9596

97+
// Validate all elements of the array.
9698
let index = 0;
9799
for (const e of val) {
98-
const eResult = validator.check(e, `${path}[${index}].`);
100+
const elementPath = `${path}[${index}]`;
101+
const eResult = validator.check(e, `${elementPath}`);
99102

100103
result.unknownKeys.push(...eResult.unknownKeys);
101104
index++;
102105

103106
if (!eResult.valid) {
104107
result.valid = false;
108+
result.invalidKeys.push(elementPath);
105109
continue;
106110
}
107111
}
@@ -212,6 +216,8 @@ export interface CheckSchemaResult {
212216
valid: boolean;
213217
/** Unknown keys that were found during validation. */
214218
unknownKeys: string[];
219+
/** Known keys that failed validation. */
220+
invalidKeys: string[];
215221
}
216222

217223
/**
@@ -221,6 +227,7 @@ function successfulCheckSchema(): CheckSchemaResult {
221227
return {
222228
valid: true,
223229
unknownKeys: [],
230+
invalidKeys: [],
224231
};
225232
}
226233

@@ -231,6 +238,7 @@ function invalidCheckSchema(): CheckSchemaResult {
231238
return {
232239
valid: false,
233240
unknownKeys: [],
241+
invalidKeys: [],
234242
};
235243
}
236244

@@ -242,13 +250,18 @@ export function checkSchema<S extends Schema>(
242250
): CheckSchemaResult {
243251
const result: CheckSchemaResult = successfulCheckSchema();
244252
const inputKeys = new Set(Object.keys(obj));
253+
const invalidKeys = new Set();
245254

246255
for (const [key, validator] of Object.entries(schema)) {
247256
const hasKey = key in obj;
248257

249258
// Remove key from set of unrecognised keys.
250259
inputKeys.delete(key);
251260

261+
// Add the key to the set of invalid keys. We remove it later once
262+
// it passes validation.
263+
invalidKeys.add(key);
264+
252265
// If the property is required, but absent, fail.
253266
if (validator.required && !hasKey) {
254267
result.valid = false;
@@ -271,9 +284,16 @@ export function checkSchema<S extends Schema>(
271284

272285
// If the property is present, validate it.
273286
if (hasKey) {
274-
const checkResult = validator.check(obj[key], `${path}${key}.`);
287+
const checkResult = validator.check(obj[key], `${path}.${key}`);
275288

276289
result.unknownKeys.push(...checkResult.unknownKeys);
290+
result.invalidKeys.push(...checkResult.invalidKeys);
291+
292+
// If we have invalid keys from the validator, then that means that
293+
// we have a more specific key than `key`. Remove `key` from the results.
294+
if (checkResult.invalidKeys.length > 0) {
295+
invalidKeys.delete(key);
296+
}
277297

278298
if (!checkResult.valid) {
279299
result.valid = false;
@@ -286,11 +306,17 @@ export function checkSchema<S extends Schema>(
286306
}
287307

288308
// If we reach this point, the key has been successfully validated.
309+
invalidKeys.delete(key);
289310
}
290311

291312
// If there are any remaining keys in `inputKeys`, add them to `unknownKeys`.
292313
for (const remainingKey of inputKeys) {
293-
result.unknownKeys.push(`${path}${remainingKey}`);
314+
result.unknownKeys.push(`${path}.${remainingKey}`);
315+
}
316+
317+
// If there are any remaining keys in `invalidKeys`, add them to the result.
318+
for (const invalidKey of invalidKeys) {
319+
result.invalidKeys.push(`${path}.${invalidKey}`);
294320
}
295321

296322
return result;

0 commit comments

Comments
 (0)