Apply Task 1 review fixes: error wrapping, validation, tests, go.mod fix, package rename
- commit
- 5b5bc74aaa9e67e0c376dd4786e214d26acc9af6
- author
- brice · · 2026-04-09 05:12 UTC
- parent
- c274f18
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +93 -5
M cmd/pipewiregen/internal/parser/parser.go +44 -1
| @@ -2,6 +2,7 @@ package parser | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "encoding/json" | |
| 5 | + | "fmt" | |
| 5 | 6 | "os" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |
| @@ -11,11 +12,53 @@ import ( | |||
| 11 | 12 | func Load(path string) (*model.Model, error) { | |
| 12 | 13 | b, err := os.ReadFile(path) | |
| 13 | 14 | if err != nil { | |
| 14 | - | return nil, err | |
| 15 | + | return nil, fmt.Errorf("failed to read file %s: %w", path, err) | |
| 15 | 16 | } | |
| 16 | 17 | var m model.Model | |
| 17 | 18 | if err := json.Unmarshal(b, &m); err != nil { | |
| 19 | + | return nil, fmt.Errorf("failed to unmarshal JSON from %s: %w", path, err) | |
| 20 | + | } | |
| 21 | + | if err := validate(&m); err != nil { | |
| 18 | 22 | return nil, err | |
| 19 | 23 | } | |
| 20 | 24 | return &m, nil | |
| 21 | 25 | } | |
| 26 | + | ||
| 27 | + | func validate(m *model.Model) error { | |
| 28 | + | // Build set of valid library names | |
| 29 | + | libNames := make(map[string]bool) | |
| 30 | + | for i, lib := range m.Libraries { | |
| 31 | + | if lib.Name == "" { | |
| 32 | + | return fmt.Errorf("library at index %d has empty name", i) | |
| 33 | + | } | |
| 34 | + | if lib.SOName == "" { | |
| 35 | + | return fmt.Errorf("library %q has empty soname", lib.Name) | |
| 36 | + | } | |
| 37 | + | libNames[lib.Name] = true | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | // Validate groups | |
| 41 | + | for i, group := range m.Groups { | |
| 42 | + | if group.Name == "" { | |
| 43 | + | return fmt.Errorf("group at index %d has empty name", i) | |
| 44 | + | } | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | // Validate symbols | |
| 48 | + | for i, sym := range m.Symbols { | |
| 49 | + | if sym.Name == "" { | |
| 50 | + | return fmt.Errorf("symbol at index %d has empty name", i) | |
| 51 | + | } | |
| 52 | + | if sym.Library == "" { | |
| 53 | + | return fmt.Errorf("symbol %q has empty library", sym.Name) | |
| 54 | + | } | |
| 55 | + | if sym.Signature == "" { | |
| 56 | + | return fmt.Errorf("symbol %q has empty signature", sym.Name) | |
| 57 | + | } | |
| 58 | + | if !libNames[sym.Library] { | |
| 59 | + | return fmt.Errorf("symbol %q references unknown library %q", sym.Name, sym.Library) | |
| 60 | + | } | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | return nil | |
| 64 | + | } | |
| @@ -2,6 +2,7 @@ package parser | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "encoding/json" | 4 | "encoding/json" | ||
| 5 | + | "fmt" | |||
| 5 | "os" | 6 | "os" | ||
| 6 | 7 | ||||
| 7 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | 8 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | ||
| @@ -11,11 +12,53 @@ import ( | |||||
| 11 | func Load(path string) (*model.Model, error) { | 12 | func Load(path string) (*model.Model, error) { | ||
| 12 | b, err := os.ReadFile(path) | 13 | b, err := os.ReadFile(path) | ||
| 13 | if err != nil { | 14 | if err != nil { | ||
| 14 | - | return nil, err | 15 | + | return nil, fmt.Errorf("failed to read file %s: %w", path, err) |
| 15 | } | 16 | } | ||
| 16 | var m model.Model | 17 | var m model.Model | ||
| 17 | if err := json.Unmarshal(b, &m); err != nil { | 18 | if err := json.Unmarshal(b, &m); err != nil { | ||
| 19 | + | return nil, fmt.Errorf("failed to unmarshal JSON from %s: %w", path, err) | |||
| 20 | + | } | |||
| 21 | + | if err := validate(&m); err != nil { | |||
| 18 | return nil, err | 22 | return nil, err | ||
| 19 | } | 23 | } | ||
| 20 | return &m, nil | 24 | return &m, nil | ||
| 21 | } | 25 | } | ||
| 26 | + | ||||
| 27 | + | func validate(m *model.Model) error { | |||
| 28 | + | // Build set of valid library names | |||
| 29 | + | libNames := make(map[string]bool) | |||
| 30 | + | for i, lib := range m.Libraries { | |||
| 31 | + | if lib.Name == "" { | |||
| 32 | + | return fmt.Errorf("library at index %d has empty name", i) | |||
| 33 | + | } | |||
| 34 | + | if lib.SOName == "" { | |||
| 35 | + | return fmt.Errorf("library %q has empty soname", lib.Name) | |||
| 36 | + | } | |||
| 37 | + | libNames[lib.Name] = true | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | // Validate groups | |||
| 41 | + | for i, group := range m.Groups { | |||
| 42 | + | if group.Name == "" { | |||
| 43 | + | return fmt.Errorf("group at index %d has empty name", i) | |||
| 44 | + | } | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | // Validate symbols | |||
| 48 | + | for i, sym := range m.Symbols { | |||
| 49 | + | if sym.Name == "" { | |||
| 50 | + | return fmt.Errorf("symbol at index %d has empty name", i) | |||
| 51 | + | } | |||
| 52 | + | if sym.Library == "" { | |||
| 53 | + | return fmt.Errorf("symbol %q has empty library", sym.Name) | |||
| 54 | + | } | |||
| 55 | + | if sym.Signature == "" { | |||
| 56 | + | return fmt.Errorf("symbol %q has empty signature", sym.Name) | |||
| 57 | + | } | |||
| 58 | + | if !libNames[sym.Library] { | |||
| 59 | + | return fmt.Errorf("symbol %q references unknown library %q", sym.Name, sym.Library) | |||
| 60 | + | } | |||
| 61 | + | } | |||
| 62 | + | ||||
| 63 | + | return nil | |||
| 64 | + | } | |||
M cmd/pipewiregen/internal/parser/parser_test.go +34 -1
| @@ -1,6 +1,9 @@ | |||
| 1 | 1 | package parser | |
| 2 | 2 | ||
| 3 | - | import "testing" | |
| 3 | + | import ( | |
| 4 | + | "strings" | |
| 5 | + | "testing" | |
| 6 | + | ) | |
| 4 | 7 | ||
| 5 | 8 | func TestLoadParsesLibraryAndSymbolGroups(t *testing.T) { | |
| 6 | 9 | model, err := Load("testdata/minimal_pipewire.json") | |
| @@ -14,3 +17,33 @@ func TestLoadParsesLibraryAndSymbolGroups(t *testing.T) { | |||
| 14 | 17 | t.Fatalf("group=%q want %q", got, want) | |
| 15 | 18 | } | |
| 16 | 19 | } | |
| 20 | + | ||
| 21 | + | func TestLoadMissingFileReturnsError(t *testing.T) { | |
| 22 | + | _, err := Load("testdata/nonexistent_file.json") | |
| 23 | + | if err == nil { | |
| 24 | + | t.Fatal("expected error for missing file, got nil") | |
| 25 | + | } | |
| 26 | + | if !strings.Contains(err.Error(), "failed to read file") { | |
| 27 | + | t.Fatalf("error message should contain 'failed to read file', got: %v", err) | |
| 28 | + | } | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func TestLoadInvalidJSONReturnsError(t *testing.T) { | |
| 32 | + | _, err := Load("testdata/invalid.json") | |
| 33 | + | if err == nil { | |
| 34 | + | t.Fatal("expected error for invalid JSON, got nil") | |
| 35 | + | } | |
| 36 | + | if !strings.Contains(err.Error(), "failed to unmarshal JSON") { | |
| 37 | + | t.Fatalf("error message should contain 'failed to unmarshal JSON', got: %v", err) | |
| 38 | + | } | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | func TestLoadSymbolWithUnknownLibraryReturnsError(t *testing.T) { | |
| 42 | + | _, err := Load("testdata/unknown_library.json") | |
| 43 | + | if err == nil { | |
| 44 | + | t.Fatal("expected error for symbol with unknown library, got nil") | |
| 45 | + | } | |
| 46 | + | if !strings.Contains(err.Error(), "unknown library") { | |
| 47 | + | t.Fatalf("error message should contain 'unknown library', got: %v", err) | |
| 48 | + | } | |
| 49 | + | } | |
| @@ -1,6 +1,9 @@ | |||||
| 1 | package parser | 1 | package parser | ||
| 2 | 2 | ||||
| 3 | - | import "testing" | 3 | + | import ( |
| 4 | + | "strings" | |||
| 5 | + | "testing" | |||
| 6 | + | ) | |||
| 4 | 7 | ||||
| 5 | func TestLoadParsesLibraryAndSymbolGroups(t *testing.T) { | 8 | func TestLoadParsesLibraryAndSymbolGroups(t *testing.T) { | ||
| 6 | model, err := Load("testdata/minimal_pipewire.json") | 9 | model, err := Load("testdata/minimal_pipewire.json") | ||
| @@ -14,3 +17,33 @@ func TestLoadParsesLibraryAndSymbolGroups(t *testing.T) { | |||||
| 14 | t.Fatalf("group=%q want %q", got, want) | 17 | t.Fatalf("group=%q want %q", got, want) | ||
| 15 | } | 18 | } | ||
| 16 | } | 19 | } | ||
| 20 | + | ||||
| 21 | + | func TestLoadMissingFileReturnsError(t *testing.T) { | |||
| 22 | + | _, err := Load("testdata/nonexistent_file.json") | |||
| 23 | + | if err == nil { | |||
| 24 | + | t.Fatal("expected error for missing file, got nil") | |||
| 25 | + | } | |||
| 26 | + | if !strings.Contains(err.Error(), "failed to read file") { | |||
| 27 | + | t.Fatalf("error message should contain 'failed to read file', got: %v", err) | |||
| 28 | + | } | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func TestLoadInvalidJSONReturnsError(t *testing.T) { | |||
| 32 | + | _, err := Load("testdata/invalid.json") | |||
| 33 | + | if err == nil { | |||
| 34 | + | t.Fatal("expected error for invalid JSON, got nil") | |||
| 35 | + | } | |||
| 36 | + | if !strings.Contains(err.Error(), "failed to unmarshal JSON") { | |||
| 37 | + | t.Fatalf("error message should contain 'failed to unmarshal JSON', got: %v", err) | |||
| 38 | + | } | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | func TestLoadSymbolWithUnknownLibraryReturnsError(t *testing.T) { | |||
| 42 | + | _, err := Load("testdata/unknown_library.json") | |||
| 43 | + | if err == nil { | |||
| 44 | + | t.Fatal("expected error for symbol with unknown library, got nil") | |||
| 45 | + | } | |||
| 46 | + | if !strings.Contains(err.Error(), "unknown library") { | |||
| 47 | + | t.Fatalf("error message should contain 'unknown library', got: %v", err) | |||
| 48 | + | } | |||
| 49 | + | } | |||
A cmd/pipewiregen/internal/parser/testdata/invalid.json +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | { this is not valid json }⏎̸ | |
| @@ -0,0 +1,1 @@ | |||||
| 1 | + | { this is not valid json }⏎̸ | |||
A cmd/pipewiregen/internal/parser/testdata/unknown_library.json +11 -0
| @@ -0,0 +1,11 @@ | |||
| 1 | + | { | |
| 2 | + | "libraries": [ | |
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | + | ], | |
| 5 | + | "groups": [ | |
| 6 | + | {"name": "init", "symbols": ["pw_init"]} | |
| 7 | + | ], | |
| 8 | + | "symbols": [ | |
| 9 | + | {"name": "pw_init", "library": "nonexistent", "signature": "func()"} | |
| 10 | + | ] | |
| 11 | + | } | |
| @@ -0,0 +1,11 @@ | |||||
| 1 | + | { | |||
| 2 | + | "libraries": [ | |||
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |||
| 4 | + | ], | |||
| 5 | + | "groups": [ | |||
| 6 | + | {"name": "init", "symbols": ["pw_init"]} | |||
| 7 | + | ], | |||
| 8 | + | "symbols": [ | |||
| 9 | + | {"name": "pw_init", "library": "nonexistent", "signature": "func()"} | |||
| 10 | + | ] | |||
| 11 | + | } | |||
M cmd/pipewiregen/internal/pipewiregen/normalize.go +2 -2
| @@ -1,3 +1,3 @@ | |||
| 1 | - | // Package pipewiregen contains PipeWire-specific naming and grouping rules | |
| 1 | + | // Package normalize contains PipeWire-specific naming and grouping rules | |
| 2 | 2 | // for the code generator. | |
| 3 | - | package pipewiregen | |
| 3 | + | package normalize | |
| @@ -1,3 +1,3 @@ | |||||
| 1 | - | // Package pipewiregen contains PipeWire-specific naming and grouping rules | 1 | + | // Package normalize contains PipeWire-specific naming and grouping rules |
| 2 | // for the code generator. | 2 | // for the code generator. | ||
| 3 | - | package pipewiregen | 3 | + | package normalize |
M go.mod +1 -1
| @@ -1,3 +1,3 @@ | |||
| 1 | 1 | module github.com/bnema/purego-pipewire | |
| 2 | 2 | ||
| 3 | - | go 1.24.0 | |
| 3 | + | go 1.23 | |
| @@ -1,3 +1,3 @@ | |||||
| 1 | module github.com/bnema/purego-pipewire | 1 | module github.com/bnema/purego-pipewire | ||
| 2 | 2 | ||||
| 3 | - | go 1.24.0 | 3 | + | go 1.23 |