/** * System Config Validator * * Validates system configuration values against YAML schema */ import type { SystemConfigProperty, SystemConfigSchema } from './system-config-schema-types'; import { parseOperatorTrustedSubnets } from './trusted-sources'; /** * Validation result (discriminated union) * When valid=false, error is ALWAYS present * When valid=true, error is NEVER present */ export type ValidationResult = { valid: true } | { valid: false; error: string }; /** * Validate value against property schema */ export function validateValue( key: string, value: string, property: SystemConfigProperty, ): ValidationResult { // Type validation if (property.type === 'integer') { const num = Number.parseInt(value, 10); if (Number.isNaN(num)) { return { valid: false, error: `${key} must be an integer` }; } if (property.minimum !== undefined && num < property.minimum) { return { valid: false, error: `${key} must be >= ${property.minimum}` }; } if (property.maximum !== undefined && num > property.maximum) { return { valid: false, error: `${key} must be <= ${property.maximum}` }; } } if (property.type === 'string' && property.pattern) { const regex = new RegExp(property.pattern); if (!regex.test(value)) { return { valid: false, error: `${key} does not match required pattern.\nExpected format: ${property.description}`, }; } } // Email format validation (simple) if (property.format === 'email') { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { return { valid: false, error: `${key} must be a valid email address` }; } } // CIDR lists validate through the key's own parser, so the write-time check // and the converge-time read can never disagree about what is valid. if (property.format === 'cidr-list') { try { parseOperatorTrustedSubnets(value); } catch (e) { return { valid: false, error: e instanceof Error ? e.message : String(e) }; } } return { valid: true }; } /** * Validate system config key exists in schema */ export function validateKey(key: string, schema: SystemConfigSchema): ValidationResult { if (!schema.properties[key]) { const validKeys = Object.keys(schema.properties).sort(); return { valid: false, error: `Invalid system config key '${key}'.\n\nValid keys:\n ${validKeys.join('\n ')}`, }; } return { valid: true }; }