diff --git a/lib/entry-points.js b/lib/entry-points.js index e8b673085..1aa30ba06 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -144509,7 +144509,7 @@ function isStringOrUndefined(value) { return value === void 0 || isString(value); } function defaultCheck(validate) { - return (arg) => ({ unknownKeys: [], valid: validate(arg) }); + return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) }); } function makeValidator(validate, required = true) { return { @@ -144541,15 +144541,18 @@ function validateSchema(schema, obj) { function successfulCheckSchema() { return { valid: true, - unknownKeys: [] + unknownKeys: [], + invalidKeys: [] }; } function checkSchema(schema, obj, options = {}, path29 = "") { const result = successfulCheckSchema(); const inputKeys = new Set(Object.keys(obj)); + const invalidKeys = /* @__PURE__ */ new Set(); for (const [key, validator] of Object.entries(schema)) { const hasKey = key in obj; inputKeys.delete(key); + invalidKeys.add(key); if (validator.required && !hasKey) { result.valid = false; if (options.failFast) { @@ -144565,8 +144568,12 @@ function checkSchema(schema, obj, options = {}, path29 = "") { continue; } if (hasKey) { - const checkResult = validator.check(obj[key], `${path29}${key}.`); + const checkResult = validator.check(obj[key], `${path29}.${key}`); result.unknownKeys.push(...checkResult.unknownKeys); + result.invalidKeys.push(...checkResult.invalidKeys); + if (checkResult.invalidKeys.length > 0) { + invalidKeys.delete(key); + } if (!checkResult.valid) { result.valid = false; if (options.failFast) { @@ -144575,9 +144582,13 @@ function checkSchema(schema, obj, options = {}, path29 = "") { continue; } } + invalidKeys.delete(key); } for (const remainingKey of inputKeys) { - result.unknownKeys.push(`${path29}${remainingKey}`); + result.unknownKeys.push(`${path29}.${remainingKey}`); + } + for (const invalidKey of invalidKeys) { + result.invalidKeys.push(`${path29}.${invalidKey}`); } return result; } diff --git a/src/json/index.test.ts b/src/json/index.test.ts index 47956c938..df682fd8b 100644 --- a/src/json/index.test.ts +++ b/src/json/index.test.ts @@ -121,6 +121,22 @@ test("validateSchema - checkSchema reports unknown keys", async (t) => { t.true(result.valid); t.deepEqual( result.unknownKeys.sort(), - ["extraKey", "rootKey.nestedExtraKey"].sort(), + [".extraKey", ".rootKey.nestedExtraKey"].sort(), + ); +}); + +test("validateSchema - checkSchema reports invalid keys", async (t) => { + const result = json.checkSchema(checkSchemaTestSchema, { + rootKey: { + objectKey: { + arrayKey: ["foo"], + }, + }, + }); + + t.false(result.valid); + t.deepEqual( + result.invalidKeys.sort(), + [".rootKey.objectKey.arrayKey[0]"].sort(), ); }); diff --git a/src/json/index.ts b/src/json/index.ts index 7bc23271e..a5c89f722 100644 --- a/src/json/index.ts +++ b/src/json/index.ts @@ -55,7 +55,7 @@ export type Validator = { function defaultCheck( validate: (val: unknown) => val is any, ): (arg: unknown) => CheckSchemaResult { - return (arg) => ({ unknownKeys: [], valid: validate(arg) }); + return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) }); } function makeValidator( @@ -88,20 +88,24 @@ export function array(validator: Validator) { check: (val: unknown, path: string) => { const result: CheckSchemaResult = successfulCheckSchema(); + // The value must be an array. if (!isArray(val)) { result.valid = false; return result; } + // Validate all elements of the array. let index = 0; for (const e of val) { - const eResult = validator.check(e, `${path}[${index}].`); + const elementPath = `${path}[${index}]`; + const eResult = validator.check(e, `${elementPath}`); result.unknownKeys.push(...eResult.unknownKeys); index++; if (!eResult.valid) { result.valid = false; + result.invalidKeys.push(elementPath); continue; } } @@ -212,6 +216,8 @@ export interface CheckSchemaResult { valid: boolean; /** Unknown keys that were found during validation. */ unknownKeys: string[]; + /** Known keys that failed validation. */ + invalidKeys: string[]; } /** @@ -221,6 +227,7 @@ function successfulCheckSchema(): CheckSchemaResult { return { valid: true, unknownKeys: [], + invalidKeys: [], }; } @@ -231,6 +238,7 @@ function invalidCheckSchema(): CheckSchemaResult { return { valid: false, unknownKeys: [], + invalidKeys: [], }; } @@ -242,6 +250,7 @@ export function checkSchema( ): CheckSchemaResult { const result: CheckSchemaResult = successfulCheckSchema(); const inputKeys = new Set(Object.keys(obj)); + const invalidKeys = new Set(); for (const [key, validator] of Object.entries(schema)) { const hasKey = key in obj; @@ -249,6 +258,10 @@ export function checkSchema( // Remove key from set of unrecognised keys. inputKeys.delete(key); + // Add the key to the set of invalid keys. We remove it later once + // it passes validation. + invalidKeys.add(key); + // If the property is required, but absent, fail. if (validator.required && !hasKey) { result.valid = false; @@ -271,9 +284,16 @@ export function checkSchema( // If the property is present, validate it. if (hasKey) { - const checkResult = validator.check(obj[key], `${path}${key}.`); + const checkResult = validator.check(obj[key], `${path}.${key}`); result.unknownKeys.push(...checkResult.unknownKeys); + result.invalidKeys.push(...checkResult.invalidKeys); + + // If we have invalid keys from the validator, then that means that + // we have a more specific key than `key`. Remove `key` from the results. + if (checkResult.invalidKeys.length > 0) { + invalidKeys.delete(key); + } if (!checkResult.valid) { result.valid = false; @@ -286,11 +306,17 @@ export function checkSchema( } // If we reach this point, the key has been successfully validated. + invalidKeys.delete(key); } // If there are any remaining keys in `inputKeys`, add them to `unknownKeys`. for (const remainingKey of inputKeys) { - result.unknownKeys.push(`${path}${remainingKey}`); + result.unknownKeys.push(`${path}.${remainingKey}`); + } + + // If there are any remaining keys in `invalidKeys`, add them to the result. + for (const invalidKey of invalidKeys) { + result.invalidKeys.push(`${path}.${invalidKey}`); } return result;