summaryrefslogtreecommitdiff
path: root/packages/core/src/utils/schemaValidator.ts
blob: b2b1f8536502971782f1db10e71898a95ce3db20 (plain)
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
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { Schema } from '@google/genai';
import * as ajv from 'ajv';

const ajValidator = new ajv.Ajv();

/**
 * Simple utility to validate objects against JSON Schemas
 */
export class SchemaValidator {
  /**
   * Returns null if the data confroms to the schema described by schema (or if schema
   *  is null). Otherwise, returns a string describing the error.
   */
  static validate(schema: Schema | undefined, data: unknown): string | null {
    if (!schema) {
      return null;
    }
    if (typeof data !== 'object' || data === null) {
      return 'Value of params must be an object';
    }
    const validate = ajValidator.compile(this.toObjectSchema(schema));
    const valid = validate(data);
    if (!valid && validate.errors) {
      return ajValidator.errorsText(validate.errors, { dataVar: 'params' });
    }
    return null;
  }

  /**
   * Converts @google/genai's Schema to an object compatible with avj.
   * This is necessry because it represents Types as an Enum (with
   * UPPERCASE values) and minItems and minLength as strings, when they should be numbers.
   */
  private static toObjectSchema(schema: Schema): object {
    const newSchema: Record<string, unknown> = { ...schema };
    if (newSchema.anyOf && Array.isArray(newSchema.anyOf)) {
      newSchema.anyOf = newSchema.anyOf.map((v) => this.toObjectSchema(v));
    }
    if (newSchema.items) {
      newSchema.items = this.toObjectSchema(newSchema.items);
    }
    if (newSchema.properties && typeof newSchema.properties === 'object') {
      const newProperties: Record<string, unknown> = {};
      for (const [key, value] of Object.entries(newSchema.properties)) {
        newProperties[key] = this.toObjectSchema(value as Schema);
      }
      newSchema.properties = newProperties;
    }
    if (newSchema.type) {
      newSchema.type = String(newSchema.type).toLowerCase();
    }
    if (newSchema.minItems) {
      newSchema.minItems = Number(newSchema.minItems);
    }
    if (newSchema.minLength) {
      newSchema.minLength = Number(newSchema.minLength);
    }
    return newSchema;
  }
}