feat: conditionally include error handling in generated client code templates

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2026-01-01 19:53:07 -05:00
parent 9e9d5cd615
commit 68b4e2052c
10 changed files with 121 additions and 18 deletions

View File

@ -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{

View File

@ -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}}

View File

@ -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 .}}),

View File

@ -41,8 +41,10 @@ struct {{responseType .}} {
#[serde(default)]
{{.RustName}}: {{rustType .}},
{{- end}}
{{- if .HasError}}
#[serde(default)]
error: Option<String>,
{{- 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}})

View File

@ -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}}

View File

@ -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")

View File

@ -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.

View File

@ -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)

View File

@ -17,8 +17,6 @@ struct CounterCountRequest {
struct CounterCountResponse {
#[serde(default)]
value: i32,
#[serde(default)]
error: Option<String>,
}
#[host_fn]
@ -43,9 +41,5 @@ pub fn count(name: &str) -> Result<i32, Error> {
}))?
};
if let Some(err) = response.0.error {
return Err(Error::msg(err));
}
Ok(response.0.value)
}

View File

@ -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.