96 lines
3 KiB
TypeScript
96 lines
3 KiB
TypeScript
import fs from "fs";
|
|
|
|
interface VerificationResult {
|
|
valid: boolean;
|
|
errors: string[];
|
|
}
|
|
|
|
/**
|
|
* Verifies that an enriched JSON file matches the expected schema.
|
|
* Returns detailed error messages for any violations.
|
|
*/
|
|
export function verifyEnrichedFile(filePath: string): VerificationResult {
|
|
const errors: string[] = [];
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
return { valid: false, errors: ["File does not exist"] };
|
|
}
|
|
|
|
let data: unknown;
|
|
try {
|
|
data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
} catch (_error: unknown) {
|
|
return { valid: false, errors: ["Invalid JSON syntax"] };
|
|
}
|
|
|
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
return { valid: false, errors: ["Root must be an object"] };
|
|
}
|
|
|
|
const obj = data as Record<string, unknown>;
|
|
|
|
// Required top-level fields
|
|
const requiredFields = ["word", "language", "pos", "senses"];
|
|
for (const field of requiredFields) {
|
|
if (!(field in obj)) {
|
|
errors.push(`Missing required field: "${field}"`);
|
|
}
|
|
}
|
|
|
|
// Validate senses array
|
|
if (!Array.isArray(obj["senses"])) {
|
|
errors.push('"senses" must be an array');
|
|
} else if (obj["senses"].length === 0) {
|
|
errors.push('"senses" array cannot be empty');
|
|
} else {
|
|
for (let i = 0; i < obj["senses"].length; i++) {
|
|
const sense = obj["senses"][i] as Record<string, unknown>;
|
|
const sensePrefix = `senses[${i}]`;
|
|
|
|
if (!sense["sense"] || typeof sense["sense"] !== "string") {
|
|
errors.push(`${sensePrefix}: missing or invalid "sense"`);
|
|
}
|
|
if (!sense["example"] || typeof sense["example"] !== "string") {
|
|
errors.push(`${sensePrefix}: missing or invalid "example"`);
|
|
}
|
|
if (
|
|
!["easy", "medium", "hard"].includes(
|
|
sense["difficulty_level"] as string,
|
|
)
|
|
) {
|
|
errors.push(`${sensePrefix}: invalid "difficulty_level"`);
|
|
}
|
|
if (!sense["translations"] || typeof sense["translations"] !== "object") {
|
|
errors.push(`${sensePrefix}: missing "translations"`);
|
|
} else {
|
|
const trans = sense["translations"] as Record<string, unknown>;
|
|
for (const lang of ["de", "it", "es", "fr"]) {
|
|
if (!Array.isArray(trans[lang])) {
|
|
errors.push(
|
|
`${sensePrefix}: missing or invalid "${lang}" translations`,
|
|
);
|
|
} else {
|
|
for (let j = 0; j < (trans[lang] as unknown[]).length; j++) {
|
|
const t = (trans[lang] as unknown[])[j] as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
if (!t["word"] || typeof t["word"] !== "string") {
|
|
errors.push(`${sensePrefix}.${lang}[${j}]: missing "word"`);
|
|
}
|
|
if (
|
|
!["masculine", "feminine", "neuter", null].includes(
|
|
t["gender"] as string | null,
|
|
)
|
|
) {
|
|
errors.push(`${sensePrefix}.${lang}[${j}]: invalid "gender"`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return { valid: errors.length === 0, errors };
|
|
}
|