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.
This commit is contained in:
Deluan 2025-12-26 13:12:55 -05:00
parent b9fceac12c
commit cab656dbe5
52 changed files with 3101 additions and 1612 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {}

View File

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

View File

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

View File

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