From cab656dbe5e72facfb492cf150086023ac500e10 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 26 Dec 2025 13:12:55 -0500 Subject: [PATCH] refactor: host function wrappers to use structured request and response types - Updated the host function signatures in `nd_host_artwork.go`, `nd_host_scheduler.go`, `nd_host_subsonicapi.go`, and `nd_host_websocket.go` to accept a single parameter for JSON requests. - Introduced structured request and response types for various cache operations in `nd_host_cache.go`. - Modified cache functions to marshal requests to JSON and unmarshal responses, improving error handling and code clarity. - Removed redundant memory allocation for string parameters in favor of JSON marshaling. - Enhanced error handling in WebSocket and cache operations to return structured error responses. --- plugins/cmd/hostgen/internal/generator.go | 320 +--------------- .../cmd/hostgen/internal/generator_test.go | 41 ++- .../internal/templates/client_go.go.tmpl | 101 ++---- .../hostgen/internal/templates/host.go.tmpl | 72 +--- plugins/cmd/hostgen/internal/types.go | 122 ------- .../hostgen/testdata/codec_client_expected.go | 19 +- .../cmd/hostgen/testdata/codec_expected.go | 28 +- .../testdata/counter_client_expected.go | 44 ++- .../cmd/hostgen/testdata/counter_expected.go | 59 ++- .../hostgen/testdata/echo_client_expected.go | 19 +- plugins/cmd/hostgen/testdata/echo_expected.go | 28 +- .../hostgen/testdata/list_client_expected.go | 23 +- plugins/cmd/hostgen/testdata/list_expected.go | 7 +- .../hostgen/testdata/math_client_expected.go | 21 +- plugins/cmd/hostgen/testdata/math_expected.go | 29 +- .../hostgen/testdata/meta_client_expected.go | 58 ++- plugins/cmd/hostgen/testdata/meta_expected.go | 49 ++- .../hostgen/testdata/ping_client_expected.go | 26 +- plugins/cmd/hostgen/testdata/ping_expected.go | 49 ++- .../testdata/search_client_expected.go | 19 +- .../cmd/hostgen/testdata/search_expected.go | 28 +- .../hostgen/testdata/store_client_expected.go | 17 +- .../cmd/hostgen/testdata/store_expected.go | 7 +- .../hostgen/testdata/users_client_expected.go | 27 +- .../cmd/hostgen/testdata/users_expected.go | 7 +- plugins/examples/crypto-ticker/main.go | 20 +- plugins/examples/crypto-ticker/nd_host.go | 188 ---------- .../crypto-ticker/nd_host_scheduler.go | 180 +++++++++ .../crypto-ticker/nd_host_websocket.go | 237 ++++++++++++ .../examples/discord-rich-presence/main.go | 2 +- .../discord-rich-presence/nd_host_artwork.go | 92 ++++- .../discord-rich-presence/nd_host_cache.go | 304 ++++++++++++---- .../nd_host_scheduler.go | 93 +++-- .../nd_host_websocket.go | 134 +++++-- plugins/examples/discord-rich-presence/rpc.go | 30 +- plugins/host/artwork_gen.go | 128 +++++-- plugins/host/cache_gen.go | 342 ++++++++++++------ plugins/host/go/nd_host_artwork.go | 92 ++++- plugins/host/go/nd_host_cache.go | 304 ++++++++++++---- plugins/host/go/nd_host_scheduler.go | 93 +++-- plugins/host/go/nd_host_subsonicapi.go | 19 +- plugins/host/go/nd_host_websocket.go | 134 +++++-- plugins/host/scheduler_gen.go | 113 +++--- plugins/host/subsonicapi_gen.go | 28 +- plugins/host/websocket_gen.go | 126 ++++--- .../testdata/fake-artwork/nd_host_artwork.go | 92 ++++- .../fake-scheduler/nd_host_scheduler.go | 93 +++-- .../nd_host_subsonicapi.go | 21 +- plugins/testdata/fake-websocket/main.go | 57 +-- .../fake-websocket/nd_host_websocket.go | 237 ++++++++++++ plugins/testdata/fake_cache_plugin/main.go | 30 +- .../fake_cache_plugin/nd_host_cache.go | 304 ++++++++++++---- 52 files changed, 3101 insertions(+), 1612 deletions(-) delete mode 100644 plugins/examples/crypto-ticker/nd_host.go create mode 100644 plugins/examples/crypto-ticker/nd_host_scheduler.go create mode 100644 plugins/examples/crypto-ticker/nd_host_websocket.go create mode 100644 plugins/testdata/fake-websocket/nd_host_websocket.go diff --git a/plugins/cmd/hostgen/internal/generator.go b/plugins/cmd/hostgen/internal/generator.go index e10c30ef8..4b609dcc4 100644 --- a/plugins/cmd/hostgen/internal/generator.go +++ b/plugins/cmd/hostgen/internal/generator.go @@ -14,45 +14,23 @@ var templatesFS embed.FS // hostFuncMap returns the template functions for host code generation. func hostFuncMap(svc Service) template.FuncMap { return template.FuncMap{ - "lower": strings.ToLower, - "title": strings.Title, - "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) }, - "requestType": func(m Method) string { return m.RequestTypeName(svc.Name) }, - "responseType": func(m Method) string { return m.ResponseTypeName(svc.Name) }, - "valueType": GoTypeToValueType, - "isSimple": IsSimpleType, - "isString": IsStringType, - "isBytes": IsBytesType, - "needsJSON": NeedsJSON, - "needsRequestType": func(m Method) bool { return m.NeedsRequestType() }, - "needsRespType": func(m Method) bool { return m.NeedsResponseType() }, - "isErrorOnly": func(m Method) bool { return m.IsErrorOnly() }, - "hasErrFromRead": hasErrorFromRead, - "readParam": generateReadParam, - "writeReturn": generateWriteReturn, - "encodeReturn": generateEncodeReturn, + "lower": strings.ToLower, + "title": strings.Title, + "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) }, + "requestType": func(m Method) string { return m.RequestTypeName(svc.Name) }, + "responseType": func(m Method) string { return m.ResponseTypeName(svc.Name) }, } } // clientFuncMap returns the template functions for client code generation. func clientFuncMap(svc Service) template.FuncMap { return template.FuncMap{ - "lower": strings.ToLower, - "title": strings.Title, - "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) }, - "responseType": func(m Method) string { return m.ResponseTypeName(svc.Name) }, - "isSimple": IsSimpleType, - "isString": IsStringType, - "isBytes": IsBytesType, - "needsJSON": NeedsJSON, - "needsRespType": func(m Method) bool { return m.NeedsResponseType() }, - "isErrorOnly": func(m Method) bool { return m.IsErrorOnly() }, - "wasmParamType": wasmParamType, - "wasmReturnType": wasmReturnType, - "wrapperReturnType": func(m Method, svcName string) string { return wrapperReturnType(m, svcName) }, - "clientCallArg": clientCallArg, - "decodeResult": decodeResult, - "formatDoc": formatDoc, + "lower": strings.ToLower, + "title": strings.Title, + "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) }, + "requestType": func(m Method) string { return m.RequestTypeName(svc.Name) }, + "responseType": func(m Method) string { return m.ResponseTypeName(svc.Name) }, + "formatDoc": formatDoc, } } @@ -69,11 +47,8 @@ func GenerateHost(svc Service, pkgName string) ([]byte, error) { } data := templateData{ - Package: pkgName, - Service: svc, - NeedsJSON: serviceNeedsJSON(svc), - NeedsWriteHelper: serviceNeedsWriteHelper(svc), - NeedsErrorHelper: serviceNeedsErrorHelper(svc), + Package: pkgName, + Service: svc, } var buf bytes.Buffer @@ -97,9 +72,7 @@ func GenerateClientGo(svc Service) ([]byte, error) { } data := templateData{ - Service: svc, - NeedsJSON: serviceClientNeedsJSON(svc), - NeedsErrors: serviceClientNeedsErrors(svc), + Service: svc, } var buf bytes.Buffer @@ -111,269 +84,8 @@ func GenerateClientGo(svc Service) ([]byte, error) { } type templateData struct { - Package string - Service Service - NeedsJSON bool - NeedsErrors bool // Client: needs "errors" import - NeedsWriteHelper bool - NeedsErrorHelper bool -} - -// serviceNeedsJSON returns true if any method needs JSON encoding. -func serviceNeedsJSON(svc Service) bool { - for _, m := range svc.Methods { - for _, p := range m.Params { - if NeedsJSON(p.Type) { - return true - } - } - for _, r := range m.Returns { - if NeedsJSON(r.Type) { - return true - } - } - // Error responses are also JSON - if m.HasError && m.NeedsResponseType() { - return true - } - } - return false -} - -// serviceNeedsWriteHelper returns true if any method needs the write helper. -func serviceNeedsWriteHelper(svc Service) bool { - for _, m := range svc.Methods { - if m.NeedsResponseType() { - return true - } - } - return false -} - -// serviceClientNeedsJSON returns true if any method needs JSON encoding in client code. -// This is true if any method has a response type (complex returns) or if any param/return needs JSON. -func serviceClientNeedsJSON(svc Service) bool { - for _, m := range svc.Methods { - // Response types use JSON for serialization - if m.NeedsResponseType() { - return true - } - // Parameters that need JSON marshaling - for _, p := range m.Params { - if NeedsJSON(p.Type) { - return true - } - } - } - return false -} - -// serviceClientNeedsErrors returns true if any method needs the errors package in client code. -// This is only true for error-only methods (methods that return just error). -func serviceClientNeedsErrors(svc Service) bool { - for _, m := range svc.Methods { - if m.IsErrorOnly() { - return true - } - } - return false -} - -// serviceNeedsErrorHelper returns true if any method needs error handling with JSON. -func serviceNeedsErrorHelper(svc Service) bool { - for _, m := range svc.Methods { - if m.HasError && m.NeedsResponseType() { - return true - } - } - return false -} - -// hasErrorFromRead returns true if reading params declares an err variable. -// This happens when using needsRequestType (JSON) or when any param is a PTR type. -func hasErrorFromRead(m Method) bool { - if m.NeedsRequestType() { - return true - } - for _, p := range m.Params { - if IsStringType(p.Type) || IsBytesType(p.Type) { - return true - } - } - return false -} - -// generateReadParam generates code to read a parameter from the stack. -func generateReadParam(p Param, stackIndex int) string { - switch { - case IsSimpleType(p.Type): - return generateReadSimple(p, stackIndex) - case IsStringType(p.Type): - return fmt.Sprintf(`%s, err := p.ReadString(stack[%d]) - if err != nil { - return - }`, p.Name, stackIndex) - case IsBytesType(p.Type): - return fmt.Sprintf(`%s, err := p.ReadBytes(stack[%d]) - if err != nil { - return - }`, p.Name, stackIndex) - default: - // Complex type - JSON - return fmt.Sprintf(`%sBytes, err := p.ReadBytes(stack[%d]) - if err != nil { - return - } - var %s %s - if err := json.Unmarshal(%sBytes, &%s); err != nil { - return - }`, p.Name, stackIndex, p.Name, p.Type, p.Name, p.Name) - } -} - -// generateReadSimple generates code to read a simple type from the stack. -func generateReadSimple(p Param, stackIndex int) string { - switch p.Type { - case "int32": - return fmt.Sprintf(`%s := extism.DecodeI32(stack[%d])`, p.Name, stackIndex) - case "uint32": - return fmt.Sprintf(`%s := extism.DecodeU32(stack[%d])`, p.Name, stackIndex) - case "int64": - return fmt.Sprintf(`%s := int64(stack[%d])`, p.Name, stackIndex) - case "uint64": - return fmt.Sprintf(`%s := stack[%d]`, p.Name, stackIndex) - case "float32": - return fmt.Sprintf(`%s := extism.DecodeF32(stack[%d])`, p.Name, stackIndex) - case "float64": - return fmt.Sprintf(`%s := extism.DecodeF64(stack[%d])`, p.Name, stackIndex) - case "bool": - return fmt.Sprintf(`%s := extism.DecodeI32(stack[%d]) != 0`, p.Name, stackIndex) - default: - return fmt.Sprintf(`// FIXME: unsupported type: %s`, p.Type) - } -} - -// generateWriteReturn generates code to write a return value to the stack. -func generateWriteReturn(p Param, stackIndex int, varName string) string { - switch { - case IsSimpleType(p.Type): - return fmt.Sprintf(`stack[%d] = %s`, stackIndex, generateEncodeReturn(p, varName)) - case IsStringType(p.Type): - return fmt.Sprintf(`if ptr, err := p.WriteString(%s); err == nil { - stack[%d] = ptr - }`, varName, stackIndex) - case IsBytesType(p.Type): - return fmt.Sprintf(`if ptr, err := p.WriteBytes(%s); err == nil { - stack[%d] = ptr - }`, varName, stackIndex) - default: - // Complex type - JSON - return fmt.Sprintf(`if bytes, err := json.Marshal(%s); err == nil { - if ptr, err := p.WriteBytes(bytes); err == nil { - stack[%d] = ptr - } - }`, varName, stackIndex) - } -} - -// generateEncodeReturn generates the encoding expression for a simple return. -func generateEncodeReturn(p Param, varName string) string { - switch p.Type { - case "int32": - return fmt.Sprintf("extism.EncodeI32(%s)", varName) - case "uint32": - return fmt.Sprintf("extism.EncodeU32(%s)", varName) - case "int64": - return fmt.Sprintf("uint64(%s)", varName) - case "uint64": - return varName - case "float32": - return fmt.Sprintf("extism.EncodeF32(%s)", varName) - case "float64": - return fmt.Sprintf("extism.EncodeF64(%s)", varName) - case "bool": - return fmt.Sprintf("func() uint64 { if %s { return 1 }; return 0 }()", varName) - default: - return "0" - } -} - -// Client-side helper functions for template - -// wasmParamType returns the WASM parameter type for a Go parameter. -func wasmParamType(p Param) string { - if IsSimpleType(p.Type) { - return p.Type - } - // All pointer types (string, []byte, complex) use uint64 offset - return "uint64" -} - -// wasmReturnType returns the WASM return type declaration for a method. -func wasmReturnType(m Method) string { - // Methods with JSON responses or error-only return uint64 (pointer) - if m.NeedsResponseType() || m.IsErrorOnly() { - return "uint64" - } - // Simple return types - if len(m.Returns) == 1 && IsSimpleType(m.Returns[0].Type) { - return m.Returns[0].Type - } - // No returns or multiple returns - use uint64 for pointer - if len(m.Returns) == 0 { - return "" - } - return "uint64" -} - -// wrapperReturnType returns the Go return type for the wrapper function. -func wrapperReturnType(m Method, svcName string) string { - if m.NeedsResponseType() { - return fmt.Sprintf("(*%s%sResponse, error)", svcName, m.Name) - } - if m.IsErrorOnly() { - return "error" - } - if len(m.Returns) == 1 { - return m.Returns[0].Type - } - return "" -} - -// clientCallArg returns the argument expression for calling the host function. -func clientCallArg(p Param) string { - if IsSimpleType(p.Type) { - return p.Name - } - // Pointer types use .Offset() - return p.Name + "Mem.Offset()" -} - -// decodeResult generates code to decode a simple return value. -func decodeResult(p Param, varName string) string { - switch p.Type { - case "int32": - return fmt.Sprintf("int32(%s)", varName) - case "uint32": - return fmt.Sprintf("uint32(%s)", varName) - case "int64": - return fmt.Sprintf("int64(%s)", varName) - case "uint64": - return varName - case "float32": - return fmt.Sprintf("math.Float32frombits(uint32(%s))", varName) - case "float64": - return fmt.Sprintf("math.Float64frombits(%s)", varName) - case "bool": - return fmt.Sprintf("%s != 0", varName) - case "string": - // pdk.FindMemory returns a value type, ReadBytes has pointer receiver - return fmt.Sprintf("func() string { m := pdk.FindMemory(%s); return string(m.ReadBytes()) }()", varName) - case "[]byte": - return fmt.Sprintf("func() []byte { m := pdk.FindMemory(%s); return m.ReadBytes() }()", varName) - default: - return varName - } + Package string + Service Service } // formatDoc formats a documentation string for Go comments. diff --git a/plugins/cmd/hostgen/internal/generator_test.go b/plugins/cmd/hostgen/internal/generator_test.go index 8ec9c0902..8b449527f 100644 --- a/plugins/cmd/hostgen/internal/generator_test.go +++ b/plugins/cmd/hostgen/internal/generator_test.go @@ -11,7 +11,7 @@ import ( var _ = Describe("Generator", func() { Describe("GenerateHost", func() { It("should generate valid Go code for a simple service with strings", func() { - // String params/returns don't need JSON - they use direct memory read/write + // All methods use JSON request/response types svc := Service{ Name: "SubsonicAPI", Permission: "subsonicapi", @@ -41,10 +41,11 @@ var _ = Describe("Generator", func() { // Check for package declaration Expect(codeStr).To(ContainSubstring("package host")) - // String params don't need request type - read directly from memory - Expect(codeStr).NotTo(ContainSubstring("type SubsonicAPICallRequest struct")) + // All methods now use request type for JSON protocol + Expect(codeStr).To(ContainSubstring("type SubsonicAPICallRequest struct")) + Expect(codeStr).To(ContainSubstring(`Uri string `)) - // String return with error needs response type for error handling + // Response type with error handling Expect(codeStr).To(ContainSubstring("type SubsonicAPICallResponse struct")) Expect(codeStr).To(ContainSubstring(`Response string `)) Expect(codeStr).To(ContainSubstring(`Error string `)) @@ -55,8 +56,8 @@ var _ = Describe("Generator", func() { // Check for host function name Expect(codeStr).To(ContainSubstring(`"subsonicapi_call"`)) - // Check for direct string read (not JSON unmarshal) - Expect(codeStr).To(ContainSubstring("p.ReadString(stack[0])")) + // Check for JSON unmarshal (all methods use JSON now) + Expect(codeStr).To(ContainSubstring("json.Unmarshal")) }) It("should generate code for methods without parameters", func() { @@ -80,8 +81,10 @@ var _ = Describe("Generator", func() { Expect(err).NotTo(HaveOccurred()) codeStr := string(code) - // Should not have request type for methods without params + // Methods without params don't need a request type - no params to serialize Expect(codeStr).NotTo(ContainSubstring("type TestNoParamsRequest struct")) + // But still uses PTR input/output for consistency + Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`)) }) It("should generate code for methods without return values", func() { @@ -144,8 +147,8 @@ var _ = Describe("Generator", func() { Expect(codeStr).To(ContainSubstring("scheduler_cancelschedule")) }) - It("should handle multiple simple parameters without JSON", func() { - // All simple params (string, int32, bool) can be passed on stack directly + It("should handle multiple simple parameters with JSON", func() { + // All params use JSON - single PTR input svc := Service{ Name: "Test", Permission: "test", @@ -171,14 +174,12 @@ var _ = Describe("Generator", func() { Expect(err).NotTo(HaveOccurred()) codeStr := string(code) - // No request type for simple params - they're read directly from stack - Expect(codeStr).NotTo(ContainSubstring("type TestMultiParamRequest struct")) - // Check for direct stack reads - Expect(codeStr).To(ContainSubstring("p.ReadString(stack[0])")) - Expect(codeStr).To(ContainSubstring("extism.DecodeI32(stack[1])")) - Expect(codeStr).To(ContainSubstring("extism.DecodeI32(stack[2])")) - // Check that input ValueType slice has correct entries (3 params: PTR for string, I32 for int32, I32 for bool) - Expect(codeStr).To(ContainSubstring("extism.ValueTypePTR, extism.ValueTypeI32, extism.ValueTypeI32")) + // All methods use request type with JSON protocol + Expect(codeStr).To(ContainSubstring("type TestMultiParamRequest struct")) + // Check for JSON unmarshal (all methods use JSON now) + Expect(codeStr).To(ContainSubstring("json.Unmarshal")) + // Check that input/output ValueType both use PTR (JSON) + Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`)) }) It("should use single PTR for mixed simple and complex params", func() { @@ -263,8 +264,8 @@ var _ = Describe("Generator", func() { Expect(codeStr).To(ContainSubstring(`extism "github.com/extism/go-sdk"`)) }) - It("should not include json import when not needed", func() { - // Service with only simple types doesn't need JSON import + It("should always include json import for JSON protocol", func() { + // All services use JSON protocol, so json import is always needed svc := Service{ Name: "Test", Permission: "test", @@ -283,7 +284,7 @@ var _ = Describe("Generator", func() { codeStr := string(code) Expect(codeStr).To(ContainSubstring(`"context"`)) - Expect(codeStr).NotTo(ContainSubstring(`"encoding/json"`)) + Expect(codeStr).To(ContainSubstring(`"encoding/json"`)) Expect(codeStr).To(ContainSubstring(`extism "github.com/extism/go-sdk"`)) }) }) diff --git a/plugins/cmd/hostgen/internal/templates/client_go.go.tmpl b/plugins/cmd/hostgen/internal/templates/client_go.go.tmpl index dcf7767f0..f87f98e5b 100644 --- a/plugins/cmd/hostgen/internal/templates/client_go.go.tmpl +++ b/plugins/cmd/hostgen/internal/templates/client_go.go.tmpl @@ -8,12 +8,7 @@ package main import ( -{{- if .NeedsJSON}} "encoding/json" -{{- end}} -{{- if .NeedsErrors}} - "errors" -{{- end}} "github.com/extism/go-pdk" ) @@ -24,12 +19,20 @@ import ( // {{exportName .}} is the host function provided by Navidrome. // //go:wasmimport extism:host/user {{exportName .}} -func {{exportName .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{wasmParamType $p}}{{end}}) {{wasmReturnType .}} +func {{exportName .}}(uint64) uint64 {{- end}} -{{- /* Generate response types for methods that need them */ -}} +{{- /* Generate request/response types for all methods */ -}} {{range .Service.Methods}} -{{- if needsRespType .}} +{{- if .HasParams}} + +// {{requestType .}} is the request type for {{$.Service.Name}}.{{.Name}}. +type {{requestType .}} struct { +{{- range .Params}} + {{title .Name}} {{.Type}} `json:"{{.JSONName}}"` +{{- end}} +} +{{- end}} // {{responseType .}} is the response type for {{$.Service.Name}}.{{.Name}}. type {{responseType .}} struct { @@ -39,7 +42,6 @@ type {{responseType .}} struct { Error string `json:"error,omitempty"` } {{- end}} -{{- end}} {{- /* Generate wrapper functions */ -}} {{range .Service.Methods}} @@ -48,28 +50,28 @@ type {{responseType .}} struct { {{- if .Doc}} {{formatDoc .Doc}} {{- end}} -func {{$.Service.Name}}{{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}} {{$p.Type}}{{end}}) {{wrapperReturnType . $.Service.Name}} { -{{- if needsRespType .}} -{{- /* Complex response - use JSON */}} +func {{$.Service.Name}}{{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}} {{$p.Type}}{{end}}) (*{{responseType .}}, error) { +{{- if .HasParams}} + // Marshal request to JSON + req := {{requestType .}}{ {{- range .Params}} -{{- if isString .Type}} - {{.Name}}Mem := pdk.AllocateString({{.Name}}) - defer {{.Name}}Mem.Free() -{{- else if isBytes .Type}} - {{.Name}}Mem := pdk.AllocateBytes({{.Name}}) - defer {{.Name}}Mem.Free() -{{- else if needsJSON .Type}} - {{.Name}}Bytes, err := json.Marshal({{.Name}}) + {{title .Name}}: {{.Name}}, +{{- end}} + } + reqBytes, err := json.Marshal(req) if err != nil { return nil, err } - {{.Name}}Mem := pdk.AllocateBytes({{.Name}}Bytes) - defer {{.Name}}Mem.Free() -{{- end}} + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() +{{- else}} + // No parameters - allocate empty JSON object + reqMem := pdk.AllocateBytes([]byte("{}")) + defer reqMem.Free() {{- end}} // Call the host function - responsePtr := {{exportName .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{clientCallArg $p}}{{end}}) + responsePtr := {{exportName .}}(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -82,56 +84,5 @@ func {{$.Service.Name}}{{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{ } return &response, nil -{{- else if isErrorOnly .}} -{{- /* Error-only response - string result */}} -{{- range .Params}} -{{- if isString .Type}} - {{.Name}}Mem := pdk.AllocateString({{.Name}}) - defer {{.Name}}Mem.Free() -{{- else if isBytes .Type}} - {{.Name}}Mem := pdk.AllocateBytes({{.Name}}) - defer {{.Name}}Mem.Free() -{{- else if needsJSON .Type}} - {{.Name}}Bytes, err := json.Marshal({{.Name}}) - if err != nil { - return err - } - {{.Name}}Mem := pdk.AllocateBytes({{.Name}}Bytes) - defer {{.Name}}Mem.Free() -{{- end}} -{{- end}} - - // Call the host function - responsePtr := {{exportName .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{clientCallArg $p}}{{end}}) - - // Read the response from memory - responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) - - if errStr != "" { - return errors.New(errStr) - } - - return nil -{{- else}} -{{- /* Simple return types - direct stack values */}} -{{- range .Params}} -{{- if isString .Type}} - {{.Name}}Mem := pdk.AllocateString({{.Name}}) - defer {{.Name}}Mem.Free() -{{- else if isBytes .Type}} - {{.Name}}Mem := pdk.AllocateBytes({{.Name}}) - defer {{.Name}}Mem.Free() -{{- end}} -{{- end}} - - // Call the host function -{{- if .HasReturns}} - result := {{exportName .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{clientCallArg $p}}{{end}}) - return {{decodeResult (index .Returns 0) "result"}} -{{- else}} - {{exportName .}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{clientCallArg $p}}{{end}}) -{{- end}} -{{- end}} } {{- end}} diff --git a/plugins/cmd/hostgen/internal/templates/host.go.tmpl b/plugins/cmd/hostgen/internal/templates/host.go.tmpl index 5e952c629..4a961bd56 100644 --- a/plugins/cmd/hostgen/internal/templates/host.go.tmpl +++ b/plugins/cmd/hostgen/internal/templates/host.go.tmpl @@ -4,16 +4,14 @@ package {{.Package}} import ( "context" -{{- if .NeedsJSON}} "encoding/json" -{{- end}} extism "github.com/extism/go-sdk" ) -{{- /* Generate request/response types only when needed */ -}} +{{- /* Generate request/response types for all methods */ -}} {{range .Service.Methods}} -{{- if needsRequestType .}} +{{- if .HasParams}} // {{requestType .}} is the request type for {{$.Service.Name}}.{{.Name}}. type {{requestType .}} struct { @@ -22,7 +20,6 @@ type {{requestType .}} struct { {{- end}} } {{- end}} -{{- if needsRespType .}} // {{responseType .}} is the response type for {{$.Service.Name}}.{{.Name}}. type {{responseType .}} struct { @@ -31,7 +28,6 @@ type {{responseType .}} struct { {{- end}} Error string `json:"error,omitempty"` } -{{- end}} {{end}} // Register{{.Service.Name}}HostFunctions registers {{.Service.Name}} service host functions. @@ -50,7 +46,6 @@ func new{{$.Service.Name}}{{.Name}}HostFunction(service {{$.Service.Interface}}) "{{exportName .}}", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { {{- if .HasParams}} -{{- if needsRequestType .}} // Read JSON request from plugin memory reqBytes, err := p.ReadBytes(stack[0]) if err != nil { @@ -62,47 +57,28 @@ func new{{$.Service.Name}}{{.Name}}HostFunction(service {{$.Service.Interface}}) {{$.Service.Name | lower}}WriteError(p, stack, err) return } -{{- else}} - // Read parameters from stack -{{- range $i, $p := .Params}} - {{readParam $p $i}} -{{- end}} -{{- end}} {{- end}} // Call the service method -{{- $m := .}} {{- if .HasReturns}} {{- if .HasError}} - {{range $i, $r := .Returns}}{{if $i}}, {{end}}{{lower $r.Name}}{{end}}, err := service.{{.Name}}(ctx{{range .Params}}, {{if needsRequestType $m}}req.{{title .Name}}{{else}}{{.Name}}{{end}}{{end}}) -{{- else}} - {{range $i, $r := .Returns}}{{if $i}}, {{end}}{{lower $r.Name}}{{end}} := service.{{.Name}}(ctx{{range .Params}}, {{if needsRequestType $m}}req.{{title .Name}}{{else}}{{.Name}}{{end}}{{end}}) -{{- end}} -{{- else if .HasError}} - err {{if hasErrFromRead .}}={{else}}:={{end}} service.{{.Name}}(ctx{{range .Params}}, {{if needsRequestType $m}}req.{{title .Name}}{{else}}{{.Name}}{{end}}{{end}}) -{{- else}} - service.{{.Name}}(ctx{{range .Params}}, {{if needsRequestType $m}}req.{{title .Name}}{{else}}{{.Name}}{{end}}{{end}}) -{{- end}} -{{- if .HasError}} - if err != nil { -{{- if isErrorOnly .}} - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } -{{- else if needsRespType .}} - {{$.Service.Name | lower}}WriteError(p, stack, err) -{{- end}} + {{range $i, $r := .Returns}}{{if $i}}, {{end}}{{lower $r.Name}}{{end}}, svcErr := service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}}) + if svcErr != nil { + {{$.Service.Name | lower}}WriteError(p, stack, svcErr) return } +{{- else}} + {{range $i, $r := .Returns}}{{if $i}}, {{end}}{{lower $r.Name}}{{end}} := service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}}) +{{- end}} +{{- else if .HasError}} + if svcErr := service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}}); svcErr != nil { + {{$.Service.Name | lower}}WriteError(p, stack, svcErr) + return + } +{{- else}} + service.{{.Name}}(ctx{{range .Params}}, req.{{title .Name}}{{end}}) {{- end}} -{{- if isErrorOnly .}} - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } -{{- else if needsRespType .}} // Write JSON response to plugin memory resp := {{responseType .}}{ {{- range .Returns}} @@ -110,27 +86,12 @@ func new{{$.Service.Name}}{{.Name}}HostFunction(service {{$.Service.Interface}}) {{- end}} } {{$.Service.Name | lower}}WriteResponse(p, stack, resp) -{{- else if .HasReturns}} - // Write return values to stack -{{- range $i, $r := .Returns}} - {{writeReturn $r $i (lower $r.Name)}} -{{- end}} -{{- end}} }, -{{- if needsRequestType $m}} []extism.ValueType{extism.ValueTypePTR}, -{{- else}} - []extism.ValueType{ {{- range $i, $p := .Params}}{{if $i}}, {{end}}{{valueType $p.Type}}{{end}}{{if not .HasParams}}{{end}} }, -{{- end}} -{{- if or (needsRespType .) (isErrorOnly .)}} []extism.ValueType{extism.ValueTypePTR}, -{{- else}} - []extism.ValueType{ {{- range $i, $r := .Returns}}{{if $i}}, {{end}}{{valueType $r.Type}}{{end}}{{if not .HasReturns}}{{end}} }, -{{- end}} ) } {{end}} -{{- if .NeedsWriteHelper}} // {{.Service.Name | lower}}WriteResponse writes a JSON response to plugin memory. func {{.Service.Name | lower}}WriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) { @@ -146,8 +107,6 @@ func {{.Service.Name | lower}}WriteResponse(p *extism.CurrentPlugin, stack []uin } stack[0] = respPtr } -{{- end}} -{{- if .NeedsErrorHelper}} // {{.Service.Name | lower}}WriteError writes an error response to plugin memory. func {{.Service.Name | lower}}WriteError(p *extism.CurrentPlugin, stack []uint64, err error) { @@ -158,4 +117,3 @@ func {{.Service.Name | lower}}WriteError(p *extism.CurrentPlugin, stack []uint64 respPtr, _ := p.WriteBytes(respBytes) stack[0] = respPtr } -{{- end}} diff --git a/plugins/cmd/hostgen/internal/types.go b/plugins/cmd/hostgen/internal/types.go index 6871559cb..8cea8d696 100644 --- a/plugins/cmd/hostgen/internal/types.go +++ b/plugins/cmd/hostgen/internal/types.go @@ -77,128 +77,6 @@ func NewParam(name, typ string) Param { } } -// IsSimple returns true if the type can be passed directly on the WASM stack -// without JSON serialization (primitive numeric types). -func (p Param) IsSimple() bool { - return IsSimpleType(p.Type) -} - -// IsPTR returns true if the type should be passed via memory pointer -// (strings, bytes, and complex types that need JSON serialization). -func (p Param) IsPTR() bool { - return !p.IsSimple() -} - -// ValueType returns the Extism ValueType constant name for this parameter. -func (p Param) ValueType() string { - return GoTypeToValueType(p.Type) -} - -// IsSimpleType returns true if a Go type can be passed directly on WASM stack. -func IsSimpleType(typ string) bool { - switch typ { - case "int32", "uint32", "int64", "uint64", "float32", "float64", "bool": - return true - default: - return false - } -} - -// IsStringType returns true if the type is a string. -func IsStringType(typ string) bool { - return typ == "string" -} - -// IsBytesType returns true if the type is []byte. -func IsBytesType(typ string) bool { - return typ == "[]byte" -} - -// NeedsJSON returns true if the type requires JSON serialization. -func NeedsJSON(typ string) bool { - if IsSimpleType(typ) || IsStringType(typ) || IsBytesType(typ) { - return false - } - return true -} - -// GoTypeToValueType returns the Extism ValueType constant for a Go type. -func GoTypeToValueType(typ string) string { - switch typ { - case "int32", "uint32": - return "extism.ValueTypeI32" - case "int64", "uint64": - return "extism.ValueTypeI64" - case "float32": - return "extism.ValueTypeF32" - case "float64": - return "extism.ValueTypeF64" - case "bool": - return "extism.ValueTypeI32" // bool as i32 - default: - // strings, []byte, structs, maps, slices all use PTR (i64) - return "extism.ValueTypePTR" - } -} - -// AllParamsSimple returns true if all params can be passed on the stack. -func (m Method) AllParamsSimple() bool { - for _, p := range m.Params { - if !p.IsSimple() { - return false - } - } - return true -} - -// AllReturnsSimple returns true if all returns can be passed on the stack. -func (m Method) AllReturnsSimple() bool { - for _, r := range m.Returns { - if !r.IsSimple() { - return false - } - } - return true -} - -// NeedsRequestType returns true if a request struct is needed. -// Only needed when we have complex params that require JSON. -func (m Method) NeedsRequestType() bool { - if !m.HasParams() { - return false - } - for _, p := range m.Params { - if NeedsJSON(p.Type) { - return true - } - } - return false -} - -// NeedsResponseType returns true if a response struct is needed. -// Needed when we have complex returns that require JSON (but not for error-only methods). -func (m Method) NeedsResponseType() bool { - // Error-only methods return a simple string, not JSON - if m.IsErrorOnly() { - return false - } - // If there's an error with other returns, we need a response type - if m.HasError && m.HasReturns() { - return true - } - for _, r := range m.Returns { - if NeedsJSON(r.Type) { - return true - } - } - return false -} - -// IsErrorOnly returns true if the method only returns an error (no other return values). -func (m Method) IsErrorOnly() bool { - return m.HasError && !m.HasReturns() -} - // toJSONName converts a Go identifier to camelCase JSON field name. func toJSONName(name string) string { if name == "" { diff --git a/plugins/cmd/hostgen/testdata/codec_client_expected.go b/plugins/cmd/hostgen/testdata/codec_client_expected.go index da2779d7c..fcc6d81a3 100644 --- a/plugins/cmd/hostgen/testdata/codec_client_expected.go +++ b/plugins/cmd/hostgen/testdata/codec_client_expected.go @@ -18,6 +18,11 @@ import ( //go:wasmimport extism:host/user codec_encode func codec_encode(uint64) uint64 +// CodecEncodeRequest is the request type for Codec.Encode. +type CodecEncodeRequest struct { + Data []byte `json:"data"` +} + // CodecEncodeResponse is the response type for Codec.Encode. type CodecEncodeResponse struct { Result []byte `json:"result,omitempty"` @@ -26,11 +31,19 @@ type CodecEncodeResponse struct { // CodecEncode calls the codec_encode host function. func CodecEncode(data []byte) (*CodecEncodeResponse, error) { - dataMem := pdk.AllocateBytes(data) - defer dataMem.Free() + // Marshal request to JSON + req := CodecEncodeRequest{ + Data: data, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := codec_encode(dataMem.Offset()) + responsePtr := codec_encode(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/cmd/hostgen/testdata/codec_expected.go b/plugins/cmd/hostgen/testdata/codec_expected.go index a66ea0b15..28eb22139 100644 --- a/plugins/cmd/hostgen/testdata/codec_expected.go +++ b/plugins/cmd/hostgen/testdata/codec_expected.go @@ -9,6 +9,11 @@ import ( extism "github.com/extism/go-sdk" ) +// CodecEncodeRequest is the request type for Codec.Encode. +type CodecEncodeRequest struct { + Data []byte `json:"data"` +} + // CodecEncodeResponse is the response type for Codec.Encode. type CodecEncodeResponse struct { Result []byte `json:"result,omitempty"` @@ -27,18 +32,25 @@ func newCodecEncodeHostFunction(service CodecService) extism.HostFunction { return extism.NewHostFunctionWithStack( "codec_encode", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - data, err := p.ReadBytes(stack[0]) - if err != nil { - return - } - - // Call the service method - result, err := service.Encode(ctx, data) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { codecWriteError(p, stack, err) return } + var req CodecEncodeRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + codecWriteError(p, stack, err) + return + } + + // Call the service method + result, svcErr := service.Encode(ctx, req.Data) + if svcErr != nil { + codecWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := CodecEncodeResponse{ Result: result, diff --git a/plugins/cmd/hostgen/testdata/counter_client_expected.go b/plugins/cmd/hostgen/testdata/counter_client_expected.go index d931cfe10..95b571282 100644 --- a/plugins/cmd/hostgen/testdata/counter_client_expected.go +++ b/plugins/cmd/hostgen/testdata/counter_client_expected.go @@ -8,20 +8,52 @@ package main import ( + "encoding/json" + "github.com/extism/go-pdk" ) // counter_count is the host function provided by Navidrome. // //go:wasmimport extism:host/user counter_count -func counter_count(uint64) int32 +func counter_count(uint64) uint64 + +// CounterCountRequest is the request type for Counter.Count. +type CounterCountRequest struct { + Name string `json:"name"` +} + +// CounterCountResponse is the response type for Counter.Count. +type CounterCountResponse struct { + Value int32 `json:"value,omitempty"` + Error string `json:"error,omitempty"` +} // CounterCount calls the counter_count host function. -func CounterCount(name string) int32 { - nameMem := pdk.AllocateString(name) - defer nameMem.Free() +func CounterCount(name string) (*CounterCountResponse, error) { + // Marshal request to JSON + req := CounterCountRequest{ + Name: name, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - result := counter_count(nameMem.Offset()) - return int32(result) + responsePtr := counter_count(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response CounterCountResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil } diff --git a/plugins/cmd/hostgen/testdata/counter_expected.go b/plugins/cmd/hostgen/testdata/counter_expected.go index 11fdf2090..036ff4523 100644 --- a/plugins/cmd/hostgen/testdata/counter_expected.go +++ b/plugins/cmd/hostgen/testdata/counter_expected.go @@ -4,10 +4,22 @@ package testpkg import ( "context" + "encoding/json" extism "github.com/extism/go-sdk" ) +// CounterCountRequest is the request type for Counter.Count. +type CounterCountRequest struct { + Name string `json:"name"` +} + +// CounterCountResponse is the response type for Counter.Count. +type CounterCountResponse struct { + Value int32 `json:"value,omitempty"` + Error string `json:"error,omitempty"` +} + // RegisterCounterHostFunctions registers Counter service host functions. // The returned host functions should be added to the plugin's configuration. func RegisterCounterHostFunctions(service CounterService) []extism.HostFunction { @@ -20,18 +32,53 @@ func newCounterCountHostFunction(service CounterService) extism.HostFunction { return extism.NewHostFunctionWithStack( "counter_count", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - name, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + counterWriteError(p, stack, err) + return + } + var req CounterCountRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + counterWriteError(p, stack, err) return } // Call the service method - value := service.Count(ctx, name) - // Write return values to stack - stack[0] = extism.EncodeI32(value) + value := service.Count(ctx, req.Name) + + // Write JSON response to plugin memory + resp := CounterCountResponse{ + Value: value, + } + counterWriteResponse(p, stack, resp) }, []extism.ValueType{extism.ValueTypePTR}, - []extism.ValueType{extism.ValueTypeI32}, + []extism.ValueType{extism.ValueTypePTR}, ) } + +// counterWriteResponse writes a JSON response to plugin memory. +func counterWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) { + respBytes, err := json.Marshal(resp) + if err != nil { + counterWriteError(p, stack, err) + return + } + respPtr, err := p.WriteBytes(respBytes) + if err != nil { + stack[0] = 0 + return + } + stack[0] = respPtr +} + +// counterWriteError writes an error response to plugin memory. +func counterWriteError(p *extism.CurrentPlugin, stack []uint64, err error) { + errResp := struct { + Error string `json:"error"` + }{Error: err.Error()} + respBytes, _ := json.Marshal(errResp) + respPtr, _ := p.WriteBytes(respBytes) + stack[0] = respPtr +} diff --git a/plugins/cmd/hostgen/testdata/echo_client_expected.go b/plugins/cmd/hostgen/testdata/echo_client_expected.go index b7adb2d8d..ef94da475 100644 --- a/plugins/cmd/hostgen/testdata/echo_client_expected.go +++ b/plugins/cmd/hostgen/testdata/echo_client_expected.go @@ -18,6 +18,11 @@ import ( //go:wasmimport extism:host/user echo_echo func echo_echo(uint64) uint64 +// EchoEchoRequest is the request type for Echo.Echo. +type EchoEchoRequest struct { + Message string `json:"message"` +} + // EchoEchoResponse is the response type for Echo.Echo. type EchoEchoResponse struct { Reply string `json:"reply,omitempty"` @@ -26,11 +31,19 @@ type EchoEchoResponse struct { // EchoEcho calls the echo_echo host function. func EchoEcho(message string) (*EchoEchoResponse, error) { - messageMem := pdk.AllocateString(message) - defer messageMem.Free() + // Marshal request to JSON + req := EchoEchoRequest{ + Message: message, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := echo_echo(messageMem.Offset()) + responsePtr := echo_echo(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/cmd/hostgen/testdata/echo_expected.go b/plugins/cmd/hostgen/testdata/echo_expected.go index 4d2927a0c..da0dc931e 100644 --- a/plugins/cmd/hostgen/testdata/echo_expected.go +++ b/plugins/cmd/hostgen/testdata/echo_expected.go @@ -9,6 +9,11 @@ import ( extism "github.com/extism/go-sdk" ) +// EchoEchoRequest is the request type for Echo.Echo. +type EchoEchoRequest struct { + Message string `json:"message"` +} + // EchoEchoResponse is the response type for Echo.Echo. type EchoEchoResponse struct { Reply string `json:"reply,omitempty"` @@ -27,18 +32,25 @@ func newEchoEchoHostFunction(service EchoService) extism.HostFunction { return extism.NewHostFunctionWithStack( "echo_echo", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - message, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - reply, err := service.Echo(ctx, message) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { echoWriteError(p, stack, err) return } + var req EchoEchoRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + echoWriteError(p, stack, err) + return + } + + // Call the service method + reply, svcErr := service.Echo(ctx, req.Message) + if svcErr != nil { + echoWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := EchoEchoResponse{ Reply: reply, diff --git a/plugins/cmd/hostgen/testdata/list_client_expected.go b/plugins/cmd/hostgen/testdata/list_client_expected.go index d5e301244..7a9576aba 100644 --- a/plugins/cmd/hostgen/testdata/list_client_expected.go +++ b/plugins/cmd/hostgen/testdata/list_client_expected.go @@ -16,7 +16,13 @@ import ( // list_items is the host function provided by Navidrome. // //go:wasmimport extism:host/user list_items -func list_items(uint64, uint64) uint64 +func list_items(uint64) uint64 + +// ListItemsRequest is the request type for List.Items. +type ListItemsRequest struct { + Name string `json:"name"` + Filter Filter `json:"filter"` +} // ListItemsResponse is the response type for List.Items. type ListItemsResponse struct { @@ -26,17 +32,20 @@ type ListItemsResponse struct { // ListItems calls the list_items host function. func ListItems(name string, filter Filter) (*ListItemsResponse, error) { - nameMem := pdk.AllocateString(name) - defer nameMem.Free() - filterBytes, err := json.Marshal(filter) + // Marshal request to JSON + req := ListItemsRequest{ + Name: name, + Filter: filter, + } + reqBytes, err := json.Marshal(req) if err != nil { return nil, err } - filterMem := pdk.AllocateBytes(filterBytes) - defer filterMem.Free() + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := list_items(nameMem.Offset(), filterMem.Offset()) + responsePtr := list_items(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/cmd/hostgen/testdata/list_expected.go b/plugins/cmd/hostgen/testdata/list_expected.go index 3cf67cb1a..8a7dee4e7 100644 --- a/plugins/cmd/hostgen/testdata/list_expected.go +++ b/plugins/cmd/hostgen/testdata/list_expected.go @@ -46,11 +46,12 @@ func newListItemsHostFunction(service ListService) extism.HostFunction { } // Call the service method - count, err := service.Items(ctx, req.Name, req.Filter) - if err != nil { - listWriteError(p, stack, err) + count, svcErr := service.Items(ctx, req.Name, req.Filter) + if svcErr != nil { + listWriteError(p, stack, svcErr) return } + // Write JSON response to plugin memory resp := ListItemsResponse{ Count: count, diff --git a/plugins/cmd/hostgen/testdata/math_client_expected.go b/plugins/cmd/hostgen/testdata/math_client_expected.go index 6710d1f10..fe9d62945 100644 --- a/plugins/cmd/hostgen/testdata/math_client_expected.go +++ b/plugins/cmd/hostgen/testdata/math_client_expected.go @@ -16,7 +16,13 @@ import ( // math_add is the host function provided by Navidrome. // //go:wasmimport extism:host/user math_add -func math_add(int32, int32) uint64 +func math_add(uint64) uint64 + +// MathAddRequest is the request type for Math.Add. +type MathAddRequest struct { + A int32 `json:"a"` + B int32 `json:"b"` +} // MathAddResponse is the response type for Math.Add. type MathAddResponse struct { @@ -26,9 +32,20 @@ type MathAddResponse struct { // MathAdd calls the math_add host function. func MathAdd(a int32, b int32) (*MathAddResponse, error) { + // Marshal request to JSON + req := MathAddRequest{ + A: a, + B: b, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := math_add(a, b) + responsePtr := math_add(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/cmd/hostgen/testdata/math_expected.go b/plugins/cmd/hostgen/testdata/math_expected.go index d4f58e035..6fc55aeef 100644 --- a/plugins/cmd/hostgen/testdata/math_expected.go +++ b/plugins/cmd/hostgen/testdata/math_expected.go @@ -9,6 +9,12 @@ import ( extism "github.com/extism/go-sdk" ) +// MathAddRequest is the request type for Math.Add. +type MathAddRequest struct { + A int32 `json:"a"` + B int32 `json:"b"` +} + // MathAddResponse is the response type for Math.Add. type MathAddResponse struct { Result int32 `json:"result,omitempty"` @@ -27,23 +33,32 @@ func newMathAddHostFunction(service MathService) extism.HostFunction { return extism.NewHostFunctionWithStack( "math_add", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - a := extism.DecodeI32(stack[0]) - b := extism.DecodeI32(stack[1]) - - // Call the service method - result, err := service.Add(ctx, a, b) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { mathWriteError(p, stack, err) return } + var req MathAddRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + mathWriteError(p, stack, err) + return + } + + // Call the service method + result, svcErr := service.Add(ctx, req.A, req.B) + if svcErr != nil { + mathWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := MathAddResponse{ Result: result, } mathWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypeI32, extism.ValueTypeI32}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } diff --git a/plugins/cmd/hostgen/testdata/meta_client_expected.go b/plugins/cmd/hostgen/testdata/meta_client_expected.go index fa14bf7ec..1cbb8f19e 100644 --- a/plugins/cmd/hostgen/testdata/meta_client_expected.go +++ b/plugins/cmd/hostgen/testdata/meta_client_expected.go @@ -9,7 +9,6 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) @@ -24,19 +23,42 @@ func meta_get(uint64) uint64 //go:wasmimport extism:host/user meta_set func meta_set(uint64) uint64 +// MetaGetRequest is the request type for Meta.Get. +type MetaGetRequest struct { + Key string `json:"key"` +} + // MetaGetResponse is the response type for Meta.Get. type MetaGetResponse struct { Value any `json:"value,omitempty"` Error string `json:"error,omitempty"` } +// MetaSetRequest is the request type for Meta.Set. +type MetaSetRequest struct { + Data map[string]any `json:"data"` +} + +// MetaSetResponse is the response type for Meta.Set. +type MetaSetResponse struct { + Error string `json:"error,omitempty"` +} + // MetaGet calls the meta_get host function. func MetaGet(key string) (*MetaGetResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := MetaGetRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := meta_get(keyMem.Offset()) + responsePtr := meta_get(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -52,24 +74,30 @@ func MetaGet(key string) (*MetaGetResponse, error) { } // MetaSet calls the meta_set host function. -func MetaSet(data map[string]any) error { - dataBytes, err := json.Marshal(data) - if err != nil { - return err +func MetaSet(data map[string]any) (*MetaSetResponse, error) { + // Marshal request to JSON + req := MetaSetRequest{ + Data: data, } - dataMem := pdk.AllocateBytes(dataBytes) - defer dataMem.Free() + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := meta_set(dataMem.Offset()) + responsePtr := meta_set(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response MetaSetResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/cmd/hostgen/testdata/meta_expected.go b/plugins/cmd/hostgen/testdata/meta_expected.go index 435b16029..cea15a200 100644 --- a/plugins/cmd/hostgen/testdata/meta_expected.go +++ b/plugins/cmd/hostgen/testdata/meta_expected.go @@ -9,6 +9,11 @@ import ( extism "github.com/extism/go-sdk" ) +// MetaGetRequest is the request type for Meta.Get. +type MetaGetRequest struct { + Key string `json:"key"` +} + // MetaGetResponse is the response type for Meta.Get. type MetaGetResponse struct { Value any `json:"value,omitempty"` @@ -20,6 +25,11 @@ type MetaSetRequest struct { Data map[string]any `json:"data"` } +// MetaSetResponse is the response type for Meta.Set. +type MetaSetResponse struct { + Error string `json:"error,omitempty"` +} + // RegisterMetaHostFunctions registers Meta service host functions. // The returned host functions should be added to the plugin's configuration. func RegisterMetaHostFunctions(service MetaService) []extism.HostFunction { @@ -33,18 +43,25 @@ func newMetaGetHostFunction(service MetaService) extism.HostFunction { return extism.NewHostFunctionWithStack( "meta_get", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - value, err := service.Get(ctx, key) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { metaWriteError(p, stack, err) return } + var req MetaGetRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + metaWriteError(p, stack, err) + return + } + + // Call the service method + value, svcErr := service.Get(ctx, req.Key) + if svcErr != nil { + metaWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := MetaGetResponse{ Value: value, @@ -73,18 +90,14 @@ func newMetaSetHostFunction(service MetaService) extism.HostFunction { } // Call the service method - err = service.Set(ctx, req.Data) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.Set(ctx, req.Data); svcErr != nil { + metaWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := MetaSetResponse{} + metaWriteResponse(p, stack, resp) }, []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, diff --git a/plugins/cmd/hostgen/testdata/ping_client_expected.go b/plugins/cmd/hostgen/testdata/ping_client_expected.go index e352dff73..870621229 100644 --- a/plugins/cmd/hostgen/testdata/ping_client_expected.go +++ b/plugins/cmd/hostgen/testdata/ping_client_expected.go @@ -8,7 +8,7 @@ package main import ( - "errors" + "encoding/json" "github.com/extism/go-pdk" ) @@ -16,21 +16,31 @@ import ( // ping_ping is the host function provided by Navidrome. // //go:wasmimport extism:host/user ping_ping -func ping_ping() uint64 +func ping_ping(uint64) uint64 + +// PingPingResponse is the response type for Ping.Ping. +type PingPingResponse struct { + Error string `json:"error,omitempty"` +} // PingPing calls the ping_ping host function. -func PingPing() error { +func PingPing() (*PingPingResponse, error) { + // No parameters - allocate empty JSON object + reqMem := pdk.AllocateBytes([]byte("{}")) + defer reqMem.Free() // Call the host function - responsePtr := ping_ping() + responsePtr := ping_ping(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response PingPingResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/cmd/hostgen/testdata/ping_expected.go b/plugins/cmd/hostgen/testdata/ping_expected.go index eb44d1d9b..dff52b03e 100644 --- a/plugins/cmd/hostgen/testdata/ping_expected.go +++ b/plugins/cmd/hostgen/testdata/ping_expected.go @@ -4,10 +4,16 @@ package testpkg import ( "context" + "encoding/json" extism "github.com/extism/go-sdk" ) +// PingPingResponse is the response type for Ping.Ping. +type PingPingResponse struct { + Error string `json:"error,omitempty"` +} + // RegisterPingHostFunctions registers Ping service host functions. // The returned host functions should be added to the plugin's configuration. func RegisterPingHostFunctions(service PingService) []extism.HostFunction { @@ -22,20 +28,41 @@ func newPingPingHostFunction(service PingService) extism.HostFunction { func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { // Call the service method - err := service.Ping(ctx) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.Ping(ctx); svcErr != nil { + pingWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := PingPingResponse{} + pingWriteResponse(p, stack, resp) }, - []extism.ValueType{}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } + +// pingWriteResponse writes a JSON response to plugin memory. +func pingWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) { + respBytes, err := json.Marshal(resp) + if err != nil { + pingWriteError(p, stack, err) + return + } + respPtr, err := p.WriteBytes(respBytes) + if err != nil { + stack[0] = 0 + return + } + stack[0] = respPtr +} + +// pingWriteError writes an error response to plugin memory. +func pingWriteError(p *extism.CurrentPlugin, stack []uint64, err error) { + errResp := struct { + Error string `json:"error"` + }{Error: err.Error()} + respBytes, _ := json.Marshal(errResp) + respPtr, _ := p.WriteBytes(respBytes) + stack[0] = respPtr +} diff --git a/plugins/cmd/hostgen/testdata/search_client_expected.go b/plugins/cmd/hostgen/testdata/search_client_expected.go index 877665567..b88438b85 100644 --- a/plugins/cmd/hostgen/testdata/search_client_expected.go +++ b/plugins/cmd/hostgen/testdata/search_client_expected.go @@ -18,6 +18,11 @@ import ( //go:wasmimport extism:host/user search_find func search_find(uint64) uint64 +// SearchFindRequest is the request type for Search.Find. +type SearchFindRequest struct { + Query string `json:"query"` +} + // SearchFindResponse is the response type for Search.Find. type SearchFindResponse struct { Results []Result `json:"results,omitempty"` @@ -27,11 +32,19 @@ type SearchFindResponse struct { // SearchFind calls the search_find host function. func SearchFind(query string) (*SearchFindResponse, error) { - queryMem := pdk.AllocateString(query) - defer queryMem.Free() + // Marshal request to JSON + req := SearchFindRequest{ + Query: query, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := search_find(queryMem.Offset()) + responsePtr := search_find(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/cmd/hostgen/testdata/search_expected.go b/plugins/cmd/hostgen/testdata/search_expected.go index c0772f513..17f634085 100644 --- a/plugins/cmd/hostgen/testdata/search_expected.go +++ b/plugins/cmd/hostgen/testdata/search_expected.go @@ -9,6 +9,11 @@ import ( extism "github.com/extism/go-sdk" ) +// SearchFindRequest is the request type for Search.Find. +type SearchFindRequest struct { + Query string `json:"query"` +} + // SearchFindResponse is the response type for Search.Find. type SearchFindResponse struct { Results []Result `json:"results,omitempty"` @@ -28,18 +33,25 @@ func newSearchFindHostFunction(service SearchService) extism.HostFunction { return extism.NewHostFunctionWithStack( "search_find", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - query, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - results, total, err := service.Find(ctx, query) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { searchWriteError(p, stack, err) return } + var req SearchFindRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + searchWriteError(p, stack, err) + return + } + + // Call the service method + results, total, svcErr := service.Find(ctx, req.Query) + if svcErr != nil { + searchWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := SearchFindResponse{ Results: results, diff --git a/plugins/cmd/hostgen/testdata/store_client_expected.go b/plugins/cmd/hostgen/testdata/store_client_expected.go index de873919a..1e9b7f837 100644 --- a/plugins/cmd/hostgen/testdata/store_client_expected.go +++ b/plugins/cmd/hostgen/testdata/store_client_expected.go @@ -18,6 +18,11 @@ import ( //go:wasmimport extism:host/user store_save func store_save(uint64) uint64 +// StoreSaveRequest is the request type for Store.Save. +type StoreSaveRequest struct { + Item Item `json:"item"` +} + // StoreSaveResponse is the response type for Store.Save. type StoreSaveResponse struct { Id string `json:"id,omitempty"` @@ -26,15 +31,19 @@ type StoreSaveResponse struct { // StoreSave calls the store_save host function. func StoreSave(item Item) (*StoreSaveResponse, error) { - itemBytes, err := json.Marshal(item) + // Marshal request to JSON + req := StoreSaveRequest{ + Item: item, + } + reqBytes, err := json.Marshal(req) if err != nil { return nil, err } - itemMem := pdk.AllocateBytes(itemBytes) - defer itemMem.Free() + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := store_save(itemMem.Offset()) + responsePtr := store_save(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/cmd/hostgen/testdata/store_expected.go b/plugins/cmd/hostgen/testdata/store_expected.go index 426096968..9242c8c4b 100644 --- a/plugins/cmd/hostgen/testdata/store_expected.go +++ b/plugins/cmd/hostgen/testdata/store_expected.go @@ -45,11 +45,12 @@ func newStoreSaveHostFunction(service StoreService) extism.HostFunction { } // Call the service method - id, err := service.Save(ctx, req.Item) - if err != nil { - storeWriteError(p, stack, err) + id, svcErr := service.Save(ctx, req.Item) + if svcErr != nil { + storeWriteError(p, stack, svcErr) return } + // Write JSON response to plugin memory resp := StoreSaveResponse{ Id: id, diff --git a/plugins/cmd/hostgen/testdata/users_client_expected.go b/plugins/cmd/hostgen/testdata/users_client_expected.go index 730f460ba..63ff0add4 100644 --- a/plugins/cmd/hostgen/testdata/users_client_expected.go +++ b/plugins/cmd/hostgen/testdata/users_client_expected.go @@ -16,7 +16,13 @@ import ( // users_get is the host function provided by Navidrome. // //go:wasmimport extism:host/user users_get -func users_get(uint64, uint64) uint64 +func users_get(uint64) uint64 + +// UsersGetRequest is the request type for Users.Get. +type UsersGetRequest struct { + Id *string `json:"id"` + Filter *User `json:"filter"` +} // UsersGetResponse is the response type for Users.Get. type UsersGetResponse struct { @@ -26,21 +32,20 @@ type UsersGetResponse struct { // UsersGet calls the users_get host function. func UsersGet(id *string, filter *User) (*UsersGetResponse, error) { - idBytes, err := json.Marshal(id) + // Marshal request to JSON + req := UsersGetRequest{ + Id: id, + Filter: filter, + } + reqBytes, err := json.Marshal(req) if err != nil { return nil, err } - idMem := pdk.AllocateBytes(idBytes) - defer idMem.Free() - filterBytes, err := json.Marshal(filter) - if err != nil { - return nil, err - } - filterMem := pdk.AllocateBytes(filterBytes) - defer filterMem.Free() + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := users_get(idMem.Offset(), filterMem.Offset()) + responsePtr := users_get(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/cmd/hostgen/testdata/users_expected.go b/plugins/cmd/hostgen/testdata/users_expected.go index f1f7e21d8..417252426 100644 --- a/plugins/cmd/hostgen/testdata/users_expected.go +++ b/plugins/cmd/hostgen/testdata/users_expected.go @@ -46,11 +46,12 @@ func newUsersGetHostFunction(service UsersService) extism.HostFunction { } // Call the service method - result, err := service.Get(ctx, req.Id, req.Filter) - if err != nil { - usersWriteError(p, stack, err) + result, svcErr := service.Get(ctx, req.Id, req.Filter) + if svcErr != nil { + usersWriteError(p, stack, svcErr) return } + // Write JSON response to plugin memory resp := UsersGetResponse{ Result: result, diff --git a/plugins/examples/crypto-ticker/main.go b/plugins/examples/crypto-ticker/main.go index 9e07ecefc..bb3c9264f 100755 --- a/plugins/examples/crypto-ticker/main.go +++ b/plugins/examples/crypto-ticker/main.go @@ -176,11 +176,14 @@ func parseTickerSymbols(tickerConfig string) []string { // connectAndSubscribe connects to Coinbase WebSocket and subscribes to tickers func connectAndSubscribe(tickers []string) error { // Connect to WebSocket using host function - connID, err := WebSocketConnect(coinbaseWSEndpoint, nil, connectionID) + resp, err := WebSocketConnect(coinbaseWSEndpoint, nil, connectionID) if err != nil { return fmt.Errorf("WebSocket connection error: %v", err) } - pdk.Log(pdk.LogInfo, fmt.Sprintf("Connected to Coinbase WebSocket API (connection: %s)", connID)) + if resp.Error != "" { + return fmt.Errorf("WebSocket connection error: %s", resp.Error) + } + pdk.Log(pdk.LogInfo, fmt.Sprintf("Connected to Coinbase WebSocket API (connection: %s)", resp.NewConnectionID)) // Subscribe to ticker channel subscription := CoinbaseSubscription{ @@ -195,10 +198,13 @@ func connectAndSubscribe(tickers []string) error { } // Send subscription message - err = WebSocketSendText(connectionID, string(subscriptionJSON)) + sendResp, err := WebSocketSendText(connectionID, string(subscriptionJSON)) if err != nil { return fmt.Errorf("WebSocket send error: %v", err) } + if sendResp.Error != "" { + return fmt.Errorf("WebSocket send error: %s", sendResp.Error) + } pdk.Log(pdk.LogInfo, "Subscription message sent to Coinbase WebSocket API") return nil @@ -266,9 +272,11 @@ func NdWebsocketOnClose(input OnCloseInput) (OnCloseOutput, error) { pdk.Log(pdk.LogInfo, "Scheduling reconnection attempt in 5 seconds...") // Schedule a one-time reconnection attempt - _, err := SchedulerScheduleOneTime(5, "reconnect", reconnectScheduleID) + resp, err := SchedulerScheduleOneTime(5, "reconnect", reconnectScheduleID) if err != nil { pdk.Log(pdk.LogError, fmt.Sprintf("Failed to schedule reconnection: %v", err)) + } else if resp.Error != "" { + pdk.Log(pdk.LogError, fmt.Sprintf("Failed to schedule reconnection: %s", resp.Error)) } } @@ -317,9 +325,11 @@ func ndSchedulerCallback() int32 { pdk.Log(pdk.LogError, fmt.Sprintf("Reconnection failed: %v - will retry in 10 seconds", err)) // Schedule another attempt - _, err = SchedulerScheduleOneTime(10, "reconnect", reconnectScheduleID) + resp, err := SchedulerScheduleOneTime(10, "reconnect", reconnectScheduleID) if err != nil { pdk.Log(pdk.LogError, fmt.Sprintf("Failed to schedule retry: %v", err)) + } else if resp.Error != "" { + pdk.Log(pdk.LogError, fmt.Sprintf("Failed to schedule retry: %s", resp.Error)) } } else { pdk.Log(pdk.LogInfo, "Successfully reconnected!") diff --git a/plugins/examples/crypto-ticker/nd_host.go b/plugins/examples/crypto-ticker/nd_host.go deleted file mode 100644 index 83e33bf68..000000000 --- a/plugins/examples/crypto-ticker/nd_host.go +++ /dev/null @@ -1,188 +0,0 @@ -// Host function wrappers for Navidrome plugin services. -// These allow the plugin to call host functions provided by Navidrome. -package main - -import ( - "encoding/json" - "errors" - - pdk "github.com/extism/go-pdk" -) - -// WebSocket host functions - -//go:wasmimport extism:host/user websocket_connect -func websocket_connect(uint64) uint64 - -//go:wasmimport extism:host/user websocket_sendtext -func websocket_sendtext(connectionID uint64, message uint64) uint64 - -//go:wasmimport extism:host/user websocket_closeconnection -func websocket_closeconnection(connectionID uint64, code int32, reason uint64) uint64 - -// WebSocketConnectRequest is the request type for WebSocket.Connect -type WebSocketConnectRequest struct { - Url string `json:"url"` - Headers map[string]string `json:"headers,omitempty"` - ConnectionID string `json:"connectionID,omitempty"` -} - -// WebSocketConnectResponse is the response type for WebSocket.Connect -type WebSocketConnectResponse struct { - NewConnectionID string `json:"newConnectionID,omitempty"` - Error string `json:"error,omitempty"` -} - -// WebSocketConnect establishes a WebSocket connection to the specified URL. -func WebSocketConnect(url string, headers map[string]string, connectionID string) (string, error) { - req := WebSocketConnectRequest{ - Url: url, - Headers: headers, - ConnectionID: connectionID, - } - reqBytes, err := json.Marshal(req) - if err != nil { - return "", err - } - - reqMem := pdk.AllocateBytes(reqBytes) - defer reqMem.Free() - - responsePtr := websocket_connect(reqMem.Offset()) - responseMem := pdk.FindMemory(responsePtr) - responseBytes := responseMem.ReadBytes() - - var resp WebSocketConnectResponse - if err := json.Unmarshal(responseBytes, &resp); err != nil { - return "", err - } - - if resp.Error != "" { - return "", errors.New(resp.Error) - } - - return resp.NewConnectionID, nil -} - -// WebSocketSendText sends a text message over an established WebSocket connection. -func WebSocketSendText(connectionID, message string) error { - connMem := pdk.AllocateString(connectionID) - defer connMem.Free() - msgMem := pdk.AllocateString(message) - defer msgMem.Free() - - responsePtr := websocket_sendtext(connMem.Offset(), msgMem.Offset()) - if responsePtr != 0 { - responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) - if errStr != "" { - return errors.New(errStr) - } - } - return nil -} - -// WebSocketCloseConnection gracefully closes a WebSocket connection. -func WebSocketCloseConnection(connectionID string, code int32, reason string) error { - connMem := pdk.AllocateString(connectionID) - defer connMem.Free() - reasonMem := pdk.AllocateString(reason) - defer reasonMem.Free() - - responsePtr := websocket_closeconnection(connMem.Offset(), code, reasonMem.Offset()) - if responsePtr != 0 { - responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) - if errStr != "" { - return errors.New(errStr) - } - } - return nil -} - -// Scheduler host functions - -//go:wasmimport extism:host/user scheduler_scheduleonetime -func scheduler_scheduleonetime(delaySeconds int32, payload uint64, scheduleID uint64) uint64 - -//go:wasmimport extism:host/user scheduler_schedulerecurring -func scheduler_schedulerecurring(cronExpression uint64, payload uint64, scheduleID uint64) uint64 - -//go:wasmimport extism:host/user scheduler_cancelschedule -func scheduler_cancelschedule(scheduleID uint64) uint64 - -// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime -type SchedulerScheduleOneTimeResponse struct { - NewScheduleID string `json:"newScheduleID,omitempty"` - Error string `json:"error,omitempty"` -} - -// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring -type SchedulerScheduleRecurringResponse struct { - NewScheduleID string `json:"newScheduleID,omitempty"` - Error string `json:"error,omitempty"` -} - -// SchedulerScheduleOneTime schedules a one-time task to run after delaySeconds. -func SchedulerScheduleOneTime(delaySeconds int32, payload, scheduleID string) (string, error) { - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() - - responsePtr := scheduler_scheduleonetime(delaySeconds, payloadMem.Offset(), scheduleIDMem.Offset()) - responseMem := pdk.FindMemory(responsePtr) - responseBytes := responseMem.ReadBytes() - - var resp SchedulerScheduleOneTimeResponse - if err := json.Unmarshal(responseBytes, &resp); err != nil { - return "", err - } - - if resp.Error != "" { - return "", errors.New(resp.Error) - } - - return resp.NewScheduleID, nil -} - -// SchedulerScheduleRecurring schedules a recurring task using a cron expression. -func SchedulerScheduleRecurring(cronExpression, payload, scheduleID string) (string, error) { - cronMem := pdk.AllocateString(cronExpression) - defer cronMem.Free() - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() - - responsePtr := scheduler_schedulerecurring(cronMem.Offset(), payloadMem.Offset(), scheduleIDMem.Offset()) - responseMem := pdk.FindMemory(responsePtr) - responseBytes := responseMem.ReadBytes() - - var resp SchedulerScheduleRecurringResponse - if err := json.Unmarshal(responseBytes, &resp); err != nil { - return "", err - } - - if resp.Error != "" { - return "", errors.New(resp.Error) - } - - return resp.NewScheduleID, nil -} - -// SchedulerCancelSchedule cancels a scheduled task. -func SchedulerCancelSchedule(scheduleID string) error { - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() - - responsePtr := scheduler_cancelschedule(scheduleIDMem.Offset()) - if responsePtr != 0 { - responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) - if errStr != "" { - return errors.New(errStr) - } - } - return nil -} diff --git a/plugins/examples/crypto-ticker/nd_host_scheduler.go b/plugins/examples/crypto-ticker/nd_host_scheduler.go new file mode 100644 index 000000000..d341343c8 --- /dev/null +++ b/plugins/examples/crypto-ticker/nd_host_scheduler.go @@ -0,0 +1,180 @@ +// Code generated by hostgen. DO NOT EDIT. +// +// This file contains client wrappers for the Scheduler host service. +// It is intended for use in Navidrome plugins built with TinyGo. +// +//go:build wasip1 + +package main + +import ( + "encoding/json" + + "github.com/extism/go-pdk" +) + +// scheduler_scheduleonetime is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user scheduler_scheduleonetime +func scheduler_scheduleonetime(uint64) uint64 + +// scheduler_schedulerecurring is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user scheduler_schedulerecurring +func scheduler_schedulerecurring(uint64) uint64 + +// scheduler_cancelschedule is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user scheduler_cancelschedule +func scheduler_cancelschedule(uint64) uint64 + +// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeRequest struct { + DelaySeconds int32 `json:"delaySeconds"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + +// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeResponse struct { + NewScheduleID string `json:"newScheduleID,omitempty"` + Error string `json:"error,omitempty"` +} + +// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringRequest struct { + CronExpression string `json:"cronExpression"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + +// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringResponse struct { + NewScheduleID string `json:"newScheduleID,omitempty"` + Error string `json:"error,omitempty"` +} + +// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleRequest struct { + ScheduleID string `json:"scheduleID"` +} + +// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleResponse struct { + Error string `json:"error,omitempty"` +} + +// SchedulerScheduleOneTime calls the scheduler_scheduleonetime host function. +// ScheduleOneTime schedules a one-time event to be triggered after the specified delay. +// Plugins that use this function must also implement the SchedulerCallback capability +// +// Parameters: +// - delaySeconds: Number of seconds to wait before triggering the event +// - payload: Data to be passed to the scheduled event handler +// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated +// +// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. +func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) { + // Marshal request to JSON + req := SchedulerScheduleOneTimeRequest{ + DelaySeconds: delaySeconds, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := scheduler_scheduleonetime(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response SchedulerScheduleOneTimeResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// SchedulerScheduleRecurring calls the scheduler_schedulerecurring host function. +// ScheduleRecurring schedules a recurring event using a cron expression. +// Plugins that use this function must also implement the SchedulerCallback capability +// +// Parameters: +// - cronExpression: Standard cron format expression (e.g., "0 0 * * *" for daily at midnight) +// - payload: Data to be passed to each scheduled event handler invocation +// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated +// +// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. +func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (*SchedulerScheduleRecurringResponse, error) { + // Marshal request to JSON + req := SchedulerScheduleRecurringRequest{ + CronExpression: cronExpression, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := scheduler_schedulerecurring(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response SchedulerScheduleRecurringResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// SchedulerCancelSchedule calls the scheduler_cancelschedule host function. +// CancelSchedule cancels a scheduled job identified by its schedule ID. +// +// This works for both one-time and recurring schedules. Once cancelled, the job will not trigger +// any future events. +// +// Returns an error if the schedule ID is not found or if cancellation fails. +func SchedulerCancelSchedule(scheduleID string) (*SchedulerCancelScheduleResponse, error) { + // Marshal request to JSON + req := SchedulerCancelScheduleRequest{ + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := scheduler_cancelschedule(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response SchedulerCancelScheduleResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} diff --git a/plugins/examples/crypto-ticker/nd_host_websocket.go b/plugins/examples/crypto-ticker/nd_host_websocket.go new file mode 100644 index 000000000..e6daea765 --- /dev/null +++ b/plugins/examples/crypto-ticker/nd_host_websocket.go @@ -0,0 +1,237 @@ +// Code generated by hostgen. DO NOT EDIT. +// +// This file contains client wrappers for the WebSocket host service. +// It is intended for use in Navidrome plugins built with TinyGo. +// +//go:build wasip1 + +package main + +import ( + "encoding/json" + + "github.com/extism/go-pdk" +) + +// websocket_connect is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_connect +func websocket_connect(uint64) uint64 + +// websocket_sendtext is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_sendtext +func websocket_sendtext(uint64) uint64 + +// websocket_sendbinary is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_sendbinary +func websocket_sendbinary(uint64) uint64 + +// websocket_closeconnection is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_closeconnection +func websocket_closeconnection(uint64) uint64 + +// WebSocketConnectRequest is the request type for WebSocket.Connect. +type WebSocketConnectRequest struct { + Url string `json:"url"` + Headers map[string]string `json:"headers"` + ConnectionID string `json:"connectionID"` +} + +// WebSocketConnectResponse is the response type for WebSocket.Connect. +type WebSocketConnectResponse struct { + NewConnectionID string `json:"newConnectionID,omitempty"` + Error string `json:"error,omitempty"` +} + +// WebSocketSendTextRequest is the request type for WebSocket.SendText. +type WebSocketSendTextRequest struct { + ConnectionID string `json:"connectionID"` + Message string `json:"message"` +} + +// WebSocketSendTextResponse is the response type for WebSocket.SendText. +type WebSocketSendTextResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary. +type WebSocketSendBinaryRequest struct { + ConnectionID string `json:"connectionID"` + Data []byte `json:"data"` +} + +// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary. +type WebSocketSendBinaryResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection. +type WebSocketCloseConnectionRequest struct { + ConnectionID string `json:"connectionID"` + Code int32 `json:"code"` + Reason string `json:"reason"` +} + +// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection. +type WebSocketCloseConnectionResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketConnect calls the websocket_connect host function. +// Connect establishes a WebSocket connection to the specified URL. +// +// Plugins that use this function must also implement the WebSocketCallback capability +// to receive incoming messages and connection events. +// +// Parameters: +// - url: The WebSocket URL to connect to (ws:// or wss://) +// - headers: Optional HTTP headers to include in the handshake request +// - connectionID: Optional unique identifier for the connection. If empty, one will be generated +// +// Returns the connection ID that can be used to send messages or close the connection, +// or an error if the connection fails. +func WebSocketConnect(url string, headers map[string]string, connectionID string) (*WebSocketConnectResponse, error) { + // Marshal request to JSON + req := WebSocketConnectRequest{ + Url: url, + Headers: headers, + ConnectionID: connectionID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_connect(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketConnectResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// WebSocketSendText calls the websocket_sendtext host function. +// SendText sends a text message over an established WebSocket connection. +// +// Parameters: +// - connectionID: The connection identifier returned by Connect +// - message: The text message to send +// +// Returns an error if the connection is not found or if sending fails. +func WebSocketSendText(connectionID string, message string) (*WebSocketSendTextResponse, error) { + // Marshal request to JSON + req := WebSocketSendTextRequest{ + ConnectionID: connectionID, + Message: message, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_sendtext(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketSendTextResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// WebSocketSendBinary calls the websocket_sendbinary host function. +// SendBinary sends binary data over an established WebSocket connection. +// +// Parameters: +// - connectionID: The connection identifier returned by Connect +// - data: The binary data to send +// +// Returns an error if the connection is not found or if sending fails. +func WebSocketSendBinary(connectionID string, data []byte) (*WebSocketSendBinaryResponse, error) { + // Marshal request to JSON + req := WebSocketSendBinaryRequest{ + ConnectionID: connectionID, + Data: data, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_sendbinary(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketSendBinaryResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// WebSocketCloseConnection calls the websocket_closeconnection host function. +// CloseConnection gracefully closes a WebSocket connection. +// +// Parameters: +// - connectionID: The connection identifier returned by Connect +// - code: WebSocket close status code (e.g., 1000 for normal closure) +// - reason: Optional human-readable reason for closing +// +// Returns an error if the connection is not found or if closing fails. +func WebSocketCloseConnection(connectionID string, code int32, reason string) (*WebSocketCloseConnectionResponse, error) { + // Marshal request to JSON + req := WebSocketCloseConnectionRequest{ + ConnectionID: connectionID, + Code: code, + Reason: reason, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_closeconnection(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketCloseConnectionResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} diff --git a/plugins/examples/discord-rich-presence/main.go b/plugins/examples/discord-rich-presence/main.go index 48de2fd73..99b81028b 100644 --- a/plugins/examples/discord-rich-presence/main.go +++ b/plugins/examples/discord-rich-presence/main.go @@ -181,7 +181,7 @@ func NdScrobblerNowPlaying(input NowPlayingInput) (ScrobblerOutput, error) { } // Cancel any existing completion schedule - _ = SchedulerCancelSchedule(fmt.Sprintf("%s-clear", input.Username)) + _, _ = SchedulerCancelSchedule(fmt.Sprintf("%s-clear", input.Username)) // Calculate timestamps now := time.Now().Unix() diff --git a/plugins/examples/discord-rich-presence/nd_host_artwork.go b/plugins/examples/discord-rich-presence/nd_host_artwork.go index 0943c794c..65e5efb81 100644 --- a/plugins/examples/discord-rich-presence/nd_host_artwork.go +++ b/plugins/examples/discord-rich-presence/nd_host_artwork.go @@ -16,22 +16,28 @@ import ( // artwork_getartisturl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getartisturl -func artwork_getartisturl(uint64, int32) uint64 +func artwork_getartisturl(uint64) uint64 // artwork_getalbumurl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getalbumurl -func artwork_getalbumurl(uint64, int32) uint64 +func artwork_getalbumurl(uint64) uint64 // artwork_gettrackurl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_gettrackurl -func artwork_gettrackurl(uint64, int32) uint64 +func artwork_gettrackurl(uint64) uint64 // artwork_getplaylisturl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getplaylisturl -func artwork_getplaylisturl(uint64, int32) uint64 +func artwork_getplaylisturl(uint64) uint64 + +// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl. +type ArtworkGetArtistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} // ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl. type ArtworkGetArtistUrlResponse struct { @@ -39,18 +45,36 @@ type ArtworkGetArtistUrlResponse struct { Error string `json:"error,omitempty"` } +// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl. +type ArtworkGetAlbumUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl. type ArtworkGetAlbumUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl. +type ArtworkGetTrackUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl. type ArtworkGetTrackUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl. +type ArtworkGetPlaylistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl. type ArtworkGetPlaylistUrlResponse struct { Url string `json:"url,omitempty"` @@ -66,11 +90,20 @@ type ArtworkGetPlaylistUrlResponse struct { // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetArtistUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getartisturl(idMem.Offset(), size) + responsePtr := artwork_getartisturl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -94,11 +127,20 @@ func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, e // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetAlbumUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getalbumurl(idMem.Offset(), size) + responsePtr := artwork_getalbumurl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -122,11 +164,20 @@ func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, err // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetTrackUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_gettrackurl(idMem.Offset(), size) + responsePtr := artwork_gettrackurl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -150,11 +201,20 @@ func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, err // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetPlaylistUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getplaylisturl(idMem.Offset(), size) + responsePtr := artwork_getplaylisturl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/examples/discord-rich-presence/nd_host_cache.go b/plugins/examples/discord-rich-presence/nd_host_cache.go index c9798f2b2..36be869ae 100644 --- a/plugins/examples/discord-rich-presence/nd_host_cache.go +++ b/plugins/examples/discord-rich-presence/nd_host_cache.go @@ -9,7 +9,6 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) @@ -17,7 +16,7 @@ import ( // cache_setstring is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setstring -func cache_setstring(uint64, uint64, int64) uint64 +func cache_setstring(uint64) uint64 // cache_getstring is the host function provided by Navidrome. // @@ -27,7 +26,7 @@ func cache_getstring(uint64) uint64 // cache_setint is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setint -func cache_setint(uint64, int64, int64) uint64 +func cache_setint(uint64) uint64 // cache_getint is the host function provided by Navidrome. // @@ -37,7 +36,7 @@ func cache_getint(uint64) uint64 // cache_setfloat is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setfloat -func cache_setfloat(uint64, float64, int64) uint64 +func cache_setfloat(uint64) uint64 // cache_getfloat is the host function provided by Navidrome. // @@ -47,7 +46,7 @@ func cache_getfloat(uint64) uint64 // cache_setbytes is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setbytes -func cache_setbytes(uint64, uint64, int64) uint64 +func cache_setbytes(uint64) uint64 // cache_getbytes is the host function provided by Navidrome. // @@ -64,6 +63,23 @@ func cache_has(uint64) uint64 //go:wasmimport extism:host/user cache_remove func cache_remove(uint64) uint64 +// CacheSetStringRequest is the request type for Cache.SetString. +type CacheSetStringRequest struct { + Key string `json:"key"` + Value string `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetStringResponse is the response type for Cache.SetString. +type CacheSetStringResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetStringRequest is the request type for Cache.GetString. +type CacheGetStringRequest struct { + Key string `json:"key"` +} + // CacheGetStringResponse is the response type for Cache.GetString. type CacheGetStringResponse struct { Value string `json:"value,omitempty"` @@ -71,6 +87,23 @@ type CacheGetStringResponse struct { Error string `json:"error,omitempty"` } +// CacheSetIntRequest is the request type for Cache.SetInt. +type CacheSetIntRequest struct { + Key string `json:"key"` + Value int64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetIntResponse is the response type for Cache.SetInt. +type CacheSetIntResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetIntRequest is the request type for Cache.GetInt. +type CacheGetIntRequest struct { + Key string `json:"key"` +} + // CacheGetIntResponse is the response type for Cache.GetInt. type CacheGetIntResponse struct { Value int64 `json:"value,omitempty"` @@ -78,6 +111,23 @@ type CacheGetIntResponse struct { Error string `json:"error,omitempty"` } +// CacheSetFloatRequest is the request type for Cache.SetFloat. +type CacheSetFloatRequest struct { + Key string `json:"key"` + Value float64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetFloatResponse is the response type for Cache.SetFloat. +type CacheSetFloatResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetFloatRequest is the request type for Cache.GetFloat. +type CacheGetFloatRequest struct { + Key string `json:"key"` +} + // CacheGetFloatResponse is the response type for Cache.GetFloat. type CacheGetFloatResponse struct { Value float64 `json:"value,omitempty"` @@ -85,6 +135,23 @@ type CacheGetFloatResponse struct { Error string `json:"error,omitempty"` } +// CacheSetBytesRequest is the request type for Cache.SetBytes. +type CacheSetBytesRequest struct { + Key string `json:"key"` + Value []byte `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetBytesResponse is the response type for Cache.SetBytes. +type CacheSetBytesResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetBytesRequest is the request type for Cache.GetBytes. +type CacheGetBytesRequest struct { + Key string `json:"key"` +} + // CacheGetBytesResponse is the response type for Cache.GetBytes. type CacheGetBytesResponse struct { Value []byte `json:"value,omitempty"` @@ -92,12 +159,27 @@ type CacheGetBytesResponse struct { Error string `json:"error,omitempty"` } +// CacheHasRequest is the request type for Cache.Has. +type CacheHasRequest struct { + Key string `json:"key"` +} + // CacheHasResponse is the response type for Cache.Has. type CacheHasResponse struct { Exists bool `json:"exists,omitempty"` Error string `json:"error,omitempty"` } +// CacheRemoveRequest is the request type for Cache.Remove. +type CacheRemoveRequest struct { + Key string `json:"key"` +} + +// CacheRemoveResponse is the response type for Cache.Remove. +type CacheRemoveResponse struct { + Error string `json:"error,omitempty"` +} + // CacheSetString calls the cache_setstring host function. // SetString stores a string value in the cache. // @@ -107,24 +189,34 @@ type CacheHasResponse struct { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetString(key string, value string, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() - valueMem := pdk.AllocateString(value) - defer valueMem.Free() +func CacheSetString(key string, value string, ttlSeconds int64) (*CacheSetStringResponse, error) { + // Marshal request to JSON + req := CacheSetStringRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setstring(keyMem.Offset(), valueMem.Offset(), ttlSeconds) + responsePtr := cache_setstring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetStringResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetString calls the cache_getstring host function. @@ -136,11 +228,19 @@ func CacheSetString(key string, value string, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a string, exists will be false. func CacheGetString(key string) (*CacheGetStringResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetStringRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getstring(keyMem.Offset()) + responsePtr := cache_getstring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -164,22 +264,34 @@ func CacheGetString(key string) (*CacheGetStringResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetInt(key string, value int64, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheSetInt(key string, value int64, ttlSeconds int64) (*CacheSetIntResponse, error) { + // Marshal request to JSON + req := CacheSetIntRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setint(keyMem.Offset(), value, ttlSeconds) + responsePtr := cache_setint(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetIntResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetInt calls the cache_getint host function. @@ -191,11 +303,19 @@ func CacheSetInt(key string, value int64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not an integer, exists will be false. func CacheGetInt(key string) (*CacheGetIntResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetIntRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getint(keyMem.Offset()) + responsePtr := cache_getint(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -219,22 +339,34 @@ func CacheGetInt(key string) (*CacheGetIntResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetFloat(key string, value float64, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheSetFloat(key string, value float64, ttlSeconds int64) (*CacheSetFloatResponse, error) { + // Marshal request to JSON + req := CacheSetFloatRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setfloat(keyMem.Offset(), value, ttlSeconds) + responsePtr := cache_setfloat(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetFloatResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetFloat calls the cache_getfloat host function. @@ -246,11 +378,19 @@ func CacheSetFloat(key string, value float64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a float, exists will be false. func CacheGetFloat(key string) (*CacheGetFloatResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetFloatRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getfloat(keyMem.Offset()) + responsePtr := cache_getfloat(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -274,24 +414,34 @@ func CacheGetFloat(key string) (*CacheGetFloatResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() - valueMem := pdk.AllocateBytes(value) - defer valueMem.Free() +func CacheSetBytes(key string, value []byte, ttlSeconds int64) (*CacheSetBytesResponse, error) { + // Marshal request to JSON + req := CacheSetBytesRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setbytes(keyMem.Offset(), valueMem.Offset(), ttlSeconds) + responsePtr := cache_setbytes(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetBytesResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetBytes calls the cache_getbytes host function. @@ -303,11 +453,19 @@ func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a byte slice, exists will be false. func CacheGetBytes(key string) (*CacheGetBytesResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetBytesRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getbytes(keyMem.Offset()) + responsePtr := cache_getbytes(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -330,11 +488,19 @@ func CacheGetBytes(key string) (*CacheGetBytesResponse, error) { // // Returns true if the key exists and has not expired. func CacheHas(key string) (*CacheHasResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheHasRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_has(keyMem.Offset()) + responsePtr := cache_has(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -356,20 +522,30 @@ func CacheHas(key string) (*CacheHasResponse, error) { // - key: The cache key (will be namespaced with plugin ID) // // Returns an error if the operation fails. Does not return an error if the key doesn't exist. -func CacheRemove(key string) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheRemove(key string) (*CacheRemoveResponse, error) { + // Marshal request to JSON + req := CacheRemoveRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_remove(keyMem.Offset()) + responsePtr := cache_remove(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheRemoveResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/examples/discord-rich-presence/nd_host_scheduler.go b/plugins/examples/discord-rich-presence/nd_host_scheduler.go index 1aef70972..d341343c8 100644 --- a/plugins/examples/discord-rich-presence/nd_host_scheduler.go +++ b/plugins/examples/discord-rich-presence/nd_host_scheduler.go @@ -9,7 +9,6 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) @@ -17,30 +16,54 @@ import ( // scheduler_scheduleonetime is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_scheduleonetime -func scheduler_scheduleonetime(int32, uint64, uint64) uint64 +func scheduler_scheduleonetime(uint64) uint64 // scheduler_schedulerecurring is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_schedulerecurring -func scheduler_schedulerecurring(uint64, uint64, uint64) uint64 +func scheduler_schedulerecurring(uint64) uint64 // scheduler_cancelschedule is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_cancelschedule func scheduler_cancelschedule(uint64) uint64 +// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeRequest struct { + DelaySeconds int32 `json:"delaySeconds"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime. type SchedulerScheduleOneTimeResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringRequest struct { + CronExpression string `json:"cronExpression"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring. type SchedulerScheduleRecurringResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleRequest struct { + ScheduleID string `json:"scheduleID"` +} + +// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleResponse struct { + Error string `json:"error,omitempty"` +} + // SchedulerScheduleOneTime calls the scheduler_scheduleonetime host function. // ScheduleOneTime schedules a one-time event to be triggered after the specified delay. // Plugins that use this function must also implement the SchedulerCallback capability @@ -52,13 +75,21 @@ type SchedulerScheduleRecurringResponse struct { // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) { - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() + // Marshal request to JSON + req := SchedulerScheduleOneTimeRequest{ + DelaySeconds: delaySeconds, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_scheduleonetime(delaySeconds, payloadMem.Offset(), scheduleIDMem.Offset()) + responsePtr := scheduler_scheduleonetime(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -84,15 +115,21 @@ func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID str // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (*SchedulerScheduleRecurringResponse, error) { - cronExpressionMem := pdk.AllocateString(cronExpression) - defer cronExpressionMem.Free() - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() + // Marshal request to JSON + req := SchedulerScheduleRecurringRequest{ + CronExpression: cronExpression, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_schedulerecurring(cronExpressionMem.Offset(), payloadMem.Offset(), scheduleIDMem.Offset()) + responsePtr := scheduler_schedulerecurring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -114,20 +151,30 @@ func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleI // any future events. // // Returns an error if the schedule ID is not found or if cancellation fails. -func SchedulerCancelSchedule(scheduleID string) error { - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() +func SchedulerCancelSchedule(scheduleID string) (*SchedulerCancelScheduleResponse, error) { + // Marshal request to JSON + req := SchedulerCancelScheduleRequest{ + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_cancelschedule(scheduleIDMem.Offset()) + responsePtr := scheduler_cancelschedule(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response SchedulerCancelScheduleResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/examples/discord-rich-presence/nd_host_websocket.go b/plugins/examples/discord-rich-presence/nd_host_websocket.go index 462c00754..e6daea765 100644 --- a/plugins/examples/discord-rich-presence/nd_host_websocket.go +++ b/plugins/examples/discord-rich-presence/nd_host_websocket.go @@ -9,13 +9,11 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) // websocket_connect is the host function provided by Navidrome. -// Takes a single JSON request pointer containing url, headers, and connectionID. // //go:wasmimport extism:host/user websocket_connect func websocket_connect(uint64) uint64 @@ -23,17 +21,17 @@ func websocket_connect(uint64) uint64 // websocket_sendtext is the host function provided by Navidrome. // //go:wasmimport extism:host/user websocket_sendtext -func websocket_sendtext(uint64, uint64) uint64 +func websocket_sendtext(uint64) uint64 // websocket_sendbinary is the host function provided by Navidrome. // //go:wasmimport extism:host/user websocket_sendbinary -func websocket_sendbinary(uint64, uint64) uint64 +func websocket_sendbinary(uint64) uint64 // websocket_closeconnection is the host function provided by Navidrome. // //go:wasmimport extism:host/user websocket_closeconnection -func websocket_closeconnection(uint64, int32, uint64) uint64 +func websocket_closeconnection(uint64) uint64 // WebSocketConnectRequest is the request type for WebSocket.Connect. type WebSocketConnectRequest struct { @@ -48,6 +46,40 @@ type WebSocketConnectResponse struct { Error string `json:"error,omitempty"` } +// WebSocketSendTextRequest is the request type for WebSocket.SendText. +type WebSocketSendTextRequest struct { + ConnectionID string `json:"connectionID"` + Message string `json:"message"` +} + +// WebSocketSendTextResponse is the response type for WebSocket.SendText. +type WebSocketSendTextResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary. +type WebSocketSendBinaryRequest struct { + ConnectionID string `json:"connectionID"` + Data []byte `json:"data"` +} + +// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary. +type WebSocketSendBinaryResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection. +type WebSocketCloseConnectionRequest struct { + ConnectionID string `json:"connectionID"` + Code int32 `json:"code"` + Reason string `json:"reason"` +} + +// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection. +type WebSocketCloseConnectionResponse struct { + Error string `json:"error,omitempty"` +} + // WebSocketConnect calls the websocket_connect host function. // Connect establishes a WebSocket connection to the specified URL. // @@ -62,7 +94,7 @@ type WebSocketConnectResponse struct { // Returns the connection ID that can be used to send messages or close the connection, // or an error if the connection fails. func WebSocketConnect(url string, headers map[string]string, connectionID string) (*WebSocketConnectResponse, error) { - // Create JSON request with all parameters + // Marshal request to JSON req := WebSocketConnectRequest{ Url: url, Headers: headers, @@ -75,7 +107,7 @@ func WebSocketConnect(url string, headers map[string]string, connectionID string reqMem := pdk.AllocateBytes(reqBytes) defer reqMem.Free() - // Call the host function with single JSON request + // Call the host function responsePtr := websocket_connect(reqMem.Offset()) // Read the response from memory @@ -99,24 +131,33 @@ func WebSocketConnect(url string, headers map[string]string, connectionID string // - message: The text message to send // // Returns an error if the connection is not found or if sending fails. -func WebSocketSendText(connectionID string, message string) error { - connectionIDMem := pdk.AllocateString(connectionID) - defer connectionIDMem.Free() - messageMem := pdk.AllocateString(message) - defer messageMem.Free() +func WebSocketSendText(connectionID string, message string) (*WebSocketSendTextResponse, error) { + // Marshal request to JSON + req := WebSocketSendTextRequest{ + ConnectionID: connectionID, + Message: message, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := websocket_sendtext(connectionIDMem.Offset(), messageMem.Offset()) + responsePtr := websocket_sendtext(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response WebSocketSendTextResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // WebSocketSendBinary calls the websocket_sendbinary host function. @@ -127,24 +168,33 @@ func WebSocketSendText(connectionID string, message string) error { // - data: The binary data to send // // Returns an error if the connection is not found or if sending fails. -func WebSocketSendBinary(connectionID string, data []byte) error { - connectionIDMem := pdk.AllocateString(connectionID) - defer connectionIDMem.Free() - dataMem := pdk.AllocateBytes(data) - defer dataMem.Free() +func WebSocketSendBinary(connectionID string, data []byte) (*WebSocketSendBinaryResponse, error) { + // Marshal request to JSON + req := WebSocketSendBinaryRequest{ + ConnectionID: connectionID, + Data: data, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := websocket_sendbinary(connectionIDMem.Offset(), dataMem.Offset()) + responsePtr := websocket_sendbinary(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response WebSocketSendBinaryResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // WebSocketCloseConnection calls the websocket_closeconnection host function. @@ -156,22 +206,32 @@ func WebSocketSendBinary(connectionID string, data []byte) error { // - reason: Optional human-readable reason for closing // // Returns an error if the connection is not found or if closing fails. -func WebSocketCloseConnection(connectionID string, code int32, reason string) error { - connectionIDMem := pdk.AllocateString(connectionID) - defer connectionIDMem.Free() - reasonMem := pdk.AllocateString(reason) - defer reasonMem.Free() +func WebSocketCloseConnection(connectionID string, code int32, reason string) (*WebSocketCloseConnectionResponse, error) { + // Marshal request to JSON + req := WebSocketCloseConnectionRequest{ + ConnectionID: connectionID, + Code: code, + Reason: reason, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := websocket_closeconnection(connectionIDMem.Offset(), code, reasonMem.Offset()) + responsePtr := websocket_closeconnection(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response WebSocketCloseConnectionResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/examples/discord-rich-presence/rpc.go b/plugins/examples/discord-rich-presence/rpc.go index 114c0eef1..12f0d624b 100644 --- a/plugins/examples/discord-rich-presence/rpc.go +++ b/plugins/examples/discord-rich-presence/rpc.go @@ -140,7 +140,7 @@ func processImage(imageURL, clientID, token string, isDefaultImage bool) (string ttl = 48 * 60 * 60 // 48 hours for default image } - _ = CacheSetString(cacheKey, processedImage, ttl) + _, _ = CacheSetString(cacheKey, processedImage, ttl) pdk.Log(pdk.LogDebug, fmt.Sprintf("Cached processed image URL for %s (TTL: %ds)", imageURL, ttl)) return processedImage, nil @@ -183,9 +183,13 @@ func sendMessage(username string, opCode int, payload any) error { return fmt.Errorf("failed to marshal message: %w", err) } - if err := WebSocketSendText(username, string(b)); err != nil { + resp, err := WebSocketSendText(username, string(b)) + if err != nil { return fmt.Errorf("failed to send message: %w", err) } + if resp.Error != "" { + return fmt.Errorf("failed to send message: %s", resp.Error) + } return nil } @@ -220,17 +224,21 @@ func cleanupFailedConnection(username string) { pdk.Log(pdk.LogInfo, fmt.Sprintf("Cleaning up failed connection for user %s", username)) // Cancel the heartbeat schedule - if err := SchedulerCancelSchedule(username); err != nil { + if resp, err := SchedulerCancelSchedule(username); err != nil { pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to cancel heartbeat schedule for user %s: %v", username, err)) + } else if resp.Error != "" { + pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to cancel heartbeat schedule for user %s: %s", username, resp.Error)) } // Close the WebSocket connection - if err := WebSocketCloseConnection(username, 1000, "Connection lost"); err != nil { + if resp, err := WebSocketCloseConnection(username, 1000, "Connection lost"); err != nil { pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to close WebSocket connection for user %s: %v", username, err)) + } else if resp.Error != "" { + pdk.Log(pdk.LogWarn, fmt.Sprintf("Failed to close WebSocket connection for user %s: %s", username, resp.Error)) } // Clean up cache entries - _ = CacheRemove(fmt.Sprintf("discord.seq.%s", username)) + _, _ = CacheRemove(fmt.Sprintf("discord.seq.%s", username)) pdk.Log(pdk.LogInfo, fmt.Sprintf("Cleaned up connection for user %s", username)) } @@ -297,12 +305,16 @@ func connect(username, token string) error { // disconnect closes the Discord connection for a user. func disconnect(username string) error { - if err := SchedulerCancelSchedule(username); err != nil { + if resp, err := SchedulerCancelSchedule(username); err != nil { return fmt.Errorf("failed to cancel schedule: %w", err) + } else if resp.Error != "" { + return fmt.Errorf("failed to cancel schedule: %s", resp.Error) } - if err := WebSocketCloseConnection(username, 1000, "Navidrome disconnect"); err != nil { + if resp, err := WebSocketCloseConnection(username, 1000, "Navidrome disconnect"); err != nil { return fmt.Errorf("failed to close WebSocket connection: %w", err) + } else if resp.Error != "" { + return fmt.Errorf("failed to close WebSocket connection: %s", resp.Error) } return nil } @@ -325,8 +337,10 @@ func handleWebSocketMessage(connectionID, message string) error { if v := msg["s"]; v != nil { seq := int64(v.(float64)) pdk.Log(pdk.LogTrace, fmt.Sprintf("Received sequence number for connection '%s': %d", connectionID, seq)) - if err := CacheSetInt(fmt.Sprintf("discord.seq.%s", connectionID), seq, int64(heartbeatInterval*2)); err != nil { + if resp, err := CacheSetInt(fmt.Sprintf("discord.seq.%s", connectionID), seq, int64(heartbeatInterval*2)); err != nil { return fmt.Errorf("failed to store sequence number for user %s: %w", connectionID, err) + } else if resp.Error != "" { + return fmt.Errorf("failed to store sequence number for user %s: %s", connectionID, resp.Error) } } return nil diff --git a/plugins/host/artwork_gen.go b/plugins/host/artwork_gen.go index 4615ef1d0..ff76ba7cf 100644 --- a/plugins/host/artwork_gen.go +++ b/plugins/host/artwork_gen.go @@ -9,24 +9,48 @@ import ( extism "github.com/extism/go-sdk" ) +// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl. +type ArtworkGetArtistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl. type ArtworkGetArtistUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl. +type ArtworkGetAlbumUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl. type ArtworkGetAlbumUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl. +type ArtworkGetTrackUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl. type ArtworkGetTrackUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl. +type ArtworkGetPlaylistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl. type ArtworkGetPlaylistUrlResponse struct { Url string `json:"url,omitempty"` @@ -48,26 +72,32 @@ func newArtworkGetArtistUrlHostFunction(service ArtworkService) extism.HostFunct return extism.NewHostFunctionWithStack( "artwork_getartisturl", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - id, err := p.ReadString(stack[0]) - if err != nil { - return - } - size := extism.DecodeI32(stack[1]) - - // Call the service method - url, err := service.GetArtistUrl(ctx, id, size) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { artworkWriteError(p, stack, err) return } + var req ArtworkGetArtistUrlRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + artworkWriteError(p, stack, err) + return + } + + // Call the service method + url, svcErr := service.GetArtistUrl(ctx, req.Id, req.Size) + if svcErr != nil { + artworkWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := ArtworkGetArtistUrlResponse{ Url: url, } artworkWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -76,26 +106,32 @@ func newArtworkGetAlbumUrlHostFunction(service ArtworkService) extism.HostFuncti return extism.NewHostFunctionWithStack( "artwork_getalbumurl", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - id, err := p.ReadString(stack[0]) - if err != nil { - return - } - size := extism.DecodeI32(stack[1]) - - // Call the service method - url, err := service.GetAlbumUrl(ctx, id, size) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { artworkWriteError(p, stack, err) return } + var req ArtworkGetAlbumUrlRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + artworkWriteError(p, stack, err) + return + } + + // Call the service method + url, svcErr := service.GetAlbumUrl(ctx, req.Id, req.Size) + if svcErr != nil { + artworkWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := ArtworkGetAlbumUrlResponse{ Url: url, } artworkWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -104,26 +140,32 @@ func newArtworkGetTrackUrlHostFunction(service ArtworkService) extism.HostFuncti return extism.NewHostFunctionWithStack( "artwork_gettrackurl", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - id, err := p.ReadString(stack[0]) - if err != nil { - return - } - size := extism.DecodeI32(stack[1]) - - // Call the service method - url, err := service.GetTrackUrl(ctx, id, size) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { artworkWriteError(p, stack, err) return } + var req ArtworkGetTrackUrlRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + artworkWriteError(p, stack, err) + return + } + + // Call the service method + url, svcErr := service.GetTrackUrl(ctx, req.Id, req.Size) + if svcErr != nil { + artworkWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := ArtworkGetTrackUrlResponse{ Url: url, } artworkWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -132,26 +174,32 @@ func newArtworkGetPlaylistUrlHostFunction(service ArtworkService) extism.HostFun return extism.NewHostFunctionWithStack( "artwork_getplaylisturl", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - id, err := p.ReadString(stack[0]) - if err != nil { - return - } - size := extism.DecodeI32(stack[1]) - - // Call the service method - url, err := service.GetPlaylistUrl(ctx, id, size) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { artworkWriteError(p, stack, err) return } + var req ArtworkGetPlaylistUrlRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + artworkWriteError(p, stack, err) + return + } + + // Call the service method + url, svcErr := service.GetPlaylistUrl(ctx, req.Id, req.Size) + if svcErr != nil { + artworkWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := ArtworkGetPlaylistUrlResponse{ Url: url, } artworkWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } diff --git a/plugins/host/cache_gen.go b/plugins/host/cache_gen.go index 613c781c3..792f7420c 100644 --- a/plugins/host/cache_gen.go +++ b/plugins/host/cache_gen.go @@ -9,6 +9,23 @@ import ( extism "github.com/extism/go-sdk" ) +// CacheSetStringRequest is the request type for Cache.SetString. +type CacheSetStringRequest struct { + Key string `json:"key"` + Value string `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetStringResponse is the response type for Cache.SetString. +type CacheSetStringResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetStringRequest is the request type for Cache.GetString. +type CacheGetStringRequest struct { + Key string `json:"key"` +} + // CacheGetStringResponse is the response type for Cache.GetString. type CacheGetStringResponse struct { Value string `json:"value,omitempty"` @@ -16,6 +33,23 @@ type CacheGetStringResponse struct { Error string `json:"error,omitempty"` } +// CacheSetIntRequest is the request type for Cache.SetInt. +type CacheSetIntRequest struct { + Key string `json:"key"` + Value int64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetIntResponse is the response type for Cache.SetInt. +type CacheSetIntResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetIntRequest is the request type for Cache.GetInt. +type CacheGetIntRequest struct { + Key string `json:"key"` +} + // CacheGetIntResponse is the response type for Cache.GetInt. type CacheGetIntResponse struct { Value int64 `json:"value,omitempty"` @@ -23,6 +57,23 @@ type CacheGetIntResponse struct { Error string `json:"error,omitempty"` } +// CacheSetFloatRequest is the request type for Cache.SetFloat. +type CacheSetFloatRequest struct { + Key string `json:"key"` + Value float64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetFloatResponse is the response type for Cache.SetFloat. +type CacheSetFloatResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetFloatRequest is the request type for Cache.GetFloat. +type CacheGetFloatRequest struct { + Key string `json:"key"` +} + // CacheGetFloatResponse is the response type for Cache.GetFloat. type CacheGetFloatResponse struct { Value float64 `json:"value,omitempty"` @@ -30,6 +81,23 @@ type CacheGetFloatResponse struct { Error string `json:"error,omitempty"` } +// CacheSetBytesRequest is the request type for Cache.SetBytes. +type CacheSetBytesRequest struct { + Key string `json:"key"` + Value []byte `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetBytesResponse is the response type for Cache.SetBytes. +type CacheSetBytesResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetBytesRequest is the request type for Cache.GetBytes. +type CacheGetBytesRequest struct { + Key string `json:"key"` +} + // CacheGetBytesResponse is the response type for Cache.GetBytes. type CacheGetBytesResponse struct { Value []byte `json:"value,omitempty"` @@ -37,12 +105,27 @@ type CacheGetBytesResponse struct { Error string `json:"error,omitempty"` } +// CacheHasRequest is the request type for Cache.Has. +type CacheHasRequest struct { + Key string `json:"key"` +} + // CacheHasResponse is the response type for Cache.Has. type CacheHasResponse struct { Exists bool `json:"exists,omitempty"` Error string `json:"error,omitempty"` } +// CacheRemoveRequest is the request type for Cache.Remove. +type CacheRemoveRequest struct { + Key string `json:"key"` +} + +// CacheRemoveResponse is the response type for Cache.Remove. +type CacheRemoveResponse struct { + Error string `json:"error,omitempty"` +} + // RegisterCacheHostFunctions registers Cache service host functions. // The returned host functions should be added to the plugin's configuration. func RegisterCacheHostFunctions(service CacheService) []extism.HostFunction { @@ -64,32 +147,29 @@ func newCacheSetStringHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_setstring", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + cacheWriteError(p, stack, err) return } - value, err := p.ReadString(stack[1]) - if err != nil { + var req CacheSetStringRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) return } - ttlSeconds := int64(stack[2]) // Call the service method - err = service.SetString(ctx, key, value, ttlSeconds) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.SetString(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil { + cacheWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := CacheSetStringResponse{} + cacheWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypePTR, extism.ValueTypeI64}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -98,18 +178,25 @@ func newCacheGetStringHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_getstring", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - value, exists, err := service.GetString(ctx, key) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { cacheWriteError(p, stack, err) return } + var req CacheGetStringRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) + return + } + + // Call the service method + value, exists, svcErr := service.GetString(ctx, req.Key) + if svcErr != nil { + cacheWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := CacheGetStringResponse{ Value: value, @@ -126,29 +213,29 @@ func newCacheSetIntHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_setint", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + cacheWriteError(p, stack, err) + return + } + var req CacheSetIntRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) return } - value := int64(stack[1]) - ttlSeconds := int64(stack[2]) // Call the service method - err = service.SetInt(ctx, key, value, ttlSeconds) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.SetInt(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil { + cacheWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := CacheSetIntResponse{} + cacheWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI64, extism.ValueTypeI64}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -157,18 +244,25 @@ func newCacheGetIntHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_getint", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - value, exists, err := service.GetInt(ctx, key) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { cacheWriteError(p, stack, err) return } + var req CacheGetIntRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) + return + } + + // Call the service method + value, exists, svcErr := service.GetInt(ctx, req.Key) + if svcErr != nil { + cacheWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := CacheGetIntResponse{ Value: value, @@ -185,29 +279,29 @@ func newCacheSetFloatHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_setfloat", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + cacheWriteError(p, stack, err) + return + } + var req CacheSetFloatRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) return } - value := extism.DecodeF64(stack[1]) - ttlSeconds := int64(stack[2]) // Call the service method - err = service.SetFloat(ctx, key, value, ttlSeconds) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.SetFloat(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil { + cacheWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := CacheSetFloatResponse{} + cacheWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypeF64, extism.ValueTypeI64}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -216,18 +310,25 @@ func newCacheGetFloatHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_getfloat", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - value, exists, err := service.GetFloat(ctx, key) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { cacheWriteError(p, stack, err) return } + var req CacheGetFloatRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) + return + } + + // Call the service method + value, exists, svcErr := service.GetFloat(ctx, req.Key) + if svcErr != nil { + cacheWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := CacheGetFloatResponse{ Value: value, @@ -244,32 +345,29 @@ func newCacheSetBytesHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_setbytes", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + cacheWriteError(p, stack, err) return } - value, err := p.ReadBytes(stack[1]) - if err != nil { + var req CacheSetBytesRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) return } - ttlSeconds := int64(stack[2]) // Call the service method - err = service.SetBytes(ctx, key, value, ttlSeconds) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.SetBytes(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil { + cacheWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := CacheSetBytesResponse{} + cacheWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypePTR, extism.ValueTypeI64}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -278,18 +376,25 @@ func newCacheGetBytesHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_getbytes", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - value, exists, err := service.GetBytes(ctx, key) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { cacheWriteError(p, stack, err) return } + var req CacheGetBytesRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) + return + } + + // Call the service method + value, exists, svcErr := service.GetBytes(ctx, req.Key) + if svcErr != nil { + cacheWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := CacheGetBytesResponse{ Value: value, @@ -306,18 +411,25 @@ func newCacheHasHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_has", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - exists, err := service.Has(ctx, key) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { cacheWriteError(p, stack, err) return } + var req CacheHasRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) + return + } + + // Call the service method + exists, svcErr := service.Has(ctx, req.Key) + if svcErr != nil { + cacheWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := CacheHasResponse{ Exists: exists, @@ -333,25 +445,27 @@ func newCacheRemoveHostFunction(service CacheService) extism.HostFunction { return extism.NewHostFunctionWithStack( "cache_remove", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - key, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + cacheWriteError(p, stack, err) + return + } + var req CacheRemoveRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + cacheWriteError(p, stack, err) return } // Call the service method - err = service.Remove(ctx, key) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.Remove(ctx, req.Key); svcErr != nil { + cacheWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := CacheRemoveResponse{} + cacheWriteResponse(p, stack, resp) }, []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, diff --git a/plugins/host/go/nd_host_artwork.go b/plugins/host/go/nd_host_artwork.go index 0943c794c..65e5efb81 100644 --- a/plugins/host/go/nd_host_artwork.go +++ b/plugins/host/go/nd_host_artwork.go @@ -16,22 +16,28 @@ import ( // artwork_getartisturl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getartisturl -func artwork_getartisturl(uint64, int32) uint64 +func artwork_getartisturl(uint64) uint64 // artwork_getalbumurl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getalbumurl -func artwork_getalbumurl(uint64, int32) uint64 +func artwork_getalbumurl(uint64) uint64 // artwork_gettrackurl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_gettrackurl -func artwork_gettrackurl(uint64, int32) uint64 +func artwork_gettrackurl(uint64) uint64 // artwork_getplaylisturl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getplaylisturl -func artwork_getplaylisturl(uint64, int32) uint64 +func artwork_getplaylisturl(uint64) uint64 + +// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl. +type ArtworkGetArtistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} // ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl. type ArtworkGetArtistUrlResponse struct { @@ -39,18 +45,36 @@ type ArtworkGetArtistUrlResponse struct { Error string `json:"error,omitempty"` } +// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl. +type ArtworkGetAlbumUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl. type ArtworkGetAlbumUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl. +type ArtworkGetTrackUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl. type ArtworkGetTrackUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl. +type ArtworkGetPlaylistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl. type ArtworkGetPlaylistUrlResponse struct { Url string `json:"url,omitempty"` @@ -66,11 +90,20 @@ type ArtworkGetPlaylistUrlResponse struct { // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetArtistUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getartisturl(idMem.Offset(), size) + responsePtr := artwork_getartisturl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -94,11 +127,20 @@ func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, e // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetAlbumUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getalbumurl(idMem.Offset(), size) + responsePtr := artwork_getalbumurl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -122,11 +164,20 @@ func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, err // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetTrackUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_gettrackurl(idMem.Offset(), size) + responsePtr := artwork_gettrackurl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -150,11 +201,20 @@ func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, err // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetPlaylistUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getplaylisturl(idMem.Offset(), size) + responsePtr := artwork_getplaylisturl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/host/go/nd_host_cache.go b/plugins/host/go/nd_host_cache.go index c9798f2b2..36be869ae 100644 --- a/plugins/host/go/nd_host_cache.go +++ b/plugins/host/go/nd_host_cache.go @@ -9,7 +9,6 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) @@ -17,7 +16,7 @@ import ( // cache_setstring is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setstring -func cache_setstring(uint64, uint64, int64) uint64 +func cache_setstring(uint64) uint64 // cache_getstring is the host function provided by Navidrome. // @@ -27,7 +26,7 @@ func cache_getstring(uint64) uint64 // cache_setint is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setint -func cache_setint(uint64, int64, int64) uint64 +func cache_setint(uint64) uint64 // cache_getint is the host function provided by Navidrome. // @@ -37,7 +36,7 @@ func cache_getint(uint64) uint64 // cache_setfloat is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setfloat -func cache_setfloat(uint64, float64, int64) uint64 +func cache_setfloat(uint64) uint64 // cache_getfloat is the host function provided by Navidrome. // @@ -47,7 +46,7 @@ func cache_getfloat(uint64) uint64 // cache_setbytes is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setbytes -func cache_setbytes(uint64, uint64, int64) uint64 +func cache_setbytes(uint64) uint64 // cache_getbytes is the host function provided by Navidrome. // @@ -64,6 +63,23 @@ func cache_has(uint64) uint64 //go:wasmimport extism:host/user cache_remove func cache_remove(uint64) uint64 +// CacheSetStringRequest is the request type for Cache.SetString. +type CacheSetStringRequest struct { + Key string `json:"key"` + Value string `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetStringResponse is the response type for Cache.SetString. +type CacheSetStringResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetStringRequest is the request type for Cache.GetString. +type CacheGetStringRequest struct { + Key string `json:"key"` +} + // CacheGetStringResponse is the response type for Cache.GetString. type CacheGetStringResponse struct { Value string `json:"value,omitempty"` @@ -71,6 +87,23 @@ type CacheGetStringResponse struct { Error string `json:"error,omitempty"` } +// CacheSetIntRequest is the request type for Cache.SetInt. +type CacheSetIntRequest struct { + Key string `json:"key"` + Value int64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetIntResponse is the response type for Cache.SetInt. +type CacheSetIntResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetIntRequest is the request type for Cache.GetInt. +type CacheGetIntRequest struct { + Key string `json:"key"` +} + // CacheGetIntResponse is the response type for Cache.GetInt. type CacheGetIntResponse struct { Value int64 `json:"value,omitempty"` @@ -78,6 +111,23 @@ type CacheGetIntResponse struct { Error string `json:"error,omitempty"` } +// CacheSetFloatRequest is the request type for Cache.SetFloat. +type CacheSetFloatRequest struct { + Key string `json:"key"` + Value float64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetFloatResponse is the response type for Cache.SetFloat. +type CacheSetFloatResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetFloatRequest is the request type for Cache.GetFloat. +type CacheGetFloatRequest struct { + Key string `json:"key"` +} + // CacheGetFloatResponse is the response type for Cache.GetFloat. type CacheGetFloatResponse struct { Value float64 `json:"value,omitempty"` @@ -85,6 +135,23 @@ type CacheGetFloatResponse struct { Error string `json:"error,omitempty"` } +// CacheSetBytesRequest is the request type for Cache.SetBytes. +type CacheSetBytesRequest struct { + Key string `json:"key"` + Value []byte `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetBytesResponse is the response type for Cache.SetBytes. +type CacheSetBytesResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetBytesRequest is the request type for Cache.GetBytes. +type CacheGetBytesRequest struct { + Key string `json:"key"` +} + // CacheGetBytesResponse is the response type for Cache.GetBytes. type CacheGetBytesResponse struct { Value []byte `json:"value,omitempty"` @@ -92,12 +159,27 @@ type CacheGetBytesResponse struct { Error string `json:"error,omitempty"` } +// CacheHasRequest is the request type for Cache.Has. +type CacheHasRequest struct { + Key string `json:"key"` +} + // CacheHasResponse is the response type for Cache.Has. type CacheHasResponse struct { Exists bool `json:"exists,omitempty"` Error string `json:"error,omitempty"` } +// CacheRemoveRequest is the request type for Cache.Remove. +type CacheRemoveRequest struct { + Key string `json:"key"` +} + +// CacheRemoveResponse is the response type for Cache.Remove. +type CacheRemoveResponse struct { + Error string `json:"error,omitempty"` +} + // CacheSetString calls the cache_setstring host function. // SetString stores a string value in the cache. // @@ -107,24 +189,34 @@ type CacheHasResponse struct { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetString(key string, value string, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() - valueMem := pdk.AllocateString(value) - defer valueMem.Free() +func CacheSetString(key string, value string, ttlSeconds int64) (*CacheSetStringResponse, error) { + // Marshal request to JSON + req := CacheSetStringRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setstring(keyMem.Offset(), valueMem.Offset(), ttlSeconds) + responsePtr := cache_setstring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetStringResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetString calls the cache_getstring host function. @@ -136,11 +228,19 @@ func CacheSetString(key string, value string, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a string, exists will be false. func CacheGetString(key string) (*CacheGetStringResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetStringRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getstring(keyMem.Offset()) + responsePtr := cache_getstring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -164,22 +264,34 @@ func CacheGetString(key string) (*CacheGetStringResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetInt(key string, value int64, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheSetInt(key string, value int64, ttlSeconds int64) (*CacheSetIntResponse, error) { + // Marshal request to JSON + req := CacheSetIntRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setint(keyMem.Offset(), value, ttlSeconds) + responsePtr := cache_setint(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetIntResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetInt calls the cache_getint host function. @@ -191,11 +303,19 @@ func CacheSetInt(key string, value int64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not an integer, exists will be false. func CacheGetInt(key string) (*CacheGetIntResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetIntRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getint(keyMem.Offset()) + responsePtr := cache_getint(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -219,22 +339,34 @@ func CacheGetInt(key string) (*CacheGetIntResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetFloat(key string, value float64, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheSetFloat(key string, value float64, ttlSeconds int64) (*CacheSetFloatResponse, error) { + // Marshal request to JSON + req := CacheSetFloatRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setfloat(keyMem.Offset(), value, ttlSeconds) + responsePtr := cache_setfloat(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetFloatResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetFloat calls the cache_getfloat host function. @@ -246,11 +378,19 @@ func CacheSetFloat(key string, value float64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a float, exists will be false. func CacheGetFloat(key string) (*CacheGetFloatResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetFloatRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getfloat(keyMem.Offset()) + responsePtr := cache_getfloat(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -274,24 +414,34 @@ func CacheGetFloat(key string) (*CacheGetFloatResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() - valueMem := pdk.AllocateBytes(value) - defer valueMem.Free() +func CacheSetBytes(key string, value []byte, ttlSeconds int64) (*CacheSetBytesResponse, error) { + // Marshal request to JSON + req := CacheSetBytesRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setbytes(keyMem.Offset(), valueMem.Offset(), ttlSeconds) + responsePtr := cache_setbytes(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetBytesResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetBytes calls the cache_getbytes host function. @@ -303,11 +453,19 @@ func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a byte slice, exists will be false. func CacheGetBytes(key string) (*CacheGetBytesResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetBytesRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getbytes(keyMem.Offset()) + responsePtr := cache_getbytes(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -330,11 +488,19 @@ func CacheGetBytes(key string) (*CacheGetBytesResponse, error) { // // Returns true if the key exists and has not expired. func CacheHas(key string) (*CacheHasResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheHasRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_has(keyMem.Offset()) + responsePtr := cache_has(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -356,20 +522,30 @@ func CacheHas(key string) (*CacheHasResponse, error) { // - key: The cache key (will be namespaced with plugin ID) // // Returns an error if the operation fails. Does not return an error if the key doesn't exist. -func CacheRemove(key string) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheRemove(key string) (*CacheRemoveResponse, error) { + // Marshal request to JSON + req := CacheRemoveRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_remove(keyMem.Offset()) + responsePtr := cache_remove(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheRemoveResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/host/go/nd_host_scheduler.go b/plugins/host/go/nd_host_scheduler.go index 1aef70972..d341343c8 100644 --- a/plugins/host/go/nd_host_scheduler.go +++ b/plugins/host/go/nd_host_scheduler.go @@ -9,7 +9,6 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) @@ -17,30 +16,54 @@ import ( // scheduler_scheduleonetime is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_scheduleonetime -func scheduler_scheduleonetime(int32, uint64, uint64) uint64 +func scheduler_scheduleonetime(uint64) uint64 // scheduler_schedulerecurring is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_schedulerecurring -func scheduler_schedulerecurring(uint64, uint64, uint64) uint64 +func scheduler_schedulerecurring(uint64) uint64 // scheduler_cancelschedule is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_cancelschedule func scheduler_cancelschedule(uint64) uint64 +// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeRequest struct { + DelaySeconds int32 `json:"delaySeconds"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime. type SchedulerScheduleOneTimeResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringRequest struct { + CronExpression string `json:"cronExpression"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring. type SchedulerScheduleRecurringResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleRequest struct { + ScheduleID string `json:"scheduleID"` +} + +// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleResponse struct { + Error string `json:"error,omitempty"` +} + // SchedulerScheduleOneTime calls the scheduler_scheduleonetime host function. // ScheduleOneTime schedules a one-time event to be triggered after the specified delay. // Plugins that use this function must also implement the SchedulerCallback capability @@ -52,13 +75,21 @@ type SchedulerScheduleRecurringResponse struct { // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) { - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() + // Marshal request to JSON + req := SchedulerScheduleOneTimeRequest{ + DelaySeconds: delaySeconds, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_scheduleonetime(delaySeconds, payloadMem.Offset(), scheduleIDMem.Offset()) + responsePtr := scheduler_scheduleonetime(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -84,15 +115,21 @@ func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID str // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (*SchedulerScheduleRecurringResponse, error) { - cronExpressionMem := pdk.AllocateString(cronExpression) - defer cronExpressionMem.Free() - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() + // Marshal request to JSON + req := SchedulerScheduleRecurringRequest{ + CronExpression: cronExpression, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_schedulerecurring(cronExpressionMem.Offset(), payloadMem.Offset(), scheduleIDMem.Offset()) + responsePtr := scheduler_schedulerecurring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -114,20 +151,30 @@ func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleI // any future events. // // Returns an error if the schedule ID is not found or if cancellation fails. -func SchedulerCancelSchedule(scheduleID string) error { - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() +func SchedulerCancelSchedule(scheduleID string) (*SchedulerCancelScheduleResponse, error) { + // Marshal request to JSON + req := SchedulerCancelScheduleRequest{ + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_cancelschedule(scheduleIDMem.Offset()) + responsePtr := scheduler_cancelschedule(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response SchedulerCancelScheduleResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/host/go/nd_host_subsonicapi.go b/plugins/host/go/nd_host_subsonicapi.go index ea1de838d..ec69b718d 100644 --- a/plugins/host/go/nd_host_subsonicapi.go +++ b/plugins/host/go/nd_host_subsonicapi.go @@ -18,6 +18,11 @@ import ( //go:wasmimport extism:host/user subsonicapi_call func subsonicapi_call(uint64) uint64 +// SubsonicAPICallRequest is the request type for SubsonicAPI.Call. +type SubsonicAPICallRequest struct { + Uri string `json:"uri"` +} + // SubsonicAPICallResponse is the response type for SubsonicAPI.Call. type SubsonicAPICallResponse struct { ResponseJSON string `json:"responseJSON,omitempty"` @@ -30,11 +35,19 @@ type SubsonicAPICallResponse struct { // The uri parameter should be the Subsonic API path without the server prefix, // e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON. func SubsonicAPICall(uri string) (*SubsonicAPICallResponse, error) { - uriMem := pdk.AllocateString(uri) - defer uriMem.Free() + // Marshal request to JSON + req := SubsonicAPICallRequest{ + Uri: uri, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := subsonicapi_call(uriMem.Offset()) + responsePtr := subsonicapi_call(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/host/go/nd_host_websocket.go b/plugins/host/go/nd_host_websocket.go index 462c00754..e6daea765 100644 --- a/plugins/host/go/nd_host_websocket.go +++ b/plugins/host/go/nd_host_websocket.go @@ -9,13 +9,11 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) // websocket_connect is the host function provided by Navidrome. -// Takes a single JSON request pointer containing url, headers, and connectionID. // //go:wasmimport extism:host/user websocket_connect func websocket_connect(uint64) uint64 @@ -23,17 +21,17 @@ func websocket_connect(uint64) uint64 // websocket_sendtext is the host function provided by Navidrome. // //go:wasmimport extism:host/user websocket_sendtext -func websocket_sendtext(uint64, uint64) uint64 +func websocket_sendtext(uint64) uint64 // websocket_sendbinary is the host function provided by Navidrome. // //go:wasmimport extism:host/user websocket_sendbinary -func websocket_sendbinary(uint64, uint64) uint64 +func websocket_sendbinary(uint64) uint64 // websocket_closeconnection is the host function provided by Navidrome. // //go:wasmimport extism:host/user websocket_closeconnection -func websocket_closeconnection(uint64, int32, uint64) uint64 +func websocket_closeconnection(uint64) uint64 // WebSocketConnectRequest is the request type for WebSocket.Connect. type WebSocketConnectRequest struct { @@ -48,6 +46,40 @@ type WebSocketConnectResponse struct { Error string `json:"error,omitempty"` } +// WebSocketSendTextRequest is the request type for WebSocket.SendText. +type WebSocketSendTextRequest struct { + ConnectionID string `json:"connectionID"` + Message string `json:"message"` +} + +// WebSocketSendTextResponse is the response type for WebSocket.SendText. +type WebSocketSendTextResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary. +type WebSocketSendBinaryRequest struct { + ConnectionID string `json:"connectionID"` + Data []byte `json:"data"` +} + +// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary. +type WebSocketSendBinaryResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection. +type WebSocketCloseConnectionRequest struct { + ConnectionID string `json:"connectionID"` + Code int32 `json:"code"` + Reason string `json:"reason"` +} + +// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection. +type WebSocketCloseConnectionResponse struct { + Error string `json:"error,omitempty"` +} + // WebSocketConnect calls the websocket_connect host function. // Connect establishes a WebSocket connection to the specified URL. // @@ -62,7 +94,7 @@ type WebSocketConnectResponse struct { // Returns the connection ID that can be used to send messages or close the connection, // or an error if the connection fails. func WebSocketConnect(url string, headers map[string]string, connectionID string) (*WebSocketConnectResponse, error) { - // Create JSON request with all parameters + // Marshal request to JSON req := WebSocketConnectRequest{ Url: url, Headers: headers, @@ -75,7 +107,7 @@ func WebSocketConnect(url string, headers map[string]string, connectionID string reqMem := pdk.AllocateBytes(reqBytes) defer reqMem.Free() - // Call the host function with single JSON request + // Call the host function responsePtr := websocket_connect(reqMem.Offset()) // Read the response from memory @@ -99,24 +131,33 @@ func WebSocketConnect(url string, headers map[string]string, connectionID string // - message: The text message to send // // Returns an error if the connection is not found or if sending fails. -func WebSocketSendText(connectionID string, message string) error { - connectionIDMem := pdk.AllocateString(connectionID) - defer connectionIDMem.Free() - messageMem := pdk.AllocateString(message) - defer messageMem.Free() +func WebSocketSendText(connectionID string, message string) (*WebSocketSendTextResponse, error) { + // Marshal request to JSON + req := WebSocketSendTextRequest{ + ConnectionID: connectionID, + Message: message, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := websocket_sendtext(connectionIDMem.Offset(), messageMem.Offset()) + responsePtr := websocket_sendtext(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response WebSocketSendTextResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // WebSocketSendBinary calls the websocket_sendbinary host function. @@ -127,24 +168,33 @@ func WebSocketSendText(connectionID string, message string) error { // - data: The binary data to send // // Returns an error if the connection is not found or if sending fails. -func WebSocketSendBinary(connectionID string, data []byte) error { - connectionIDMem := pdk.AllocateString(connectionID) - defer connectionIDMem.Free() - dataMem := pdk.AllocateBytes(data) - defer dataMem.Free() +func WebSocketSendBinary(connectionID string, data []byte) (*WebSocketSendBinaryResponse, error) { + // Marshal request to JSON + req := WebSocketSendBinaryRequest{ + ConnectionID: connectionID, + Data: data, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := websocket_sendbinary(connectionIDMem.Offset(), dataMem.Offset()) + responsePtr := websocket_sendbinary(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response WebSocketSendBinaryResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // WebSocketCloseConnection calls the websocket_closeconnection host function. @@ -156,22 +206,32 @@ func WebSocketSendBinary(connectionID string, data []byte) error { // - reason: Optional human-readable reason for closing // // Returns an error if the connection is not found or if closing fails. -func WebSocketCloseConnection(connectionID string, code int32, reason string) error { - connectionIDMem := pdk.AllocateString(connectionID) - defer connectionIDMem.Free() - reasonMem := pdk.AllocateString(reason) - defer reasonMem.Free() +func WebSocketCloseConnection(connectionID string, code int32, reason string) (*WebSocketCloseConnectionResponse, error) { + // Marshal request to JSON + req := WebSocketCloseConnectionRequest{ + ConnectionID: connectionID, + Code: code, + Reason: reason, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := websocket_closeconnection(connectionIDMem.Offset(), code, reasonMem.Offset()) + responsePtr := websocket_closeconnection(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response WebSocketCloseConnectionResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/host/scheduler_gen.go b/plugins/host/scheduler_gen.go index 39cc504d5..84fef8894 100644 --- a/plugins/host/scheduler_gen.go +++ b/plugins/host/scheduler_gen.go @@ -9,18 +9,42 @@ import ( extism "github.com/extism/go-sdk" ) +// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeRequest struct { + DelaySeconds int32 `json:"delaySeconds"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime. type SchedulerScheduleOneTimeResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringRequest struct { + CronExpression string `json:"cronExpression"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring. type SchedulerScheduleRecurringResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleRequest struct { + ScheduleID string `json:"scheduleID"` +} + +// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleResponse struct { + Error string `json:"error,omitempty"` +} + // RegisterSchedulerHostFunctions registers Scheduler service host functions. // The returned host functions should be added to the plugin's configuration. func RegisterSchedulerHostFunctions(service SchedulerService) []extism.HostFunction { @@ -35,30 +59,32 @@ func newSchedulerScheduleOneTimeHostFunction(service SchedulerService) extism.Ho return extism.NewHostFunctionWithStack( "scheduler_scheduleonetime", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - delaySeconds := extism.DecodeI32(stack[0]) - payload, err := p.ReadString(stack[1]) - if err != nil { - return - } - scheduleID, err := p.ReadString(stack[2]) - if err != nil { - return - } - - // Call the service method - newscheduleid, err := service.ScheduleOneTime(ctx, delaySeconds, payload, scheduleID) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { schedulerWriteError(p, stack, err) return } + var req SchedulerScheduleOneTimeRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + schedulerWriteError(p, stack, err) + return + } + + // Call the service method + newscheduleid, svcErr := service.ScheduleOneTime(ctx, req.DelaySeconds, req.Payload, req.ScheduleID) + if svcErr != nil { + schedulerWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := SchedulerScheduleOneTimeResponse{ NewScheduleID: newscheduleid, } schedulerWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypeI32, extism.ValueTypePTR, extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -67,33 +93,32 @@ func newSchedulerScheduleRecurringHostFunction(service SchedulerService) extism. return extism.NewHostFunctionWithStack( "scheduler_schedulerecurring", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - cronExpression, err := p.ReadString(stack[0]) - if err != nil { - return - } - payload, err := p.ReadString(stack[1]) - if err != nil { - return - } - scheduleID, err := p.ReadString(stack[2]) - if err != nil { - return - } - - // Call the service method - newscheduleid, err := service.ScheduleRecurring(ctx, cronExpression, payload, scheduleID) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { schedulerWriteError(p, stack, err) return } + var req SchedulerScheduleRecurringRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + schedulerWriteError(p, stack, err) + return + } + + // Call the service method + newscheduleid, svcErr := service.ScheduleRecurring(ctx, req.CronExpression, req.Payload, req.ScheduleID) + if svcErr != nil { + schedulerWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := SchedulerScheduleRecurringResponse{ NewScheduleID: newscheduleid, } schedulerWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypePTR, extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -102,25 +127,27 @@ func newSchedulerCancelScheduleHostFunction(service SchedulerService) extism.Hos return extism.NewHostFunctionWithStack( "scheduler_cancelschedule", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - scheduleID, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + schedulerWriteError(p, stack, err) + return + } + var req SchedulerCancelScheduleRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + schedulerWriteError(p, stack, err) return } // Call the service method - err = service.CancelSchedule(ctx, scheduleID) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.CancelSchedule(ctx, req.ScheduleID); svcErr != nil { + schedulerWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := SchedulerCancelScheduleResponse{} + schedulerWriteResponse(p, stack, resp) }, []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, diff --git a/plugins/host/subsonicapi_gen.go b/plugins/host/subsonicapi_gen.go index 5b42d67d8..92d187b25 100644 --- a/plugins/host/subsonicapi_gen.go +++ b/plugins/host/subsonicapi_gen.go @@ -9,6 +9,11 @@ import ( extism "github.com/extism/go-sdk" ) +// SubsonicAPICallRequest is the request type for SubsonicAPI.Call. +type SubsonicAPICallRequest struct { + Uri string `json:"uri"` +} + // SubsonicAPICallResponse is the response type for SubsonicAPI.Call. type SubsonicAPICallResponse struct { ResponseJSON string `json:"responseJSON,omitempty"` @@ -27,18 +32,25 @@ func newSubsonicAPICallHostFunction(service SubsonicAPIService) extism.HostFunct return extism.NewHostFunctionWithStack( "subsonicapi_call", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - uri, err := p.ReadString(stack[0]) - if err != nil { - return - } - - // Call the service method - responsejson, err := service.Call(ctx, uri) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { subsonicapiWriteError(p, stack, err) return } + var req SubsonicAPICallRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + subsonicapiWriteError(p, stack, err) + return + } + + // Call the service method + responsejson, svcErr := service.Call(ctx, req.Uri) + if svcErr != nil { + subsonicapiWriteError(p, stack, svcErr) + return + } + // Write JSON response to plugin memory resp := SubsonicAPICallResponse{ ResponseJSON: responsejson, diff --git a/plugins/host/websocket_gen.go b/plugins/host/websocket_gen.go index b134b0250..dc8f211a1 100644 --- a/plugins/host/websocket_gen.go +++ b/plugins/host/websocket_gen.go @@ -22,6 +22,40 @@ type WebSocketConnectResponse struct { Error string `json:"error,omitempty"` } +// WebSocketSendTextRequest is the request type for WebSocket.SendText. +type WebSocketSendTextRequest struct { + ConnectionID string `json:"connectionID"` + Message string `json:"message"` +} + +// WebSocketSendTextResponse is the response type for WebSocket.SendText. +type WebSocketSendTextResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary. +type WebSocketSendBinaryRequest struct { + ConnectionID string `json:"connectionID"` + Data []byte `json:"data"` +} + +// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary. +type WebSocketSendBinaryResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection. +type WebSocketCloseConnectionRequest struct { + ConnectionID string `json:"connectionID"` + Code int32 `json:"code"` + Reason string `json:"reason"` +} + +// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection. +type WebSocketCloseConnectionResponse struct { + Error string `json:"error,omitempty"` +} + // RegisterWebSocketHostFunctions registers WebSocket service host functions. // The returned host functions should be added to the plugin's configuration. func RegisterWebSocketHostFunctions(service WebSocketService) []extism.HostFunction { @@ -50,11 +84,12 @@ func newWebSocketConnectHostFunction(service WebSocketService) extism.HostFuncti } // Call the service method - newconnectionid, err := service.Connect(ctx, req.Url, req.Headers, req.ConnectionID) - if err != nil { - websocketWriteError(p, stack, err) + newconnectionid, svcErr := service.Connect(ctx, req.Url, req.Headers, req.ConnectionID) + if svcErr != nil { + websocketWriteError(p, stack, svcErr) return } + // Write JSON response to plugin memory resp := WebSocketConnectResponse{ NewConnectionID: newconnectionid, @@ -70,31 +105,29 @@ func newWebSocketSendTextHostFunction(service WebSocketService) extism.HostFunct return extism.NewHostFunctionWithStack( "websocket_sendtext", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - connectionID, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + websocketWriteError(p, stack, err) return } - message, err := p.ReadString(stack[1]) - if err != nil { + var req WebSocketSendTextRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + websocketWriteError(p, stack, err) return } // Call the service method - err = service.SendText(ctx, connectionID, message) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.SendText(ctx, req.ConnectionID, req.Message); svcErr != nil { + websocketWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := WebSocketSendTextResponse{} + websocketWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -103,31 +136,29 @@ func newWebSocketSendBinaryHostFunction(service WebSocketService) extism.HostFun return extism.NewHostFunctionWithStack( "websocket_sendbinary", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - connectionID, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + websocketWriteError(p, stack, err) return } - data, err := p.ReadBytes(stack[1]) - if err != nil { + var req WebSocketSendBinaryRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + websocketWriteError(p, stack, err) return } // Call the service method - err = service.SendBinary(ctx, connectionID, data) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.SendBinary(ctx, req.ConnectionID, req.Data); svcErr != nil { + websocketWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := WebSocketSendBinaryResponse{} + websocketWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } @@ -136,32 +167,29 @@ func newWebSocketCloseConnectionHostFunction(service WebSocketService) extism.Ho return extism.NewHostFunctionWithStack( "websocket_closeconnection", func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { - // Read parameters from stack - connectionID, err := p.ReadString(stack[0]) + // Read JSON request from plugin memory + reqBytes, err := p.ReadBytes(stack[0]) if err != nil { + websocketWriteError(p, stack, err) return } - code := extism.DecodeI32(stack[1]) - reason, err := p.ReadString(stack[2]) - if err != nil { + var req WebSocketCloseConnectionRequest + if err := json.Unmarshal(reqBytes, &req); err != nil { + websocketWriteError(p, stack, err) return } // Call the service method - err = service.CloseConnection(ctx, connectionID, code, reason) - if err != nil { - // Write error string to plugin memory - if ptr, err := p.WriteString(err.Error()); err == nil { - stack[0] = ptr - } + if svcErr := service.CloseConnection(ctx, req.ConnectionID, req.Code, req.Reason); svcErr != nil { + websocketWriteError(p, stack, svcErr) return } - // Write empty string to indicate success - if ptr, err := p.WriteString(""); err == nil { - stack[0] = ptr - } + + // Write JSON response to plugin memory + resp := WebSocketCloseConnectionResponse{} + websocketWriteResponse(p, stack, resp) }, - []extism.ValueType{extism.ValueTypePTR, extism.ValueTypeI32, extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}, ) } diff --git a/plugins/testdata/fake-artwork/nd_host_artwork.go b/plugins/testdata/fake-artwork/nd_host_artwork.go index 0943c794c..65e5efb81 100644 --- a/plugins/testdata/fake-artwork/nd_host_artwork.go +++ b/plugins/testdata/fake-artwork/nd_host_artwork.go @@ -16,22 +16,28 @@ import ( // artwork_getartisturl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getartisturl -func artwork_getartisturl(uint64, int32) uint64 +func artwork_getartisturl(uint64) uint64 // artwork_getalbumurl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getalbumurl -func artwork_getalbumurl(uint64, int32) uint64 +func artwork_getalbumurl(uint64) uint64 // artwork_gettrackurl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_gettrackurl -func artwork_gettrackurl(uint64, int32) uint64 +func artwork_gettrackurl(uint64) uint64 // artwork_getplaylisturl is the host function provided by Navidrome. // //go:wasmimport extism:host/user artwork_getplaylisturl -func artwork_getplaylisturl(uint64, int32) uint64 +func artwork_getplaylisturl(uint64) uint64 + +// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl. +type ArtworkGetArtistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} // ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl. type ArtworkGetArtistUrlResponse struct { @@ -39,18 +45,36 @@ type ArtworkGetArtistUrlResponse struct { Error string `json:"error,omitempty"` } +// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl. +type ArtworkGetAlbumUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl. type ArtworkGetAlbumUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl. +type ArtworkGetTrackUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl. type ArtworkGetTrackUrlResponse struct { Url string `json:"url,omitempty"` Error string `json:"error,omitempty"` } +// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl. +type ArtworkGetPlaylistUrlRequest struct { + Id string `json:"id"` + Size int32 `json:"size"` +} + // ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl. type ArtworkGetPlaylistUrlResponse struct { Url string `json:"url,omitempty"` @@ -66,11 +90,20 @@ type ArtworkGetPlaylistUrlResponse struct { // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetArtistUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getartisturl(idMem.Offset(), size) + responsePtr := artwork_getartisturl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -94,11 +127,20 @@ func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, e // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetAlbumUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getalbumurl(idMem.Offset(), size) + responsePtr := artwork_getalbumurl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -122,11 +164,20 @@ func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, err // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetTrackUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_gettrackurl(idMem.Offset(), size) + responsePtr := artwork_gettrackurl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -150,11 +201,20 @@ func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, err // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) { - idMem := pdk.AllocateString(id) - defer idMem.Free() + // Marshal request to JSON + req := ArtworkGetPlaylistUrlRequest{ + Id: id, + Size: size, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := artwork_getplaylisturl(idMem.Offset(), size) + responsePtr := artwork_getplaylisturl(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/testdata/fake-scheduler/nd_host_scheduler.go b/plugins/testdata/fake-scheduler/nd_host_scheduler.go index 1aef70972..d341343c8 100644 --- a/plugins/testdata/fake-scheduler/nd_host_scheduler.go +++ b/plugins/testdata/fake-scheduler/nd_host_scheduler.go @@ -9,7 +9,6 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) @@ -17,30 +16,54 @@ import ( // scheduler_scheduleonetime is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_scheduleonetime -func scheduler_scheduleonetime(int32, uint64, uint64) uint64 +func scheduler_scheduleonetime(uint64) uint64 // scheduler_schedulerecurring is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_schedulerecurring -func scheduler_schedulerecurring(uint64, uint64, uint64) uint64 +func scheduler_schedulerecurring(uint64) uint64 // scheduler_cancelschedule is the host function provided by Navidrome. // //go:wasmimport extism:host/user scheduler_cancelschedule func scheduler_cancelschedule(uint64) uint64 +// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeRequest struct { + DelaySeconds int32 `json:"delaySeconds"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime. type SchedulerScheduleOneTimeResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringRequest struct { + CronExpression string `json:"cronExpression"` + Payload string `json:"payload"` + ScheduleID string `json:"scheduleID"` +} + // SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring. type SchedulerScheduleRecurringResponse struct { NewScheduleID string `json:"newScheduleID,omitempty"` Error string `json:"error,omitempty"` } +// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleRequest struct { + ScheduleID string `json:"scheduleID"` +} + +// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule. +type SchedulerCancelScheduleResponse struct { + Error string `json:"error,omitempty"` +} + // SchedulerScheduleOneTime calls the scheduler_scheduleonetime host function. // ScheduleOneTime schedules a one-time event to be triggered after the specified delay. // Plugins that use this function must also implement the SchedulerCallback capability @@ -52,13 +75,21 @@ type SchedulerScheduleRecurringResponse struct { // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) { - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() + // Marshal request to JSON + req := SchedulerScheduleOneTimeRequest{ + DelaySeconds: delaySeconds, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_scheduleonetime(delaySeconds, payloadMem.Offset(), scheduleIDMem.Offset()) + responsePtr := scheduler_scheduleonetime(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -84,15 +115,21 @@ func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID str // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (*SchedulerScheduleRecurringResponse, error) { - cronExpressionMem := pdk.AllocateString(cronExpression) - defer cronExpressionMem.Free() - payloadMem := pdk.AllocateString(payload) - defer payloadMem.Free() - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() + // Marshal request to JSON + req := SchedulerScheduleRecurringRequest{ + CronExpression: cronExpression, + Payload: payload, + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_schedulerecurring(cronExpressionMem.Offset(), payloadMem.Offset(), scheduleIDMem.Offset()) + responsePtr := scheduler_schedulerecurring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -114,20 +151,30 @@ func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleI // any future events. // // Returns an error if the schedule ID is not found or if cancellation fails. -func SchedulerCancelSchedule(scheduleID string) error { - scheduleIDMem := pdk.AllocateString(scheduleID) - defer scheduleIDMem.Free() +func SchedulerCancelSchedule(scheduleID string) (*SchedulerCancelScheduleResponse, error) { + // Marshal request to JSON + req := SchedulerCancelScheduleRequest{ + ScheduleID: scheduleID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := scheduler_cancelschedule(scheduleIDMem.Offset()) + responsePtr := scheduler_cancelschedule(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response SchedulerCancelScheduleResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } diff --git a/plugins/testdata/fake-subsonicapi-plugin/nd_host_subsonicapi.go b/plugins/testdata/fake-subsonicapi-plugin/nd_host_subsonicapi.go index 30846a496..ec69b718d 100644 --- a/plugins/testdata/fake-subsonicapi-plugin/nd_host_subsonicapi.go +++ b/plugins/testdata/fake-subsonicapi-plugin/nd_host_subsonicapi.go @@ -2,6 +2,8 @@ // // This file contains client wrappers for the SubsonicAPI host service. // It is intended for use in Navidrome plugins built with TinyGo. +// +//go:build wasip1 package main @@ -16,6 +18,11 @@ import ( //go:wasmimport extism:host/user subsonicapi_call func subsonicapi_call(uint64) uint64 +// SubsonicAPICallRequest is the request type for SubsonicAPI.Call. +type SubsonicAPICallRequest struct { + Uri string `json:"uri"` +} + // SubsonicAPICallResponse is the response type for SubsonicAPI.Call. type SubsonicAPICallResponse struct { ResponseJSON string `json:"responseJSON,omitempty"` @@ -28,11 +35,19 @@ type SubsonicAPICallResponse struct { // The uri parameter should be the Subsonic API path without the server prefix, // e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON. func SubsonicAPICall(uri string) (*SubsonicAPICallResponse, error) { - uriMem := pdk.AllocateString(uri) - defer uriMem.Free() + // Marshal request to JSON + req := SubsonicAPICallRequest{ + Uri: uri, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := subsonicapi_call(uriMem.Offset()) + responsePtr := subsonicapi_call(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) diff --git a/plugins/testdata/fake-websocket/main.go b/plugins/testdata/fake-websocket/main.go index 673ccfa76..7508b8ef2 100644 --- a/plugins/testdata/fake-websocket/main.go +++ b/plugins/testdata/fake-websocket/main.go @@ -4,7 +4,6 @@ package main import ( "encoding/json" - "errors" pdk "github.com/extism/go-pdk" ) @@ -82,20 +81,28 @@ func ndWebSocketOnTextMessage() int32 { switch input.Message { case "echo": - err := webSocketSendText(input.ConnectionID, "echo:"+input.Message) + resp, err := WebSocketSendText(input.ConnectionID, "echo:"+input.Message) if err != nil { errStr := err.Error() pdk.OutputJSON(OnTextMessageOutput{Error: &errStr}) return 0 } + if resp.Error != "" { + pdk.OutputJSON(OnTextMessageOutput{Error: &resp.Error}) + return 0 + } case "close": - err := webSocketCloseConnection(input.ConnectionID) + resp, err := WebSocketCloseConnection(input.ConnectionID, 1000, "closed by plugin") if err != nil { errStr := err.Error() pdk.OutputJSON(OnTextMessageOutput{Error: &errStr}) return 0 } + if resp.Error != "" { + pdk.OutputJSON(OnTextMessageOutput{Error: &resp.Error}) + return 0 + } case "fail": errStr := "intentional test failure" @@ -205,48 +212,4 @@ func storeReceivedMessage(msg string) { pdk.SetVar("_received_messages", []byte(msg)) } -// Host function declarations for WebSocket operations -// -//go:wasmimport extism:host/user websocket_sendtext -func websocket_sendtext(connectionID uint64, message uint64) uint64 - -//go:wasmimport extism:host/user websocket_closeconnection -func websocket_closeconnection(connectionID uint64, code int32, reason uint64) uint64 - -// webSocketSendText sends a text message to the specified connection. -func webSocketSendText(connectionID, message string) error { - connMem := pdk.AllocateString(connectionID) - defer connMem.Free() - msgMem := pdk.AllocateString(message) - defer msgMem.Free() - - responsePtr := websocket_sendtext(connMem.Offset(), msgMem.Offset()) - if responsePtr != 0 { - responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) - if errStr != "" { - return errors.New(errStr) - } - } - return nil -} - -// webSocketCloseConnection closes the specified WebSocket connection. -func webSocketCloseConnection(connectionID string) error { - connMem := pdk.AllocateString(connectionID) - defer connMem.Free() - reasonMem := pdk.AllocateString("closed by plugin") - defer reasonMem.Free() - - responsePtr := websocket_closeconnection(connMem.Offset(), 1000, reasonMem.Offset()) - if responsePtr != 0 { - responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) - if errStr != "" { - return errors.New(errStr) - } - } - return nil -} - func main() {} diff --git a/plugins/testdata/fake-websocket/nd_host_websocket.go b/plugins/testdata/fake-websocket/nd_host_websocket.go new file mode 100644 index 000000000..e6daea765 --- /dev/null +++ b/plugins/testdata/fake-websocket/nd_host_websocket.go @@ -0,0 +1,237 @@ +// Code generated by hostgen. DO NOT EDIT. +// +// This file contains client wrappers for the WebSocket host service. +// It is intended for use in Navidrome plugins built with TinyGo. +// +//go:build wasip1 + +package main + +import ( + "encoding/json" + + "github.com/extism/go-pdk" +) + +// websocket_connect is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_connect +func websocket_connect(uint64) uint64 + +// websocket_sendtext is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_sendtext +func websocket_sendtext(uint64) uint64 + +// websocket_sendbinary is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_sendbinary +func websocket_sendbinary(uint64) uint64 + +// websocket_closeconnection is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user websocket_closeconnection +func websocket_closeconnection(uint64) uint64 + +// WebSocketConnectRequest is the request type for WebSocket.Connect. +type WebSocketConnectRequest struct { + Url string `json:"url"` + Headers map[string]string `json:"headers"` + ConnectionID string `json:"connectionID"` +} + +// WebSocketConnectResponse is the response type for WebSocket.Connect. +type WebSocketConnectResponse struct { + NewConnectionID string `json:"newConnectionID,omitempty"` + Error string `json:"error,omitempty"` +} + +// WebSocketSendTextRequest is the request type for WebSocket.SendText. +type WebSocketSendTextRequest struct { + ConnectionID string `json:"connectionID"` + Message string `json:"message"` +} + +// WebSocketSendTextResponse is the response type for WebSocket.SendText. +type WebSocketSendTextResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary. +type WebSocketSendBinaryRequest struct { + ConnectionID string `json:"connectionID"` + Data []byte `json:"data"` +} + +// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary. +type WebSocketSendBinaryResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection. +type WebSocketCloseConnectionRequest struct { + ConnectionID string `json:"connectionID"` + Code int32 `json:"code"` + Reason string `json:"reason"` +} + +// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection. +type WebSocketCloseConnectionResponse struct { + Error string `json:"error,omitempty"` +} + +// WebSocketConnect calls the websocket_connect host function. +// Connect establishes a WebSocket connection to the specified URL. +// +// Plugins that use this function must also implement the WebSocketCallback capability +// to receive incoming messages and connection events. +// +// Parameters: +// - url: The WebSocket URL to connect to (ws:// or wss://) +// - headers: Optional HTTP headers to include in the handshake request +// - connectionID: Optional unique identifier for the connection. If empty, one will be generated +// +// Returns the connection ID that can be used to send messages or close the connection, +// or an error if the connection fails. +func WebSocketConnect(url string, headers map[string]string, connectionID string) (*WebSocketConnectResponse, error) { + // Marshal request to JSON + req := WebSocketConnectRequest{ + Url: url, + Headers: headers, + ConnectionID: connectionID, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_connect(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketConnectResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// WebSocketSendText calls the websocket_sendtext host function. +// SendText sends a text message over an established WebSocket connection. +// +// Parameters: +// - connectionID: The connection identifier returned by Connect +// - message: The text message to send +// +// Returns an error if the connection is not found or if sending fails. +func WebSocketSendText(connectionID string, message string) (*WebSocketSendTextResponse, error) { + // Marshal request to JSON + req := WebSocketSendTextRequest{ + ConnectionID: connectionID, + Message: message, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_sendtext(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketSendTextResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// WebSocketSendBinary calls the websocket_sendbinary host function. +// SendBinary sends binary data over an established WebSocket connection. +// +// Parameters: +// - connectionID: The connection identifier returned by Connect +// - data: The binary data to send +// +// Returns an error if the connection is not found or if sending fails. +func WebSocketSendBinary(connectionID string, data []byte) (*WebSocketSendBinaryResponse, error) { + // Marshal request to JSON + req := WebSocketSendBinaryRequest{ + ConnectionID: connectionID, + Data: data, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_sendbinary(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketSendBinaryResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} + +// WebSocketCloseConnection calls the websocket_closeconnection host function. +// CloseConnection gracefully closes a WebSocket connection. +// +// Parameters: +// - connectionID: The connection identifier returned by Connect +// - code: WebSocket close status code (e.g., 1000 for normal closure) +// - reason: Optional human-readable reason for closing +// +// Returns an error if the connection is not found or if closing fails. +func WebSocketCloseConnection(connectionID string, code int32, reason string) (*WebSocketCloseConnectionResponse, error) { + // Marshal request to JSON + req := WebSocketCloseConnectionRequest{ + ConnectionID: connectionID, + Code: code, + Reason: reason, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() + + // Call the host function + responsePtr := websocket_closeconnection(reqMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + responseBytes := responseMem.ReadBytes() + + // Parse the response + var response WebSocketCloseConnectionResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err + } + + return &response, nil +} diff --git a/plugins/testdata/fake_cache_plugin/main.go b/plugins/testdata/fake_cache_plugin/main.go index ab860a41b..5e14bc933 100644 --- a/plugins/testdata/fake_cache_plugin/main.go +++ b/plugins/testdata/fake_cache_plugin/main.go @@ -81,12 +81,16 @@ func ndTestCache() int32 { switch input.Operation { case "set_string": - err := CacheSetString(input.Key, input.StringVal, input.TTLSeconds) + resp, err := CacheSetString(input.Key, input.StringVal, input.TTLSeconds) if err != nil { errStr := err.Error() pdk.OutputJSON(TestCacheOutput{Error: &errStr}) return 0 } + if resp.Error != "" { + pdk.OutputJSON(TestCacheOutput{Error: &resp.Error}) + return 0 + } pdk.OutputJSON(TestCacheOutput{}) return 0 @@ -105,12 +109,16 @@ func ndTestCache() int32 { return 0 case "set_int": - err := CacheSetInt(input.Key, input.IntVal, input.TTLSeconds) + resp, err := CacheSetInt(input.Key, input.IntVal, input.TTLSeconds) if err != nil { errStr := err.Error() pdk.OutputJSON(TestCacheOutput{Error: &errStr}) return 0 } + if resp.Error != "" { + pdk.OutputJSON(TestCacheOutput{Error: &resp.Error}) + return 0 + } pdk.OutputJSON(TestCacheOutput{}) return 0 @@ -129,12 +137,16 @@ func ndTestCache() int32 { return 0 case "set_float": - err := CacheSetFloat(input.Key, input.FloatVal, input.TTLSeconds) + resp, err := CacheSetFloat(input.Key, input.FloatVal, input.TTLSeconds) if err != nil { errStr := err.Error() pdk.OutputJSON(TestCacheOutput{Error: &errStr}) return 0 } + if resp.Error != "" { + pdk.OutputJSON(TestCacheOutput{Error: &resp.Error}) + return 0 + } pdk.OutputJSON(TestCacheOutput{}) return 0 @@ -153,12 +165,16 @@ func ndTestCache() int32 { return 0 case "set_bytes": - err := CacheSetBytes(input.Key, input.BytesVal, input.TTLSeconds) + resp, err := CacheSetBytes(input.Key, input.BytesVal, input.TTLSeconds) if err != nil { errStr := err.Error() pdk.OutputJSON(TestCacheOutput{Error: &errStr}) return 0 } + if resp.Error != "" { + pdk.OutputJSON(TestCacheOutput{Error: &resp.Error}) + return 0 + } pdk.OutputJSON(TestCacheOutput{}) return 0 @@ -191,12 +207,16 @@ func ndTestCache() int32 { return 0 case "remove": - err := CacheRemove(input.Key) + resp, err := CacheRemove(input.Key) if err != nil { errStr := err.Error() pdk.OutputJSON(TestCacheOutput{Error: &errStr}) return 0 } + if resp.Error != "" { + pdk.OutputJSON(TestCacheOutput{Error: &resp.Error}) + return 0 + } pdk.OutputJSON(TestCacheOutput{}) return 0 diff --git a/plugins/testdata/fake_cache_plugin/nd_host_cache.go b/plugins/testdata/fake_cache_plugin/nd_host_cache.go index c9798f2b2..36be869ae 100644 --- a/plugins/testdata/fake_cache_plugin/nd_host_cache.go +++ b/plugins/testdata/fake_cache_plugin/nd_host_cache.go @@ -9,7 +9,6 @@ package main import ( "encoding/json" - "errors" "github.com/extism/go-pdk" ) @@ -17,7 +16,7 @@ import ( // cache_setstring is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setstring -func cache_setstring(uint64, uint64, int64) uint64 +func cache_setstring(uint64) uint64 // cache_getstring is the host function provided by Navidrome. // @@ -27,7 +26,7 @@ func cache_getstring(uint64) uint64 // cache_setint is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setint -func cache_setint(uint64, int64, int64) uint64 +func cache_setint(uint64) uint64 // cache_getint is the host function provided by Navidrome. // @@ -37,7 +36,7 @@ func cache_getint(uint64) uint64 // cache_setfloat is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setfloat -func cache_setfloat(uint64, float64, int64) uint64 +func cache_setfloat(uint64) uint64 // cache_getfloat is the host function provided by Navidrome. // @@ -47,7 +46,7 @@ func cache_getfloat(uint64) uint64 // cache_setbytes is the host function provided by Navidrome. // //go:wasmimport extism:host/user cache_setbytes -func cache_setbytes(uint64, uint64, int64) uint64 +func cache_setbytes(uint64) uint64 // cache_getbytes is the host function provided by Navidrome. // @@ -64,6 +63,23 @@ func cache_has(uint64) uint64 //go:wasmimport extism:host/user cache_remove func cache_remove(uint64) uint64 +// CacheSetStringRequest is the request type for Cache.SetString. +type CacheSetStringRequest struct { + Key string `json:"key"` + Value string `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetStringResponse is the response type for Cache.SetString. +type CacheSetStringResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetStringRequest is the request type for Cache.GetString. +type CacheGetStringRequest struct { + Key string `json:"key"` +} + // CacheGetStringResponse is the response type for Cache.GetString. type CacheGetStringResponse struct { Value string `json:"value,omitempty"` @@ -71,6 +87,23 @@ type CacheGetStringResponse struct { Error string `json:"error,omitempty"` } +// CacheSetIntRequest is the request type for Cache.SetInt. +type CacheSetIntRequest struct { + Key string `json:"key"` + Value int64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetIntResponse is the response type for Cache.SetInt. +type CacheSetIntResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetIntRequest is the request type for Cache.GetInt. +type CacheGetIntRequest struct { + Key string `json:"key"` +} + // CacheGetIntResponse is the response type for Cache.GetInt. type CacheGetIntResponse struct { Value int64 `json:"value,omitempty"` @@ -78,6 +111,23 @@ type CacheGetIntResponse struct { Error string `json:"error,omitempty"` } +// CacheSetFloatRequest is the request type for Cache.SetFloat. +type CacheSetFloatRequest struct { + Key string `json:"key"` + Value float64 `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetFloatResponse is the response type for Cache.SetFloat. +type CacheSetFloatResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetFloatRequest is the request type for Cache.GetFloat. +type CacheGetFloatRequest struct { + Key string `json:"key"` +} + // CacheGetFloatResponse is the response type for Cache.GetFloat. type CacheGetFloatResponse struct { Value float64 `json:"value,omitempty"` @@ -85,6 +135,23 @@ type CacheGetFloatResponse struct { Error string `json:"error,omitempty"` } +// CacheSetBytesRequest is the request type for Cache.SetBytes. +type CacheSetBytesRequest struct { + Key string `json:"key"` + Value []byte `json:"value"` + TtlSeconds int64 `json:"ttlSeconds"` +} + +// CacheSetBytesResponse is the response type for Cache.SetBytes. +type CacheSetBytesResponse struct { + Error string `json:"error,omitempty"` +} + +// CacheGetBytesRequest is the request type for Cache.GetBytes. +type CacheGetBytesRequest struct { + Key string `json:"key"` +} + // CacheGetBytesResponse is the response type for Cache.GetBytes. type CacheGetBytesResponse struct { Value []byte `json:"value,omitempty"` @@ -92,12 +159,27 @@ type CacheGetBytesResponse struct { Error string `json:"error,omitempty"` } +// CacheHasRequest is the request type for Cache.Has. +type CacheHasRequest struct { + Key string `json:"key"` +} + // CacheHasResponse is the response type for Cache.Has. type CacheHasResponse struct { Exists bool `json:"exists,omitempty"` Error string `json:"error,omitempty"` } +// CacheRemoveRequest is the request type for Cache.Remove. +type CacheRemoveRequest struct { + Key string `json:"key"` +} + +// CacheRemoveResponse is the response type for Cache.Remove. +type CacheRemoveResponse struct { + Error string `json:"error,omitempty"` +} + // CacheSetString calls the cache_setstring host function. // SetString stores a string value in the cache. // @@ -107,24 +189,34 @@ type CacheHasResponse struct { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetString(key string, value string, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() - valueMem := pdk.AllocateString(value) - defer valueMem.Free() +func CacheSetString(key string, value string, ttlSeconds int64) (*CacheSetStringResponse, error) { + // Marshal request to JSON + req := CacheSetStringRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setstring(keyMem.Offset(), valueMem.Offset(), ttlSeconds) + responsePtr := cache_setstring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetStringResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetString calls the cache_getstring host function. @@ -136,11 +228,19 @@ func CacheSetString(key string, value string, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a string, exists will be false. func CacheGetString(key string) (*CacheGetStringResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetStringRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getstring(keyMem.Offset()) + responsePtr := cache_getstring(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -164,22 +264,34 @@ func CacheGetString(key string) (*CacheGetStringResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetInt(key string, value int64, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheSetInt(key string, value int64, ttlSeconds int64) (*CacheSetIntResponse, error) { + // Marshal request to JSON + req := CacheSetIntRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setint(keyMem.Offset(), value, ttlSeconds) + responsePtr := cache_setint(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetIntResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetInt calls the cache_getint host function. @@ -191,11 +303,19 @@ func CacheSetInt(key string, value int64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not an integer, exists will be false. func CacheGetInt(key string) (*CacheGetIntResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetIntRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getint(keyMem.Offset()) + responsePtr := cache_getint(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -219,22 +339,34 @@ func CacheGetInt(key string) (*CacheGetIntResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetFloat(key string, value float64, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheSetFloat(key string, value float64, ttlSeconds int64) (*CacheSetFloatResponse, error) { + // Marshal request to JSON + req := CacheSetFloatRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setfloat(keyMem.Offset(), value, ttlSeconds) + responsePtr := cache_setfloat(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetFloatResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetFloat calls the cache_getfloat host function. @@ -246,11 +378,19 @@ func CacheSetFloat(key string, value float64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a float, exists will be false. func CacheGetFloat(key string) (*CacheGetFloatResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetFloatRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getfloat(keyMem.Offset()) + responsePtr := cache_getfloat(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -274,24 +414,34 @@ func CacheGetFloat(key string) (*CacheGetFloatResponse, error) { // - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours) // // Returns an error if the operation fails. -func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() - valueMem := pdk.AllocateBytes(value) - defer valueMem.Free() +func CacheSetBytes(key string, value []byte, ttlSeconds int64) (*CacheSetBytesResponse, error) { + // Marshal request to JSON + req := CacheSetBytesRequest{ + Key: key, + Value: value, + TtlSeconds: ttlSeconds, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_setbytes(keyMem.Offset(), valueMem.Offset(), ttlSeconds) + responsePtr := cache_setbytes(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheSetBytesResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil } // CacheGetBytes calls the cache_getbytes host function. @@ -303,11 +453,19 @@ func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a byte slice, exists will be false. func CacheGetBytes(key string) (*CacheGetBytesResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheGetBytesRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_getbytes(keyMem.Offset()) + responsePtr := cache_getbytes(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -330,11 +488,19 @@ func CacheGetBytes(key string) (*CacheGetBytesResponse, error) { // // Returns true if the key exists and has not expired. func CacheHas(key string) (*CacheHasResponse, error) { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() + // Marshal request to JSON + req := CacheHasRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_has(keyMem.Offset()) + responsePtr := cache_has(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) @@ -356,20 +522,30 @@ func CacheHas(key string) (*CacheHasResponse, error) { // - key: The cache key (will be namespaced with plugin ID) // // Returns an error if the operation fails. Does not return an error if the key doesn't exist. -func CacheRemove(key string) error { - keyMem := pdk.AllocateString(key) - defer keyMem.Free() +func CacheRemove(key string) (*CacheRemoveResponse, error) { + // Marshal request to JSON + req := CacheRemoveRequest{ + Key: key, + } + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + reqMem := pdk.AllocateBytes(reqBytes) + defer reqMem.Free() // Call the host function - responsePtr := cache_remove(keyMem.Offset()) + responsePtr := cache_remove(reqMem.Offset()) // Read the response from memory responseMem := pdk.FindMemory(responsePtr) - errStr := string(responseMem.ReadBytes()) + responseBytes := responseMem.ReadBytes() - if errStr != "" { - return errors.New(errStr) + // Parse the response + var response CacheRemoveResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return nil, err } - return nil + return &response, nil }