Created
October 27, 2024 15:25
-
-
Save Pietro-Putelli/2b07a046e20e4b62ee682203e1a96b9d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const validateSchema = (data, schema, path = "", errors = []) => { | |
switch (schema.type) { | |
case "string": | |
if (typeof data !== "string") { | |
errors.push(`Expected type "string" but got ${typeof data} at ${path}`); | |
} | |
break; | |
case "number": | |
if (typeof data !== "number") { | |
errors.push(`Expected type "number" but got ${typeof data} at ${path}`); | |
} | |
break; | |
case "boolean": | |
if (typeof data !== "boolean") { | |
errors.push( | |
`Expected type "boolean" but got ${typeof data} at ${path}` | |
); | |
} | |
break; | |
case "object": | |
if (typeof data !== "object" || Array.isArray(data)) { | |
errors.push(`Expected type "object" but got ${typeof data} at ${path}`); | |
} else { | |
// 1. Check that all required props are in the data object. | |
const requiredProps = schema.required; | |
if (requiredProps) { | |
requiredProps.forEach((prop) => { | |
if (!(prop in data)) { | |
errors.push( | |
`Missing required property ${ | |
path == "" ? "data" : path | |
}.${prop}` | |
); | |
} | |
}); | |
} | |
// 2. Check that all props in data are allowed by the schema. | |
const invalidDataProps = Object.keys(data) | |
.map((key) => { | |
if (!(key in schema.properties)) { | |
return key; | |
} | |
}) | |
.filter(Boolean); | |
if (invalidDataProps.length != 0) { | |
invalidDataProps.forEach((key) => { | |
errors.push(`Invalid key at ${path == "" ? "data" : path}.${key}`); | |
}); | |
} | |
if (schema.properties) { | |
for (const key in schema.properties) { | |
const dataField = data?.[key]; | |
if (dataField != undefined) { | |
const newPath = `${path ? path + "." : ""}${key}`; | |
validateSchema( | |
dataField, | |
schema.properties[key], | |
newPath, | |
errors | |
); | |
} | |
} | |
} | |
} | |
break; | |
case "array": | |
if (!Array.isArray(data)) { | |
errors.push( | |
`Expected type "array" but got ${typeof data} at path ${path}` | |
); | |
} else { | |
for (const index in data) { | |
// 1. Validate each single item in the array | |
validateSchema( | |
data[index], | |
schema.items, | |
`${path == "" ? "data" : path}[${index}]`, | |
errors | |
); | |
} | |
} | |
break; | |
case "oneOf": | |
const oneOfErrors = schema.oneOf.map((subSchema) => { | |
const subErrors = []; | |
validateSchema( | |
data, | |
subSchema, | |
`oneOf.${path == "" ? data : ""}.${subSchema.type}`, | |
subErrors | |
); | |
return subErrors; | |
}); | |
// Check if at least one schema validation passed with no errors | |
if (oneOfErrors.every((errorList) => errorList.length > 0)) { | |
errors.push( | |
`Value at oneOf.${path} does not match any allowed schemas` | |
); | |
} | |
break; | |
case "merge": | |
// Merge sub-schemas | |
const mergedSchema = schema.merge.reduce( | |
(acc, subSchema) => { | |
if (subSchema.type === "object") { | |
// Merge properties | |
if (subSchema.properties) { | |
acc.properties = { ...acc.properties, ...subSchema.properties }; | |
} | |
// Merge required fields | |
if (subSchema.required) { | |
acc.required = [...(acc.required || []), ...subSchema.required]; | |
} | |
} else { | |
// For non-object types, store them in a list to validate against | |
acc.nonObjectSchemas = acc.nonObjectSchemas || []; | |
acc.nonObjectSchemas.push(subSchema); | |
} | |
return acc; | |
}, | |
{ type: "object", properties: {}, required: [] } | |
); | |
if ( | |
mergedSchema.nonObjectSchemas && | |
mergedSchema.nonObjectSchemas.length > 0 | |
) { | |
// Validate data against non-object schemas | |
mergedSchema.nonObjectSchemas.forEach((subSchema) => { | |
validateSchema(data, subSchema, path, errors); | |
}); | |
} else { | |
// Validate data against the merged object schema | |
validateSchema(data, mergedSchema, path, errors); | |
} | |
break; | |
} | |
return errors; | |
}; | |
/* Testing */ | |
// Test Case 1: "object" type | |
const schema_1 = { | |
type: "object", | |
properties: { | |
name: { type: "string" }, | |
surname: { type: "string" }, | |
address: { | |
type: "object", | |
properties: { | |
value: { type: "string" }, | |
caps: { type: "array", items: { type: "string" } }, | |
country: { | |
type: "oneOf", | |
oneOf: [{ type: "string" }, { type: "number" }], | |
}, | |
}, | |
required: ["caps"], | |
}, | |
keywords: { | |
type: "array", | |
items: { | |
type: "string", | |
}, | |
}, | |
}, | |
required: ["name", "surname", "address"], | |
}; | |
const obj_1 = { | |
name: "Mario", | |
surname: "Rossi", | |
address: { | |
caps: ["00100", "00200"], | |
country: "Italy", | |
}, | |
keywords: ["developer", "javascript"], | |
}; | |
const errors1 = validateSchema(obj_1, schema_1); | |
console.log("Errors for obj_1 with schema_1:", errors1); | |
// Test Case 2: "array" type | |
const schema_2 = { | |
type: "array", | |
items: { | |
type: "number", | |
}, | |
}; | |
const obj_2 = [1, 2, "3"]; | |
const errors2 = validateSchema(obj_2, schema_2); | |
console.log("\nErrors for obj_2 with schema_2:", errors2); | |
// Test Case 3: "oneOf" type | |
const schema_3 = { | |
type: "oneOf", | |
oneOf: [ | |
{ | |
type: "string", | |
}, | |
{ | |
type: "number", | |
}, | |
], | |
}; | |
const obj_3 = "Hello World"; | |
const errors3 = validateSchema(obj_3, schema_3); | |
console.log("\nErrors for obj_3 with schema_3:", errors3); | |
// Test Case 4: "merge" type | |
const mergeSchema = { | |
type: "merge", | |
merge: [ | |
{ | |
type: "object", | |
properties: { | |
value: { | |
type: "oneOf", | |
oneOf: [{ type: "string" }, { type: "number" }], | |
}, | |
}, | |
required: ["value"], | |
}, | |
{ | |
type: "object", | |
properties: { | |
numbers: { | |
type: "array", | |
items: { type: "number" }, | |
}, | |
}, | |
required: ["numbers"], | |
}, | |
{ | |
type: "object", | |
properties: { | |
name: { type: "string" }, | |
}, | |
required: ["name"], | |
}, | |
], | |
}; | |
const data1 = { | |
value: 42, | |
numbers: [1, 2, 3], | |
name: "Luigi", | |
}; | |
const errors4 = validateSchema(data1, mergeSchema); | |
console.log("\nErrors for data1 with mergeSchema:", errors4); | |
// Additional test for "merge" with invalid data | |
const invalidData = { | |
value: "true", // Invalid type | |
numbers: [1, 2], // Invalid item in array | |
}; | |
const errors5 = validateSchema(invalidData, mergeSchema); | |
console.log("\nErrors for invalidData with mergeSchema:", errors5); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment