fix(plugins): add base64 handling for []byte and remove raw=true (#5121)
* fix(plugins): add base64 handling for []byte and remove raw=true Go's json.Marshal automatically base64-encodes []byte fields, but Rust's serde_json serializes Vec<u8> as a JSON array and Python's json.dumps raises TypeError on bytes. This fixes both directions of plugin communication by adding proper base64 encoding/decoding in generated client code. For Rust templates (client and capability): adds a base64_bytes serde helper module with #[serde(with = "base64_bytes")] on all Vec<u8> fields, and adds base64 as a dependency. For Python templates: wraps bytes params with base64.b64encode() and responses with base64.b64decode(). Also removes the raw=true binary framing protocol from all templates, the parser, and the Method type. The raw mechanism added complexity that is no longer needed once []byte works properly over JSON. * fix(plugins): update production code and tests for base64 migration Remove raw=true annotation from SubsonicAPI.CallRaw, delete all raw test fixtures, remove raw-related test cases from parser, generator, and integration tests, and add new test cases validating base64 handling for Rust and Python templates. * fix(plugins): update golden files and regenerate production code Update golden test fixtures for codec and comprehensive services to include base64 handling for []byte fields. Regenerate all production PDK code (Go, Rust, Python) and host wrappers to use standard JSON with base64-encoded byte fields instead of binary framing protocol. * refactor: remove base64 helper duplication from rust template Signed-off-by: Deluan <deluan@navidrome.org> * fix(plugins): add base64 dependency to capabilities' Cargo.toml Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -4,7 +4,6 @@ package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
|
||||
extism "github.com/extism/go-sdk"
|
||||
@@ -26,6 +25,13 @@ type SubsonicAPICallRawRequest struct {
|
||||
Uri string `json:"uri"`
|
||||
}
|
||||
|
||||
// SubsonicAPICallRawResponse is the response type for SubsonicAPI.CallRaw.
|
||||
type SubsonicAPICallRawResponse struct {
|
||||
ContentType string `json:"contentType,omitempty"`
|
||||
Data []byte `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// RegisterSubsonicAPIHostFunctions registers SubsonicAPI service host functions.
|
||||
// The returned host functions should be added to the plugin's configuration.
|
||||
func RegisterSubsonicAPIHostFunctions(service SubsonicAPIService) []extism.HostFunction {
|
||||
@@ -76,37 +82,28 @@ func newSubsonicAPICallRawHostFunction(service SubsonicAPIService) extism.HostFu
|
||||
// Read JSON request from plugin memory
|
||||
reqBytes, err := p.ReadBytes(stack[0])
|
||||
if err != nil {
|
||||
subsonicapiWriteRawError(p, stack, err)
|
||||
subsonicapiWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
var req SubsonicAPICallRawRequest
|
||||
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||
subsonicapiWriteRawError(p, stack, err)
|
||||
subsonicapiWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Call the service method
|
||||
contenttype, data, svcErr := service.CallRaw(ctx, req.Uri)
|
||||
if svcErr != nil {
|
||||
subsonicapiWriteRawError(p, stack, svcErr)
|
||||
subsonicapiWriteError(p, stack, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Write binary-framed response to plugin memory:
|
||||
// [0x00][4-byte content-type length (big-endian)][content-type string][raw data]
|
||||
ctBytes := []byte(contenttype)
|
||||
frame := make([]byte, 1+4+len(ctBytes)+len(data))
|
||||
frame[0] = 0x00 // success
|
||||
binary.BigEndian.PutUint32(frame[1:5], uint32(len(ctBytes)))
|
||||
copy(frame[5:5+len(ctBytes)], ctBytes)
|
||||
copy(frame[5+len(ctBytes):], data)
|
||||
|
||||
respPtr, err := p.WriteBytes(frame)
|
||||
if err != nil {
|
||||
stack[0] = 0
|
||||
return
|
||||
// Write JSON response to plugin memory
|
||||
resp := SubsonicAPICallRawResponse{
|
||||
ContentType: contenttype,
|
||||
Data: data,
|
||||
}
|
||||
stack[0] = respPtr
|
||||
subsonicapiWriteResponse(p, stack, resp)
|
||||
},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
@@ -137,14 +134,3 @@ func subsonicapiWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
|
||||
respPtr, _ := p.WriteBytes(respBytes)
|
||||
stack[0] = respPtr
|
||||
}
|
||||
|
||||
// subsonicapiWriteRawError writes a binary-framed error response to plugin memory.
|
||||
// Format: [0x01][UTF-8 error message]
|
||||
func subsonicapiWriteRawError(p *extism.CurrentPlugin, stack []uint64, err error) {
|
||||
errMsg := []byte(err.Error())
|
||||
frame := make([]byte, 1+len(errMsg))
|
||||
frame[0] = 0x01 // error
|
||||
copy(frame[1:], errMsg)
|
||||
respPtr, _ := p.WriteBytes(frame)
|
||||
stack[0] = respPtr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user