Harden initial framework skeleton
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user