diff --git a/plugins/cmd/ndpgen/internal/generator_test.go b/plugins/cmd/ndpgen/internal/generator_test.go index 449b648b6..58ba64788 100644 --- a/plugins/cmd/ndpgen/internal/generator_test.go +++ b/plugins/cmd/ndpgen/internal/generator_test.go @@ -586,6 +586,101 @@ var _ = Describe("Generator", func() { }) }) + Describe("GenerateClientGo", func() { + It("should include errors import when service has methods with errors", func() { + svc := Service{ + Name: "Cache", + Permission: "cache", + Interface: "CacheService", + Methods: []Method{ + { + Name: "Get", + HasError: true, + Params: []Param{NewParam("key", "string")}, + Returns: []Param{NewParam("value", "string")}, + }, + }, + } + + code, err := GenerateClientGo(svc, "host") + Expect(err).NotTo(HaveOccurred()) + + // Verify the code is valid Go (can't actually compile without wasip1) + codeStr := string(code) + + // Check for errors import when methods have errors + Expect(codeStr).To(ContainSubstring(`"errors"`)) + Expect(codeStr).To(ContainSubstring("errors.New")) + }) + + It("should not include errors import when service has no methods with errors", func() { + svc := Service{ + Name: "Config", + Permission: "config", + Interface: "ConfigService", + Methods: []Method{ + { + Name: "Get", + HasError: false, + Params: []Param{NewParam("key", "string")}, + Returns: []Param{NewParam("value", "string"), NewParam("exists", "bool")}, + }, + { + Name: "List", + HasError: false, + Params: []Param{NewParam("prefix", "string")}, + Returns: []Param{NewParam("keys", "[]string")}, + }, + }, + } + + code, err := GenerateClientGo(svc, "host") + Expect(err).NotTo(HaveOccurred()) + + codeStr := string(code) + + // Check that errors is NOT imported when no methods have errors + Expect(codeStr).NotTo(ContainSubstring(`"errors"`)) + Expect(codeStr).NotTo(ContainSubstring("errors.New")) + }) + + It("should generate valid Go code structure", func() { + svc := Service{ + Name: "SubsonicAPI", + Permission: "subsonicapi", + Interface: "SubsonicAPIService", + Methods: []Method{ + { + Name: "Call", + HasError: true, + Params: []Param{NewParam("uri", "string")}, + Returns: []Param{NewParam("response", "string")}, + }, + }, + } + + code, err := GenerateClientGo(svc, "host") + Expect(err).NotTo(HaveOccurred()) + + codeStr := string(code) + + // Check for generated header + Expect(codeStr).To(ContainSubstring("Code generated by ndpgen. DO NOT EDIT.")) + + // Check for build tag + Expect(codeStr).To(ContainSubstring("//go:build wasip1")) + + // Check for package declaration + Expect(codeStr).To(ContainSubstring("package host")) + + // Check for wasmimport directive + Expect(codeStr).To(ContainSubstring("//go:wasmimport extism:host/user")) + + // Check for PDK import + Expect(codeStr).To(ContainSubstring("github.com/navidrome/navidrome/plugins/pdk/go/pdk")) + }) + }) + Describe("GenerateClientGoStub", func() { It("should generate valid mock code with testify/mock", func() { svc := Service{ diff --git a/plugins/cmd/ndpgen/internal/templates/client.go.tmpl b/plugins/cmd/ndpgen/internal/templates/client.go.tmpl index a99796f66..a6ee04446 100644 --- a/plugins/cmd/ndpgen/internal/templates/client.go.tmpl +++ b/plugins/cmd/ndpgen/internal/templates/client.go.tmpl @@ -9,7 +9,9 @@ package {{.Package}} import ( "encoding/json" +{{- if .Service.HasErrors}} "errors" +{{- end}} "github.com/navidrome/navidrome/plugins/pdk/go/pdk" ) @@ -53,7 +55,9 @@ type {{responseType .}} struct { {{- range .Returns}} {{title .Name}} {{.Type}} `json:"{{.JSONName}},omitempty"` {{- end}} +{{- if .HasError}} Error string `json:"error,omitempty"` +{{- end}} } {{- end}} {{- end}} diff --git a/plugins/cmd/ndpgen/internal/templates/client.py.tmpl b/plugins/cmd/ndpgen/internal/templates/client.py.tmpl index 67053e134..99c5be51b 100644 --- a/plugins/cmd/ndpgen/internal/templates/client.py.tmpl +++ b/plugins/cmd/ndpgen/internal/templates/client.py.tmpl @@ -80,10 +80,11 @@ def {{pythonFunc .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.PythonNam response_offset = _{{exportName .}}(request_mem.offset) response_mem = extism.memory.find(response_offset) response = json.loads(extism.memory.string(response_mem)) - +{{if .HasError}} if response.get("error"): raise HostFunctionError(response["error"]) -{{if .NeedsResultClass}} +{{end}} +{{- if .NeedsResultClass}} return {{pythonResultType .}}( {{- range .Returns}} {{.PythonName}}=response.get("{{.JSONName}}"{{pythonDefault .}}), diff --git a/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl b/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl index 8fc01c1f3..39f5ea5ce 100644 --- a/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl +++ b/plugins/cmd/ndpgen/internal/templates/client.rs.tmpl @@ -41,8 +41,10 @@ struct {{responseType .}} { #[serde(default)] {{.RustName}}: {{rustType .}}, {{- end}} +{{- if .HasError}} #[serde(default)] error: Option, +{{- end}} } {{- end}} @@ -88,11 +90,12 @@ pub fn {{rustFunc .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.RustName {{exportName .}}(Json(serde_json::json!({})))? {{- end}} }; - +{{if .HasError}} if let Some(err) = response.0.error { return Err(Error::msg(err)); } -{{if eq (len .Returns) 0}} +{{end}} +{{- if eq (len .Returns) 0}} Ok(()) {{- else if eq (len .Returns) 1}} Ok(response.0.{{(index .Returns 0).RustName}}) diff --git a/plugins/cmd/ndpgen/internal/templates/host.go.tmpl b/plugins/cmd/ndpgen/internal/templates/host.go.tmpl index 39e6ad0ce..d10c01ee4 100644 --- a/plugins/cmd/ndpgen/internal/templates/host.go.tmpl +++ b/plugins/cmd/ndpgen/internal/templates/host.go.tmpl @@ -26,7 +26,9 @@ type {{responseType .}} struct { {{- range .Returns}} {{title .Name}} {{.Type}} `json:"{{.JSONName}},omitempty"` {{- end}} +{{- if .HasError}} Error string `json:"error,omitempty"` +{{- end}} } {{end}} diff --git a/plugins/cmd/ndpgen/internal/types.go b/plugins/cmd/ndpgen/internal/types.go index 99668bcc2..b46600d73 100644 --- a/plugins/cmd/ndpgen/internal/types.go +++ b/plugins/cmd/ndpgen/internal/types.go @@ -163,6 +163,16 @@ func (s Service) KnownStructs() map[string]bool { return result } +// HasErrors returns true if any method in the service returns an error. +func (s Service) HasErrors() bool { + for _, m := range s.Methods { + if m.HasError { + return true + } + } + return false +} + // Method represents a host function method within a service. type Method struct { Name string // Go method name (e.g., "Call") diff --git a/plugins/cmd/ndpgen/testdata/counter_client_expected.go.txt b/plugins/cmd/ndpgen/testdata/counter_client_expected.go.txt index 32f24e042..3fbb53727 100644 --- a/plugins/cmd/ndpgen/testdata/counter_client_expected.go.txt +++ b/plugins/cmd/ndpgen/testdata/counter_client_expected.go.txt @@ -9,7 +9,6 @@ package ndhost import ( "encoding/json" - "errors" "github.com/navidrome/navidrome/plugins/pdk/go/pdk" ) @@ -24,8 +23,7 @@ type counterCountRequest struct { } type counterCountResponse struct { - Value int32 `json:"value,omitempty"` - Error string `json:"error,omitempty"` + Value int32 `json:"value,omitempty"` } // CounterCount calls the counter_count host function. diff --git a/plugins/cmd/ndpgen/testdata/counter_client_expected.py b/plugins/cmd/ndpgen/testdata/counter_client_expected.py index 2af5b3b38..872d407bb 100644 --- a/plugins/cmd/ndpgen/testdata/counter_client_expected.py +++ b/plugins/cmd/ndpgen/testdata/counter_client_expected.py @@ -46,7 +46,4 @@ def counter_count(name: str) -> int: response_mem = extism.memory.find(response_offset) response = json.loads(extism.memory.string(response_mem)) - if response.get("error"): - raise HostFunctionError(response["error"]) - return response.get("value", 0) diff --git a/plugins/cmd/ndpgen/testdata/counter_client_expected.rs b/plugins/cmd/ndpgen/testdata/counter_client_expected.rs index 37b8f8605..a58dd8e1e 100644 --- a/plugins/cmd/ndpgen/testdata/counter_client_expected.rs +++ b/plugins/cmd/ndpgen/testdata/counter_client_expected.rs @@ -17,8 +17,6 @@ struct CounterCountRequest { struct CounterCountResponse { #[serde(default)] value: i32, - #[serde(default)] - error: Option, } #[host_fn] @@ -43,9 +41,5 @@ pub fn count(name: &str) -> Result { }))? }; - if let Some(err) = response.0.error { - return Err(Error::msg(err)); - } - Ok(response.0.value) } diff --git a/plugins/cmd/ndpgen/testdata/counter_expected.go.txt b/plugins/cmd/ndpgen/testdata/counter_expected.go.txt index f15f3c2a7..7fd1cbb84 100644 --- a/plugins/cmd/ndpgen/testdata/counter_expected.go.txt +++ b/plugins/cmd/ndpgen/testdata/counter_expected.go.txt @@ -16,8 +16,7 @@ type CounterCountRequest struct { // CounterCountResponse is the response type for Counter.Count. type CounterCountResponse struct { - Value int32 `json:"value,omitempty"` - Error string `json:"error,omitempty"` + Value int32 `json:"value,omitempty"` } // RegisterCounterHostFunctions registers Counter service host functions.