import type { CustomField } from './custom-field.ts'; export interface ValidationResult { valid: boolean; error?: string; } /** * Dispatch to the correct type-specific validator based on field.field_type. */ export function validateCustomFieldValue( field: CustomField, value: string, ): ValidationResult { if (!value) return { valid: true }; const config = (field.validation_config ?? {}) as Record; // Backward-compatible pattern check (field-level pattern, not validation_config) if (field.pattern) { try { const regex = new RegExp(field.pattern); if (!regex.test(value)) { return { valid: false, error: 'Value does not match the required pattern' }; } } catch { // Invalid regex — skip } } switch (field.field_type) { case 'Number': return validateNumberValue(value, config); case 'Date': return validateDateValue(value, config); case 'DateTime': return validateDateTimeValue(value, config); case 'SelectOne': case 'SelectMultiple': return validateSelectValue(value, field, config); case 'Text': case 'Textarea': return validateTextValue(value, config); default: return { valid: true }; } } function validateNumberValue( value: string, config: Record, ): ValidationResult { const num = Number(value); if (isNaN(num)) { return { valid: false, error: 'Value must be a number' }; } if (config.min !== undefined && num < Number(config.min)) { return { valid: false, error: `Value must be at least ${config.min}` }; } if (config.max !== undefined && num > Number(config.max)) { return { valid: false, error: `Value must be at most ${config.max}` }; } return { valid: true }; } function validateDateValue( value: string, config: Record, ): ValidationResult { // Accept YYYY-MM-DD format if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { return { valid: false, error: 'Value must be a date in YYYY-MM-DD format' }; } const parsed = new Date(value); if (isNaN(parsed.getTime())) { return { valid: false, error: 'Invalid date' }; } if (config.min_date) { const minDate = new Date(String(config.min_date)); if (!isNaN(minDate.getTime()) && parsed < minDate) { return { valid: false, error: `Date must be on or after ${config.min_date}` }; } } if (config.max_date) { const maxDate = new Date(String(config.max_date)); if (!isNaN(maxDate.getTime()) && parsed > maxDate) { return { valid: false, error: `Date must be on or before ${config.max_date}` }; } } return { valid: true }; } function validateDateTimeValue( value: string, config: Record, ): ValidationResult { const parsed = new Date(value); if (isNaN(parsed.getTime())) { return { valid: false, error: 'Value must be a valid ISO 8601 datetime' }; } if (config.min_date) { const minDate = new Date(String(config.min_date)); if (!isNaN(minDate.getTime()) && parsed < minDate) { return { valid: false, error: `Datetime must be on or after ${config.min_date}` }; } } if (config.max_date) { const maxDate = new Date(String(config.max_date)); if (!isNaN(maxDate.getTime()) && parsed > maxDate) { return { valid: false, error: `Datetime must be on or before ${config.max_date}` }; } } return { valid: true }; } function validateSelectValue( value: string, field: CustomField, config: Record, ): ValidationResult { const allowed: string[] = []; // Check validation_config.options first, then fall back to field.values if (Array.isArray(config.options)) { allowed.push(...config.options.map(String)); } else if (Array.isArray(field.values)) { allowed.push(...field.values.map(String)); } if (allowed.length > 0 && !allowed.includes(value)) { return { valid: false, error: `Value is not an allowed option. Allowed: ${allowed.join(', ')}` }; } return { valid: true }; } function validateTextValue( value: string, config: Record, ): ValidationResult { if (config.min_length !== undefined && value.length < Number(config.min_length)) { return { valid: false, error: `Value must be at least ${config.min_length} characters` }; } if (config.max_length !== undefined && value.length > Number(config.max_length)) { return { valid: false, error: `Value must be at most ${config.max_length} characters` }; } if (config.pattern && typeof config.pattern === 'string') { try { const regex = new RegExp(config.pattern); if (!regex.test(value)) { return { valid: false, error: 'Value does not match the required pattern' }; } } catch { // Invalid regex — skip validation } } return { valid: true }; }