package config import ( "fmt" "net/url" "strings" "time" ) // Validate checks resolved configuration for required fields and parseable durations. func Validate(cfg *Config) error { if cfg == nil { return fmt.Errorf("config is nil") } if cfg.Pipeline == nil { return fmt.Errorf("pipeline config is required") } if cfg.Session == nil { return fmt.Errorf("session config is required") } if err := validatePipeline(cfg.Pipeline); err != nil { return fmt.Errorf("pipeline config %q invalid: %w", shortName(cfg.PipelinePath, "pipeline.yml"), err) } if err := validateSession(cfg.Session); err != nil { return fmt.Errorf("session config %q invalid: %w", shortName(cfg.SessionPath, "session.yml"), err) } return nil } func validatePipeline(cfg *PipelineConfig) error { if strings.TrimSpace(cfg.Workspace.Root) == "" { return fmt.Errorf("pipeline.workspace.root is required") } if err := validateWhisperX(cfg.WhisperX); err != nil { return err } if err := validateDuration("pipeline.seriatim.timeout", cfg.Seriatim.Timeout); err != nil { return err } if err := validateDuration("pipeline.audita.timeout", cfg.Audita.Timeout); err != nil { return err } if err := validateDuration("pipeline.analyzer.timeout", cfg.Analyzer.Timeout); err != nil { return err } if err := validateDuration("pipeline.notification.timeout", cfg.Notification.Timeout); err != nil { return err } return nil } func validateWhisperX(cfg WhisperXConfig) error { if strings.TrimSpace(cfg.TranscribeURL) == "" { return fmt.Errorf("pipeline.whisperx.transcribe_url is required") } u, err := url.Parse(cfg.TranscribeURL) if err != nil || u.Scheme == "" || u.Host == "" { if err != nil { return fmt.Errorf("pipeline.whisperx.transcribe_url must be a valid URL: %w", err) } return fmt.Errorf("pipeline.whisperx.transcribe_url must be a valid URL") } if err := validateDuration("pipeline.whisperx.timeout", cfg.Timeout); err != nil { return err } if err := validateDuration("pipeline.whisperx.retry_delay", cfg.RetryDelay); err != nil { return err } if cfg.Retries == nil { return fmt.Errorf("pipeline.whisperx.retries must be set (defaults should populate this)") } if *cfg.Retries < 0 { return fmt.Errorf("pipeline.whisperx.retries must be >= 0") } if cfg.Concurrency == nil { return fmt.Errorf("pipeline.whisperx.concurrency must be set (defaults should populate this)") } if *cfg.Concurrency <= 0 { return fmt.Errorf("pipeline.whisperx.concurrency must be > 0") } return nil } func validateSession(cfg *SessionConfig) error { if strings.TrimSpace(cfg.SessionID) == "" { return fmt.Errorf("session.session_id is required") } if strings.TrimSpace(cfg.Inputs.SpeakersFile) == "" { return fmt.Errorf("session.inputs.speakers_file is required") } if strings.TrimSpace(cfg.Inputs.AutocorrectFile) == "" { return fmt.Errorf("session.inputs.autocorrect_file is required") } if strings.TrimSpace(cfg.Inputs.GlossaryFile) == "" { return fmt.Errorf("session.inputs.glossary_file is required") } hasAudioDir := strings.TrimSpace(cfg.Inputs.AudioDir) != "" hasAudioFiles := len(cfg.Inputs.AudioFiles) > 0 if !hasAudioDir && !hasAudioFiles { return fmt.Errorf("session.inputs requires audio_dir or at least one audio_files entry") } return nil } func validateDuration(fieldName, value string) error { trimmed := strings.TrimSpace(value) if trimmed == "" { return nil } if _, err := time.ParseDuration(trimmed); err != nil { return fmt.Errorf("%s must be a valid duration: %w", fieldName, err) } return nil }