@genkit-ai/core
Version:
Genkit AI framework core libraries.
220 lines (215 loc) • 6.65 kB
JavaScript
import { Validator } from "@cfworker/json-schema";
import Ajv from "ajv";
import addFormats from "ajv-formats";
import { z } from "zod";
import zodToJsonSchema from "zod-to-json-schema";
import { getGenkitRuntimeConfig } from "./config.mjs";
import { GenkitError } from "./error.mjs";
const ajv = new Ajv();
addFormats(ajv);
const jsonSchemas = /* @__PURE__ */ new WeakMap();
const ajvValidators = /* @__PURE__ */ new WeakMap();
const cfWorkerValidators = /* @__PURE__ */ new WeakMap();
const schemaAnnotations = /* @__PURE__ */ new WeakMap();
function annotateSchema(schema, annotations) {
const current = schemaAnnotations.get(schema) || {};
schemaAnnotations.set(schema, { ...current, ...annotations });
return schema;
}
class ValidationError extends GenkitError {
constructor({
data,
errors,
schema
}) {
super({
status: "INVALID_ARGUMENT",
message: `Schema validation failed. Parse Errors:
${errors.map((e) => `- ${e.path}: ${e.message}`).join("\n")}
Provided data:
${JSON.stringify(data, null, 2)}
Required JSON schema:
${JSON.stringify(schema, null, 2)}`,
detail: { errors, schema }
});
}
}
function toJsonSchema({
jsonSchema,
schema
}) {
if (!jsonSchema && !schema) return null;
if (jsonSchema) return jsonSchema;
if (jsonSchemas.has(schema)) return jsonSchemas.get(schema);
const outSchema = zodToJsonSchema(schema, {
removeAdditionalStrategy: "strict"
});
const annotatedSchema = applyAnnotations(schema, outSchema);
jsonSchemas.set(schema, annotatedSchema);
return annotatedSchema;
}
function applyAnnotations(schema, json) {
if (!json || typeof json !== "object") return json;
const annotationsToApply = [];
let current = schema;
while (current) {
const ann = schemaAnnotations.get(current);
if (ann) annotationsToApply.push(ann);
if (current instanceof z.ZodOptional || current instanceof z.ZodNullable || current instanceof z.ZodDefault || current instanceof z.ZodEffects) {
current = current._def.innerType || current._def.schema;
} else {
break;
}
}
const resolvedAnnotations = {};
for (let i = annotationsToApply.length - 1; i >= 0; i--) {
Object.assign(resolvedAnnotations, annotationsToApply[i]);
}
for (const key in resolvedAnnotations) {
if (Object.prototype.hasOwnProperty.call(json, key)) {
console.warn(
`Annotation key "${key}" conflicts with existing JSON schema property and will be ignored.`
);
continue;
}
json[key] = resolvedAnnotations[key];
}
const inner = current;
if (inner instanceof z.ZodObject && json.properties) {
for (const key in inner.shape) {
if (json.properties[key]) {
applyAnnotations(inner.shape[key], json.properties[key]);
}
}
} else if (inner instanceof z.ZodArray && json.items) {
applyAnnotations(inner.element, json.items);
} else if (inner instanceof z.ZodUnion && json.anyOf) {
for (let i = 0; i < inner.options.length; i++) {
applyAnnotations(inner.options[i], json.anyOf[i]);
}
} else if (inner instanceof z.ZodIntersection && json.allOf) {
const schemas = [];
const collect = (s) => {
if (s instanceof z.ZodIntersection) {
collect(s._def.left);
collect(s._def.right);
} else {
schemas.push(s);
}
};
collect(inner);
if (schemas.length === json.allOf.length) {
for (let i = 0; i < schemas.length; i++) {
applyAnnotations(schemas[i], json.allOf[i]);
}
}
} else if (inner instanceof z.ZodRecord && json.additionalProperties) {
applyAnnotations(inner.valueSchema, json.additionalProperties);
} else if (inner instanceof z.ZodTuple && Array.isArray(json.items)) {
for (let i = 0; i < inner.items.length; i++) {
applyAnnotations(inner.items[i], json.items[i]);
}
} else if (inner instanceof z.ZodDiscriminatedUnion && json.anyOf) {
for (let i = 0; i < inner.options.length; i++) {
applyAnnotations(inner.options[i], json.anyOf[i]);
}
}
return json;
}
function ajvErrorToValidationErrorDetail(error) {
return {
path: error.instancePath.substring(1).replace(/\//g, ".") || "(root)",
message: error.message
};
}
function cfWorkerErrorToValidationErrorDetail(error) {
const path = error.instanceLocation.startsWith("#/") ? error.instanceLocation.substring(2) : "";
return {
path: path.replace(/\//g, ".") || "(root)",
message: error.error
};
}
function validateSchema(data, options) {
const toValidate = toJsonSchema(options);
if (!toValidate) {
return { valid: true, schema: toValidate };
}
const validationMode = getGenkitRuntimeConfig().jsonSchemaMode;
if (validationMode === "interpret") {
let validator2 = cfWorkerValidators.get(toValidate);
if (!validator2) {
validator2 = new Validator(toValidate);
cfWorkerValidators.set(toValidate, validator2);
}
const result = validator2.validate(sanitizeForJsonSchema(data));
if (!result.valid) {
return {
valid: false,
errors: result.errors.map(cfWorkerErrorToValidationErrorDetail),
schema: toValidate
};
}
return {
valid: result.valid,
schema: toValidate
};
}
let validator = ajvValidators.get(toValidate);
if (!validator) {
validator = ajv.compile(toValidate);
ajvValidators.set(toValidate, validator);
}
const valid = validator(data);
if (!valid) {
return {
valid: false,
errors: (validator.errors ?? []).map(ajvErrorToValidationErrorDetail),
schema: toValidate
};
}
return { valid, schema: toValidate };
}
function parseSchema(data, options) {
const result = validateSchema(data, options);
if (!result.valid) {
throw new ValidationError({
data,
errors: result.errors,
schema: result.schema
});
}
return data;
}
function defineSchema(registry, name, schema) {
registry.registerSchema(name, { schema });
return schema;
}
function defineJsonSchema(registry, name, jsonSchema) {
registry.registerSchema(name, { jsonSchema });
return jsonSchema;
}
function sanitizeForJsonSchema(data) {
if (Array.isArray(data)) {
return data.map(sanitizeForJsonSchema);
} else if (data !== null && typeof data === "object") {
const out = {};
for (const key in data) {
if (data[key] !== void 0) {
out[key] = sanitizeForJsonSchema(data[key]);
}
}
return out;
}
return data;
}
export {
ValidationError,
annotateSchema,
defineJsonSchema,
defineSchema,
parseSchema,
toJsonSchema,
validateSchema,
z
};
//# sourceMappingURL=schema.mjs.map