Add checkSchema function

This commit is contained in:
Michael B. Gale
2026-07-09 23:27:42 +01:00
parent 6973946465
commit dc2d916874
3 changed files with 88 additions and 8 deletions

30
lib/entry-points.js generated
View File

@@ -144518,19 +144518,41 @@ function optionalOrNull(validator) {
};
}
function validateSchema(schema, obj) {
const result = checkSchema(schema, obj, { failFast: true });
return result.valid;
}
function checkSchema(schema, obj, options = {}, path29 = "") {
const result = { valid: true, unknownKeys: [] };
const inputKeys = new Set(Object.keys(obj));
for (const [key, validator] of Object.entries(schema)) {
const hasKey = key in obj;
if (validator.required && !hasKey) {
return false;
result.valid = false;
if (options.failFast) {
return result;
}
continue;
}
if (validator.required && (obj[key] === void 0 || obj[key] === null)) {
return false;
result.valid = false;
if (options.failFast) {
return result;
}
continue;
}
if (hasKey && !validator.validate(obj[key])) {
return false;
result.valid = false;
if (options.failFast) {
return result;
}
continue;
}
inputKeys.delete(key);
}
return true;
for (const remainingKey of inputKeys) {
result.unknownKeys.push(`${path29}${remainingKey}`);
}
return result;
}
// src/util.ts

View File

@@ -67,3 +67,13 @@ test("validateSchema - optional properties are optional", async (t) => {
t.true(json.validateSchema(optionalSchema, { optionalKey: "" }));
t.true(json.validateSchema(optionalSchema, { optionalKey: "foo" }));
});
test("validateSchema - checkSchema reports unknown keys", async (t) => {
const result = json.checkSchema(testSchema, {
requiredKey: "foo",
extraKey: "bar",
});
t.true(result.valid);
t.deepEqual(result.unknownKeys, ["extraKey"]);
});

View File

@@ -121,24 +121,72 @@ export function validateSchema<S extends Schema>(
schema: S,
obj: UnvalidatedObject<any>,
): obj is FromSchema<S> {
const result = checkSchema(schema, obj, { failFast: true });
return result.valid;
}
export interface CheckSchemaOptions {
/** Whether to stop validation after the first error. */
failFast?: boolean;
}
export interface CheckSchemaResult {
/** Whether the `obj` satisfies the schema. */
valid: boolean;
/** Unknown keys that were found during validation. */
unknownKeys: string[];
}
export function checkSchema<S extends Schema>(
schema: S,
obj: UnvalidatedObject<any>,
options: CheckSchemaOptions = {},
path: string = "",
): CheckSchemaResult {
const result: CheckSchemaResult = { valid: true, unknownKeys: [] };
const inputKeys = new Set(Object.keys(obj));
for (const [key, validator] of Object.entries(schema)) {
const hasKey = key in obj;
// If the property is required, but absent, fail.
if (validator.required && !hasKey) {
return false;
result.valid = false;
if (options.failFast) {
return result;
}
continue;
}
// If the property is required, but undefined or null, fail.
if (validator.required && (obj[key] === undefined || obj[key] === null)) {
return false;
result.valid = false;
if (options.failFast) {
return result;
}
continue;
}
// If the property is present, validate it.
if (hasKey && !validator.validate(obj[key])) {
return false;
result.valid = false;
if (options.failFast) {
return result;
}
continue;
}
// If we reach this point, the key has been successfully validated.
inputKeys.delete(key);
}
return true;
// If there are any remaining keys in `inputKeys`, add them to `unknownKeys`.
for (const remainingKey of inputKeys) {
result.unknownKeys.push(`${path}${remainingKey}`);
}
return result;
}