All files / generators/_collections config-validator.util.ts

94.23% Statements 98/104
87.5% Branches 35/40
100% Functions 4/4
94.23% Lines 98/104

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 1051x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 18x 18x 18x 18x 1x 1x 51x     51x 51x 51x 49x 49x 2x 2x 2x 49x 49x 49x 51x 2x 2x 49x 49x 51x 2x 2x 49x 49x 51x 16x 16x 15x 26x 2x 2x 26x 15x 16x 16x 94x 28x 28x 94x 16x 16x 49x 49x 51x 6x 1x 1x 6x 5x 5x 6x 51x 1x 1x 100x     100x 14x 14x 86x 86x 1x 1x 98x 98x 98x 100x     100x 63x 63x 100x 35x 35x 1x  
/**
 * Lightweight config-validator — `dye2e validate-config` parancs alapja.
 *
 * NEM teljes ajv-szintű JSON Schema validátor — csak a `required` + `type`
 * + `enum` + `minimum` ellenőrzéseket fedi (a leggyakoribb hibák ~80%-a).
 * Teljes draft-07 validátor: a consumer-projekt használhat `ajv`-t a
 * `DyE2E_JsonSchema_Util` schémájával.
 */
 
export interface DyE2E_ValidationError {
  path: string;
  message: string;
}
 
export class DyE2E_ConfigValidator_Util {
 
  /**
   * Egy `data` objektumot validál egy `schema` JSON-Schema (draft-07) ellen.
   * Visszaadja a hibákat — üres tömb = valid.
   */
  static validate(data: unknown, schema: any, basePath: string = '$'): DyE2E_ValidationError[] {
    const errors: DyE2E_ValidationError[] = [];
    DyE2E_ConfigValidator_Util.walkValidate(data, schema, basePath, errors);
    return errors;
  }
 
  private static walkValidate(data: unknown, schema: any, path: string, errors: DyE2E_ValidationError[]): void {
    if (!schema || typeof schema !== 'object') {
      return;
    }
 
    // Type check
    if (schema.type) {
      const ok: boolean = DyE2E_ConfigValidator_Util.typeMatches(data, schema.type);
      if (!ok) {
        errors.push({ path: path, message: `expected type "${Array.isArray(schema.type) ? schema.type.join('|') : schema.type}", got "${DyE2E_ConfigValidator_Util.typeOf(data)}"` });
        return; // ne menjünk tovább rossz type-on
      }
    }
 
    // Enum check
    if (Array.isArray(schema.enum) && schema.enum.indexOf(data) === -1) {
      errors.push({ path: path, message: `value not in enum: ${schema.enum.join(', ')}` });
    }
 
    // Number minimum
    if (typeof data === 'number' && typeof schema.minimum === 'number' && data < schema.minimum) {
      errors.push({ path: path, message: `value ${data} below minimum ${schema.minimum}` });
    }
 
    // Object — required + properties
    if (DyE2E_ConfigValidator_Util.typeMatches(data, 'object') && typeof data === 'object' && data !== null) {
      const obj: Record<string, unknown> = data as Record<string, unknown>;
      if (Array.isArray(schema.required)) {
        for (const r of schema.required) {
          if (!(r in obj)) {
            errors.push({ path: `${path}.${r}`, message: `required property missing` });
          }
        }
      }
      if (schema.properties && typeof schema.properties === 'object') {
        for (const k of Object.keys(schema.properties)) {
          if (k in obj) {
            DyE2E_ConfigValidator_Util.walkValidate(obj[k], schema.properties[k], `${path}.${k}`, errors);
          }
        }
      }
    }
 
    // Array — items
    if (Array.isArray(data) && schema.items) {
      if (typeof schema.minItems === 'number' && data.length < schema.minItems) {
        errors.push({ path: path, message: `array length ${data.length} below minItems ${schema.minItems}` });
      }
      for (let i: number = 0; i < data.length; i++) {
        DyE2E_ConfigValidator_Util.walkValidate(data[i], schema.items, `${path}[${i}]`, errors);
      }
    }
  }
 
  private static typeOf(data: unknown): string {
    if (data === null) {
      return 'null';
    }
    if (Array.isArray(data)) {
      return 'array';
    }
    return typeof data;
  }
 
  private static typeMatches(data: unknown, type: string | string[]): boolean {
    const actual: string = DyE2E_ConfigValidator_Util.typeOf(data);
    const types: string[] = Array.isArray(type) ? type : [type];
    for (const t of types) {
      if (t === 'integer' && actual === 'number' && Number.isInteger(data as number)) {
        return true;
      }
      if (t === actual) {
        return true;
      }
    }
    return false;
  }
}