import { FieldType, type BlueprintFieldDefinition } from "@capakraken/shared"; export interface CustomFieldValidationError { key: string; message: string; } /** * Validates a `dynamicFields` record against an array of BlueprintFieldDefinitions. * Returns an array of errors (empty = valid). */ export function validateCustomFields( fieldDefs: BlueprintFieldDefinition[], dynamicFields: Record, ): CustomFieldValidationError[] { const errors: CustomFieldValidationError[] = []; for (const def of fieldDefs) { const value = dynamicFields[def.key]; const isEmpty = value === undefined || value === null || value === ""; if (def.required && isEmpty) { errors.push({ key: def.key, message: `${def.label} is required` }); continue; } if (isEmpty) continue; switch (def.type) { case FieldType.NUMBER: { if (typeof value !== "number" && isNaN(Number(value))) { errors.push({ key: def.key, message: `${def.label} must be a number` }); } const validation = def.validation; if (validation) { const num = Number(value); if (validation.min !== undefined && num < validation.min) { errors.push({ key: def.key, message: `${def.label} must be at least ${validation.min}` }); } if (validation.max !== undefined && num > validation.max) { errors.push({ key: def.key, message: `${def.label} must be at most ${validation.max}` }); } } break; } case FieldType.BOOLEAN: if (value !== true && value !== false && value !== "true" && value !== "false") { errors.push({ key: def.key, message: `${def.label} must be a boolean` }); } break; case FieldType.SELECT: if (def.options && def.options.length > 0) { const valid = def.options.some((o) => o.value === value); if (!valid) { const allowed = def.options.map((o) => o.label || o.value).join(", "); errors.push({ key: def.key, message: `${def.label} must be one of: ${allowed}` }); } } break; case FieldType.MULTI_SELECT: if (Array.isArray(value) && def.options && def.options.length > 0) { const validSet = new Set(def.options.map((o) => o.value)); const invalid = (value as string[]).filter((v) => !validSet.has(v)); if (invalid.length > 0) { errors.push({ key: def.key, message: `${def.label} contains invalid values: ${invalid.join(", ")}` }); } } break; case FieldType.URL: try { new URL(String(value)); } catch { errors.push({ key: def.key, message: `${def.label} must be a valid URL` }); } break; case FieldType.EMAIL: if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))) { errors.push({ key: def.key, message: `${def.label} must be a valid email address` }); } break; // TEXT, TEXTAREA, DATE — no structural validation beyond required } } return errors; }