package config import ( "fmt" "strings" "gitea.maximumdirect.net/eric/narratio/internal/pathsafe" ) // 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.Campaign == nil { return fmt.Errorf("campaign config is required") } if cfg.Session == nil { return fmt.Errorf("session config is required") } if err := ValidatePipelineConfig(cfg.Pipeline); err != nil { return fmt.Errorf("pipeline config %q invalid: %w", shortName(cfg.PipelinePath, "pipeline.yml"), err) } if err := ValidateCampaignConfig(cfg.Campaign); err != nil { return fmt.Errorf("campaign config %q invalid: %w", shortName(cfg.CampaignPath, "campaign.yml"), err) } if err := validateSession(cfg.Session); err != nil { return fmt.Errorf("session config %q invalid: %w", shortName(cfg.SessionPath, "session.yml"), err) } if err := validateResolvedPartyInputs(cfg); err != nil { return fmt.Errorf("campaign/session config invalid: %w", err) } if err := validateCrossConfig(cfg.Pipeline, cfg.Session, cfg.StableInputs); err != nil { return fmt.Errorf("pipeline/session config invalid: %w", err) } return nil } // ValidatePipelineConfig validates a loaded, defaulted pipeline without // requiring session configuration. Callers that require party-driven artifact // expansion must resolve a campaign first through LoadPipelineCampaign. func ValidatePipelineConfig(cfg *PipelineConfig) error { if cfg == nil { return fmt.Errorf("pipeline config is required") } return validatePipeline(cfg) } // ValidateCampaignConfig validates a loaded campaign without requiring a // session. Party parsing and canonical-party checks remain owned by // LoadPipelineCampaign, which has the campaign source path available. func ValidateCampaignConfig(cfg *CampaignConfig) error { return validateCampaign(cfg) } func validatePipeline(cfg *PipelineConfig) error { if strings.TrimSpace(cfg.Workspace.Root) == "" { return fmt.Errorf("pipeline.workspace.root is required") } if campaignID := cfg.Campaigns.DefaultCampaignID; campaignID != "" { if err := pathsafe.ValidateOpaqueSegment(campaignID); err != nil { return fmt.Errorf("pipeline.campaigns.default_campaign_id: %w", err) } } if err := validateSecrets(cfg.Secrets); err != nil { return err } if err := validateStorage(cfg.Storage); err != nil { return err } if err := validateSpool(cfg.Spool); err != nil { return err } if err := validateCache(cfg.Cache); err != nil { return err } if err := validateNotarius(cfg.Notarius, cfg.Scriptorium); err != nil { return err } if err := validatePublish(cfg.Publish, cfg.Scriptorium, cfg.Notarius); err != nil { return err } if err := validateWhisperX(cfg.WhisperX); err != nil { return err } if err := validateSeriatim(cfg.Seriatim); err != nil { return err } if err := validateAudita(cfg.Audita); err != nil { return err } if err := validateNormalize(cfg.Normalize); err != nil { return err } if err := validateTrim(cfg.Trim); err != nil { return err } if err := validateRender(cfg.Render); err != nil { return err } if err := validateScriptorium(cfg.Scriptorium, cfg.Notarius); err != nil { return err } if err := validateNotification(cfg.Notification); err != nil { return err } return nil }