package config import ( "fmt" "io" "os" "path/filepath" "gopkg.in/yaml.v3" ) // LoadPipeline loads pipeline configuration from a YAML file with strict field checking. func LoadPipeline(path string) (*PipelineConfig, error) { var cfg PipelineConfig if err := decodeStrictYAML("pipeline", path, &cfg); err != nil { return nil, fmt.Errorf("load pipeline config: %w", err) } return &cfg, nil } // LoadSession loads session configuration from a YAML file with strict field checking. func LoadSession(path string) (*SessionConfig, error) { var cfg SessionConfig if err := decodeStrictYAML("session", path, &cfg); err != nil { return nil, fmt.Errorf("load session config: %w", err) } return &cfg, nil } // Load loads and resolves combined pipeline and session configuration. func Load(pipelinePath, sessionPath string) (*Config, error) { pipelineCfg, err := LoadPipeline(pipelinePath) if err != nil { return nil, err } sessionCfg, err := LoadSession(sessionPath) if err != nil { return nil, err } return &Config{ Pipeline: pipelineCfg, Session: sessionCfg, PipelinePath: pipelinePath, SessionPath: sessionPath, }, nil } func decodeStrictYAML(kind, path string, out any) error { f, err := os.Open(path) if err != nil { return fmt.Errorf("%s file %q: open: %w", kind, path, err) } defer f.Close() dec := yaml.NewDecoder(f) dec.KnownFields(true) if err := dec.Decode(out); err != nil { return fmt.Errorf("%s file %q: strict decode failed: %w", kind, path, err) } var extra any if err := dec.Decode(&extra); err != nil && err != io.EOF { return fmt.Errorf("%s file %q: trailing content decode failed: %w", kind, path, err) } return nil } func shortName(path, fallback string) string { base := filepath.Base(path) if base == "." || base == string(filepath.Separator) { return fallback } return base }