Harden initial framework skeleton

This commit is contained in:
2026-05-02 11:44:15 -05:00
parent bec784f1ec
commit 96540cebd4
19 changed files with 194 additions and 75 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
@@ -11,7 +12,7 @@ import (
// LoadPipeline loads pipeline configuration from a YAML file with strict field checking.
func LoadPipeline(path string) (*PipelineConfig, error) {
var cfg PipelineConfig
if err := decodeStrictYAML(path, &cfg); err != nil {
if err := decodeStrictYAML("pipeline", path, &cfg); err != nil {
return nil, fmt.Errorf("load pipeline config: %w", err)
}
return &cfg, nil
@@ -20,7 +21,7 @@ func LoadPipeline(path string) (*PipelineConfig, error) {
// LoadSession loads session configuration from a YAML file with strict field checking.
func LoadSession(path string) (*SessionConfig, error) {
var cfg SessionConfig
if err := decodeStrictYAML(path, &cfg); err != nil {
if err := decodeStrictYAML("session", path, &cfg); err != nil {
return nil, fmt.Errorf("load session config: %w", err)
}
return &cfg, nil
@@ -46,23 +47,31 @@ func Load(pipelinePath, sessionPath string) (*Config, error) {
}, nil
}
func decodeStrictYAML(path string, out any) error {
func decodeStrictYAML(kind, path string, out any) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("open %q: %w", path, err)
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("decode %q: %w", path, err)
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("decode trailing content in %q: %w", path, err)
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
}

View File

@@ -51,7 +51,7 @@ inputs:
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
`,
wantLoadErr: "field bogus not found",
wantLoadErr: "pipeline file",
},
{
name: "unknown session field fails",
@@ -66,7 +66,7 @@ inputs:
glossary_file: ./glossary.yml
unknown_field: true
`,
wantLoadErr: "field unknown_field not found",
wantLoadErr: "session file",
},
{
name: "missing required field fails",
@@ -80,7 +80,7 @@ inputs:
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
`,
wantValidate: "session.session_id is required",
wantValidate: "session config \"session.yml\" invalid: session.session_id is required",
},
{
name: "invalid timeout fails",
@@ -96,7 +96,7 @@ inputs:
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
`,
wantValidate: "pipeline.whisperx.timeout must be a valid duration",
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.whisperx.timeout must be a valid duration",
},
}
@@ -112,6 +112,9 @@ inputs:
if !strings.Contains(err.Error(), tt.wantLoadErr) {
t.Fatalf("load error = %q, want to contain %q", err.Error(), tt.wantLoadErr)
}
if !strings.Contains(err.Error(), "strict decode failed") {
t.Fatalf("load error = %q, want strict decode context", err.Error())
}
return
}
@@ -162,6 +165,22 @@ func TestValidateMissingAudioSource(t *testing.T) {
if !strings.Contains(err.Error(), "audio_dir or at least one audio_files") {
t.Fatalf("error = %q, want audio source guidance", err.Error())
}
if !strings.Contains(err.Error(), "session config") {
t.Fatalf("error = %q, want session config context", err.Error())
}
}
func TestExamplesLoadAndValidate(t *testing.T) {
pipelinePath := filepath.Join("..", "..", "examples", "pipeline.minimal.yml")
sessionPath := filepath.Join("..", "..", "examples", "session.minimal.yml")
cfg, err := Load(pipelinePath, sessionPath)
if err != nil {
t.Fatalf("Load(examples) error = %v", err)
}
if err := Validate(cfg); err != nil {
t.Fatalf("Validate(examples) error = %v", err)
}
}
func writeConfigFiles(t *testing.T, pipelineYAML, sessionYAML string) (string, string) {

View File

@@ -19,10 +19,10 @@ func Validate(cfg *Config) error {
}
if err := validatePipeline(cfg.Pipeline); err != nil {
return err
return fmt.Errorf("pipeline config %q invalid: %w", shortName(cfg.PipelinePath, "pipeline.yml"), err)
}
if err := validateSession(cfg.Session); err != nil {
return err
return fmt.Errorf("session config %q invalid: %w", shortName(cfg.SessionPath, "session.yml"), err)
}
return nil