File size: 1,805 Bytes
9de795a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
 * Schema-Validierung (Ajv, JSON Schema draft 2020-12) über alle Verträge.
 * Wird vom Schema-Check, den Tests und später vom Frontend (Import-Pfade)
 * genutzt. Python-Gegenstück: fp_engines.validation (jsonschema).
 */
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import { readdirSync, readFileSync } from "node:fs";
import { join, dirname, resolve, basename } from "node:path";
import { fileURLToPath } from "node:url";

const schemasDir = resolve(dirname(fileURLToPath(import.meta.url)), "../schemas");

export interface ValidationResult {
  ok: boolean;
  errors: string[];
}

export interface ContractValidator {
  /** Validiert eine Instanz gegen einen Vertrag (Schema-Name ohne Endung). */
  validate(schemaName: string, instance: unknown): ValidationResult;
  schemaNames: string[];
}

export function createValidator(): ContractValidator {
  // allowUnionTypes: Verträge nutzen bewusst Union-Typen (z.B. params: number|string).
  const ajv = new Ajv2020({ allErrors: true, strict: true, allowUnionTypes: true });
  addFormats(ajv);

  const names: string[] = [];
  for (const file of readdirSync(schemasDir).filter((f) => f.endsWith(".schema.json"))) {
    const schema: unknown = JSON.parse(readFileSync(join(schemasDir, file), "utf-8"));
    const name = basename(file, ".schema.json");
    ajv.addSchema(schema as object, name);
    names.push(name);
  }

  return {
    schemaNames: names,
    validate(schemaName, instance) {
      const validate = ajv.getSchema(schemaName);
      if (!validate) throw new Error(`Unbekanntes Schema: ${schemaName}`);
      const ok = validate(instance) as boolean;
      return {
        ok,
        errors: (validate.errors ?? []).map((e) => `${e.instancePath || "/"} ${e.message ?? ""}`),
      };
    },
  };
}