diff --git a/env.go b/env.go index c189dd5..cca8ab8 100644 --- a/env.go +++ b/env.go @@ -2,78 +2,144 @@ package pdk type extismPointer uint64 +// extismInputLength returns the number of bytes provided by the host via its input methods. +// //go:wasmimport extism:host/env input_length -func extism_input_length() uint64 +func extismInputLength() uint64 +// extismLength returns the number of bytes associated with the block of host memory +// located at `offset`. +// //go:wasmimport extism:host/env length -func extism_length(extismPointer) uint64 +func extismLength(offset extismPointer) uint64 //go:wasmimport extism:host/env length_unsafe -func extism_length_unsafe(extismPointer) uint64 +func extismLengthUnsafe(extismPointer) uint64 +// extismAlloc allocates `length` bytes of data with host memory for use by the plugin +// and returns its offset within the host memory block. +// //go:wasmimport extism:host/env alloc -func extism_alloc(uint64) extismPointer +func extismAlloc(length uint64) extismPointer +// extismFree releases the bytes previously allocated with `extism_alloc` at the given `offset`. +// //go:wasmimport extism:host/env free -func extism_free(extismPointer) +func extismFree(offset extismPointer) +// extismInputLoadU8 returns the byte at location `offset` of the "input" data from the host. +// //go:wasmimport extism:host/env input_load_u8 -func extism_input_load_u8_(extismPointer) uint32 - -func extism_input_load_u8(p extismPointer) uint8 { - return uint8(extism_input_load_u8_(p)) +func extismInputLoadU8_(offset extismPointer) uint32 +func extismInputLoadU8(offset extismPointer) uint8 { + return uint8(extismInputLoadU8_(offset)) } +// extismInputLoadU64 returns the 64-bit unsigned integer of the "input" data from the host. +// Note that `offset` must lie on an 8-byte boundary. +// //go:wasmimport extism:host/env input_load_u64 -func extism_input_load_u64(extismPointer) uint64 +func extismInputLoadU64(offset extismPointer) uint64 +// extismOutputSet sets the "output" data from the plugin to the host to be the memory that +// has been written at `offset` with the given `length`. +// The memory can be immediately freed because the host makes a copy for its use. +// //go:wasmimport extism:host/env output_set -func extism_output_set(extismPointer, uint64) +func extismOutputSet(offset extismPointer, length uint64) +// extismErrorSet sets the "error" data from the plugin to the host to be the memory that +// has been written at `offset`. +// The memory can be immediately freed because the host makes a copy for its use. +// //go:wasmimport extism:host/env error_set -func extism_error_set(extismPointer) +func extismErrorSet(offset extismPointer) +// extismConfigGet returns the host memory block offset for the "config" data associated with +// the key which is represented by the UTF-8 string which as been previously written at `offset`. +// The memory for the key can be immediately freed because the host has its own copy. +// //go:wasmimport extism:host/env config_get -func extism_config_get(extismPointer) extismPointer +func extismConfigGet(offset extismPointer) extismPointer +// extismVarGet returns the host memory block offset for the "var" data associated with +// the key which is represented by the UTF-8 string which as been previously written at `offset`. +// The memory for the key can be immediately freed because the host has its own copy. +// //go:wasmimport extism:host/env var_get -func extism_var_get(extismPointer) extismPointer - +func extismVarGet(offset extismPointer) extismPointer + +// extismVarSet sets the host "var" memory keyed by the UTF-8 string located at `offset` +// to be the value which has been previously written at `valueOffset`. +// +// A `valueOffset` of 0 causes the old value associated with this key to be freed on the host +// and the association to be completely removed. +// +// The memory for the key can be immediately freed because the host has its own copy. +// The memory for the value, however, should not be freed, as that erases the value from the host. +// //go:wasmimport extism:host/env var_set -func extism_var_set(extismPointer, extismPointer) +func extismVarSet(offset, valueOffset extismPointer) +// extismStoreU8 stores the byte `v` at location `offset` in the host memory block. +// //go:wasmimport extism:host/env store_u8 -func extism_store_u8_(extismPointer, uint32) -func extism_store_u8(p extismPointer, v uint8) { - extism_store_u8_(p, uint32(v)) +func extismStoreU8_(extismPointer, uint32) +func extismStoreU8(offset extismPointer, v uint8) { + extismStoreU8_(offset, uint32(v)) } +// extismLoadU8 returns the byte located at `offset` in the host memory block. +// //go:wasmimport extism:host/env load_u8 -func extism_load_u8_(extismPointer) uint32 -func extism_load_u8(p extismPointer) uint8 { - return uint8(extism_load_u8_(p)) +func extismLoadU8_(offset extismPointer) uint32 +func extismLoadU8(offset extismPointer) uint8 { + return uint8(extismLoadU8_(offset)) } +// extismStoreU64 stores the 64-bit unsigned integer value `v` at location `offset` in the host memory block. +// Note that `offset` must lie on an 8-byte boundary. +// //go:wasmimport extism:host/env store_u64 -func extism_store_u64(extismPointer, uint64) +func extismStoreU64(offset extismPointer, v uint64) +// extismLoadU64 returns the 64-bit unsigned integer at location `offset` in the host memory block. +// Note that `offset` must lie on an 8-byte boundary. +// //go:wasmimport extism:host/env load_u64 -func extism_load_u64(extismPointer) uint64 +func extismLoadU64(offset extismPointer) uint64 +// extismHTTPRequest sends the HTTP `request` to the Extism host with the provided `body` (0 means no body) +// and returns back the memory offset to the response body. +// //go:wasmimport extism:host/env http_request -func extism_http_request(extismPointer, extismPointer) extismPointer +func extismHTTPRequest(request, body extismPointer) extismPointer +// extismHTTPStatusCode returns the status code for the last-sent `extism_http_request` call. +// //go:wasmimport extism:host/env http_status_code -func extism_http_status_code() int32 +func extismHTTPStatusCode() int32 +// extismLogInfo logs an "info" string to the host from the previously-written UTF-8 string written to `offset`. +// Note that the memory at `offset` can be immediately freed because it is immediately logged. +// //go:wasmimport extism:host/env log_info -func extism_log_info(extismPointer) +func extismLogInfo(offset extismPointer) +// extismLogDebug logs a "debug" string to the host from the previously-written UTF-8 string written to `offset`. +// Note that the memory at `offset` can be immediately freed because it is immediately logged. +// //go:wasmimport extism:host/env log_debug -func extism_log_debug(extismPointer) +func extismLogDebug(offset extismPointer) +// extismLogWarn logs a "warning" string to the host from the previously-written UTF-8 string written to `offset`. +// Note that the memory at `offset` can be immediately freed because it is immediately logged. +// //go:wasmimport extism:host/env log_warn -func extism_log_warn(extismPointer) +func extismLogWarn(offset extismPointer) +// extismLogError logs an "error" string to the host from the previously-written UTF-8 string written to `offset`. +// Note that the memory at `offset` can be immediately freed because it is immediately logged. +// //go:wasmimport extism:host/env log_error -func extism_log_error(extismPointer) +func extismLogError(offset extismPointer) diff --git a/example/countvowels/std_main.go b/example/countvowels/std_main.go index 24c37c2..3f1f9f1 100644 --- a/example/countvowels/std_main.go +++ b/example/countvowels/std_main.go @@ -14,15 +14,17 @@ import ( // `_start` via WASI. So, `main` functions should contain the plugin behavior, that the host will // invoke by explicitly calling `_start`. func main() { - count_vowels() - count_vowels_typed() - count_vowels_json_output() + countVowels() + countVowelsTyped() + countVowelsJSONOutput() } +// CountVowelsInput represents the JSON input provided by the host. type CountVowelsInput struct { Input string `json:"input"` } +// CountVowelsOutput represents the JSON output sent to the host. type CountVowelsOuptut struct { Count int `json:"count"` Total int `json:"total"` @@ -30,7 +32,7 @@ type CountVowelsOuptut struct { } //export count_vowels_typed -func count_vowels_typed() int32 { +func countVowelsTyped() int32 { var input CountVowelsInput if err := pdk.InputJSON(&input); err != nil { pdk.SetError(err) @@ -42,7 +44,7 @@ func count_vowels_typed() int32 { } //export count_vowels_json_output -func count_vowels_json_output() int32 { +func countVowelsJSONOutput() int32 { output := CountVowelsOuptut{Count: 42, Total: 2.1e7, Vowels: "aAeEiIoOuUyY"} err := pdk.OutputJSON(output) if err != nil { @@ -52,7 +54,7 @@ func count_vowels_json_output() int32 { return 0 } -func count_vowels() int32 { +func countVowels() int32 { input := pdk.Input() count := 0 diff --git a/example/countvowels/tiny_main.go b/example/countvowels/tiny_main.go index fd45ba6..e931969 100644 --- a/example/countvowels/tiny_main.go +++ b/example/countvowels/tiny_main.go @@ -9,10 +9,12 @@ import ( "github.com/extism/go-pdk" ) +// CountVowelsInput represents the JSON input provided by the host. type CountVowelsInput struct { Input string `json:"input"` } +// CountVowelsOutput represents the JSON output sent to the host. type CountVowelsOuptut struct { Count int `json:"count"` Total int `json:"total"` @@ -20,7 +22,7 @@ type CountVowelsOuptut struct { } //export count_vowels_typed -func count_vowels_typed() int32 { +func countVowelsTyped() int32 { var input CountVowelsInput if err := pdk.InputJSON(&input); err != nil { pdk.SetError(err) @@ -32,7 +34,7 @@ func count_vowels_typed() int32 { } //export count_vowels_json_output -func count_vowels_json_output() int32 { +func countVowelsJSONOutput() int32 { output := CountVowelsOuptut{Count: 42, Total: 2.1e7, Vowels: "aAeEiIoOuUyY"} err := pdk.OutputJSON(output) if err != nil { @@ -43,7 +45,7 @@ func count_vowels_json_output() int32 { } //export count_vowels -func count_vowels() int32 { +func countVowels() int32 { input := pdk.Input() count := 0 diff --git a/example/reactor/tiny_main.go b/example/reactor/tiny_main.go index b4d5371..e84ff2a 100644 --- a/example/reactor/tiny_main.go +++ b/example/reactor/tiny_main.go @@ -11,7 +11,7 @@ import ( ) //export read_file -func read_file() { +func readFile() { name := pdk.InputString() content, err := os.ReadFile(name) diff --git a/extism_pdk.go b/extism_pdk.go index 3410791..fee867f 100644 --- a/extism_pdk.go +++ b/extism_pdk.go @@ -5,11 +5,13 @@ import ( "encoding/json" ) +// Memory represents memory allocated by (and shared with) the host. type Memory struct { offset extismPointer length uint64 } +// LogLevel represents a logging level. type LogLevel int const ( @@ -26,31 +28,31 @@ func load(offset extismPointer, buf []byte) { for chunkIdx := 0; chunkIdx < chunkCount; chunkIdx++ { i := chunkIdx << 3 - binary.LittleEndian.PutUint64(buf[i:i+8], extism_load_u64(offset+extismPointer(i))) + binary.LittleEndian.PutUint64(buf[i:i+8], extismLoadU64(offset+extismPointer(i))) } remainder := length & 7 remainderOffset := chunkCount << 3 for index := remainderOffset; index < (remainder + remainderOffset); index++ { - buf[index] = extism_load_u8(offset + extismPointer(index)) + buf[index] = extismLoadU8(offset + extismPointer(index)) } } func loadInput() []byte { - length := int(extism_input_length()) + length := int(extismInputLength()) buf := make([]byte, length) chunkCount := length >> 3 for chunkIdx := 0; chunkIdx < chunkCount; chunkIdx++ { i := chunkIdx << 3 - binary.LittleEndian.PutUint64(buf[i:i+8], extism_input_load_u64(extismPointer(i))) + binary.LittleEndian.PutUint64(buf[i:i+8], extismInputLoadU64(extismPointer(i))) } remainder := length & 7 remainderOffset := chunkCount << 3 for index := remainderOffset; index < (remainder + remainderOffset); index++ { - buf[index] = extism_input_load_u8(extismPointer(index)) + buf[index] = extismInputLoadU8(extismPointer(index)) } return buf @@ -63,42 +65,50 @@ func store(offset extismPointer, buf []byte) { for chunkIdx := 0; chunkIdx < chunkCount; chunkIdx++ { i := chunkIdx << 3 x := binary.LittleEndian.Uint64(buf[i : i+8]) - extism_store_u64(offset+extismPointer(i), x) + extismStoreU64(offset+extismPointer(i), x) } remainder := length & 7 remainderOffset := chunkCount << 3 for index := remainderOffset; index < (remainder + remainderOffset); index++ { - extism_store_u8(offset+extismPointer(index), buf[index]) + extismStoreU8(offset+extismPointer(index), buf[index]) } } +// Input returns a slice of bytes from the host. func Input() []byte { return loadInput() } +// JSONFrom unmarshals a `Memory` block located at `offset` from the host +// into the provided data `v`. func JSONFrom(offset uint64, v any) error { mem := FindMemory(offset) return json.Unmarshal(mem.ReadBytes(), v) } +// InputJSON returns unmartialed JSON data from the host "input". func InputJSON(v any) error { return json.Unmarshal(Input(), v) } +// OutputJSON marshals the provided data `v` as output to the host. func OutputJSON(v any) error { b, err := json.Marshal(v) if err != nil { return err } - OutputMemory(AllocateBytes(b)) + mem := AllocateBytes(b) + defer mem.Free() + OutputMemory(mem) return nil } +// Allocate allocates `length` uninitialized bytes on the host. func Allocate(length int) Memory { clength := uint64(length) - offset := extism_alloc(clength) + offset := extismAlloc(clength) return Memory{ offset: offset, @@ -106,9 +116,10 @@ func Allocate(length int) Memory { } } +// AllocateBytes allocates and saves the `data` into Memory on the host. func AllocateBytes(data []byte) Memory { clength := uint64(len(data)) - offset := extism_alloc(clength) + offset := extismAlloc(clength) store(offset, data) @@ -119,45 +130,56 @@ func AllocateBytes(data []byte) Memory { } +// AllocateString allocates and saves the UTF-8 string `data` into Memory on the host. func AllocateString(data string) Memory { return AllocateBytes([]byte(data)) } +// InputString returns the input data from the host as a UTF-8 string. func InputString() string { return string(Input()) } +// OutputMemory sends the `mem` Memory to the host output. +// Note that the `mem` is _NOT_ freed and is your responsibility to free when finished with it. func OutputMemory(mem Memory) { - extism_output_set(mem.offset, mem.length) + extismOutputSet(mem.offset, mem.length) } +// Output sends the `data` slice of bytes to the host output. func Output(data []byte) { clength := uint64(len(data)) - offset := extism_alloc(clength) + offset := extismAlloc(clength) store(offset, data) - extism_output_set(offset, clength) + extismOutputSet(offset, clength) + extismFree(offset) } +// OutputString sends the UTF-8 string `s` to the host output. func OutputString(s string) { Output([]byte(s)) } +// SetError sets the host error string from `err`. func SetError(err error) { SetErrorString(err.Error()) } +// SetErrorString sets the host error string from `err`. func SetErrorString(err string) { mem := AllocateString(err) - extism_error_set(mem.offset) + defer mem.Free() + extismErrorSet(mem.offset) } +// GetConfig returns the config string associated with `key` (if any). func GetConfig(key string) (string, bool) { mem := AllocateBytes([]byte(key)) defer mem.Free() - offset := extism_config_get(mem.offset) - clength := extism_length(offset) + offset := extismConfigGet(mem.offset) + clength := extismLength(offset) if offset == 0 || clength == 0 { return "", false } @@ -168,19 +190,21 @@ func GetConfig(key string) (string, bool) { return string(value), true } +// LogMemory logs the `memory` block on the host using the provided log `level`. func LogMemory(level LogLevel, memory Memory) { switch level { case LogInfo: - extism_log_info(memory.offset) + extismLogInfo(memory.offset) case LogDebug: - extism_log_debug(memory.offset) + extismLogDebug(memory.offset) case LogWarn: - extism_log_warn(memory.offset) + extismLogWarn(memory.offset) case LogError: - extism_log_error(memory.offset) + extismLogError(memory.offset) } } +// Log logs the provided UTF-8 string `s` on the host using the provided log `level`. func Log(level LogLevel, s string) { mem := AllocateString(s) defer mem.Free() @@ -188,11 +212,13 @@ func Log(level LogLevel, s string) { LogMemory(level, mem) } +// GetVar returns the byte slice (if any) associated with `key`. func GetVar(key string) []byte { mem := AllocateBytes([]byte(key)) + defer mem.Free() - offset := extism_var_get(mem.offset) - clength := extism_length(offset) + offset := extismVarGet(mem.offset) + clength := extismLength(offset) if offset == 0 || clength == 0 { return nil } @@ -203,6 +229,7 @@ func GetVar(key string) []byte { return value } +// SetVar sets the host variable associated with `key` to the `value` byte slice. func SetVar(key string, value []byte) { keyMem := AllocateBytes([]byte(key)) defer keyMem.Free() @@ -210,14 +237,16 @@ func SetVar(key string, value []byte) { valMem := AllocateBytes(value) defer valMem.Free() - extism_var_set(keyMem.offset, valMem.offset) + extismVarSet(keyMem.offset, valMem.offset) } +// GetVarInt returns the int associated with `key` (or 0 if none). func GetVarInt(key string) int { mem := AllocateBytes([]byte(key)) + defer mem.Free() - offset := extism_var_get(mem.offset) - clength := extism_length(offset) + offset := extismVarGet(mem.offset) + clength := extismLength(offset) if offset == 0 || clength == 0 { return 0 } @@ -228,6 +257,7 @@ func GetVarInt(key string) int { return int(binary.LittleEndian.Uint64(value)) } +// SetVarInt sets the host variable associated with `key` to the `value` int. func SetVarInt(key string, value int) { keyMem := AllocateBytes([]byte(key)) defer keyMem.Free() @@ -238,44 +268,57 @@ func SetVarInt(key string, value int) { valMem := AllocateBytes(bytes) defer valMem.Free() - extism_var_set(keyMem.offset, valMem.offset) + extismVarSet(keyMem.offset, valMem.offset) } +// RemoveVar removes (and frees) the host variable associated with `key`. func RemoveVar(key string) { mem := AllocateBytes([]byte(key)) - extism_var_set(mem.offset, 0) + defer mem.Free() + extismVarSet(mem.offset, 0) } +// HTTPRequestMeta represents the metadata associated with an HTTP request on the host. type HTTPRequestMeta struct { - Url string `json:"url"` + URL string `json:"url"` Method string `json:"method"` Headers map[string]string `json:"headers"` } +// HTTPRequest represents an HTTP request sent by the host. type HTTPRequest struct { meta HTTPRequestMeta body []byte } +// HTTPResponse represents an HTTP response returned from the host. type HTTPResponse struct { memory Memory status uint16 } +// Memory returns the memory associated with the `HTTPResponse`. func (r HTTPResponse) Memory() Memory { return r.memory } +// Body returns the body byte slice (if any) from the `HTTPResponse`. func (r HTTPResponse) Body() []byte { + if r.memory.length == 0 { + return nil + } + buf := make([]byte, r.memory.length) r.memory.Load(buf) return buf } +// Status returns the status code from the `HTTPResponse`. func (r HTTPResponse) Status() uint16 { return r.status } +// HTTPMethod represents an HTTP method. type HTTPMethod int32 const ( @@ -315,10 +358,11 @@ func (m HTTPMethod) String() string { } } +// NewHTTPRequest returns a new `HTTPRequest`. func NewHTTPRequest(method HTTPMethod, url string) *HTTPRequest { return &HTTPRequest{ meta: HTTPRequestMeta{ - Url: url, + URL: url, Headers: nil, Method: method.String(), }, @@ -326,6 +370,7 @@ func NewHTTPRequest(method HTTPMethod, url string) *HTTPRequest { } } +// SetHeader sets an HTTP header `key` to `value`. func (r *HTTPRequest) SetHeader(key string, value string) *HTTPRequest { if r.meta.Headers == nil { r.meta.Headers = make(map[string]string) @@ -334,22 +379,28 @@ func (r *HTTPRequest) SetHeader(key string, value string) *HTTPRequest { return r } +// SetBody sets an HTTP request body to the provided byte slice. func (r *HTTPRequest) SetBody(body []byte) *HTTPRequest { r.body = body return r } +// Send sends the `HTTPRequest` from the host and returns the `HTTPResponse`. func (r *HTTPRequest) Send() HTTPResponse { enc, _ := json.Marshal(r.meta) req := AllocateBytes(enc) defer req.Free() - data := AllocateBytes(r.body) - defer data.Free() + var dataOffset extismPointer + if len(r.body) > 0 { + data := AllocateBytes(r.body) + defer data.Free() + dataOffset = data.offset + } - offset := extism_http_request(req.offset, data.offset) - length := extism_length_unsafe(offset) - status := uint16(extism_http_status_code()) + offset := extismHTTPRequest(req.offset, dataOffset) + length := extismLengthUnsafe(offset) + status := uint16(extismHTTPStatusCode()) memory := Memory{offset, length} @@ -359,80 +410,87 @@ func (r *HTTPRequest) Send() HTTPResponse { } } +// ReadBytes returns the host memory block as a slice of bytes. func (m *Memory) ReadBytes() []byte { buff := make([]byte, m.length) m.Load(buff) return buff } +// Load copies the host memory block to the provided `buffer` byte slice. func (m *Memory) Load(buffer []byte) { load(m.offset, buffer) } +// Store copies the `data` byte slice into host memory. func (m *Memory) Store(data []byte) { store(m.offset, data) } +// Free frees the host memory block. func (m *Memory) Free() { - extism_free(m.offset) + extismFree(m.offset) } +// Length returns the number of bytes in the host memory block. func (m *Memory) Length() uint64 { return m.length } +// Offset returns the offset of the host memory block. func (m *Memory) Offset() uint64 { return uint64(m.offset) } +// FindMemory finds the host memory block at the given `offset`. func FindMemory(offset uint64) Memory { - length := extism_length(extismPointer(offset)) + length := extismLength(extismPointer(offset)) if length == 0 { return Memory{0, 0} } return Memory{extismPointer(offset), length} } -// Load bytes from Extism memory given an offset +// ParamBytes returns bytes from Extism host memory given an offset. func ParamBytes(offset uint64) []byte { mem := FindMemory(offset) return mem.ReadBytes() } -// Load string from Extism memory given an offset +// ParamString returns UTF-8 string data from Extism host memory given an offset. func ParamString(offset uint64) string { return string(ParamBytes(offset)) } -// Load uint32 from Extism memory given an offset +// ParamU32 returns a uint32 from Extism host memory given an offset. func ParamU32(offset uint64) uint32 { return binary.LittleEndian.Uint32(ParamBytes(offset)) } -// Load uint64 from Extism memory given an offset +// ParamU64 returns a uint64 from Extism host memory given an offset. func ParamU64(offset uint64) uint64 { return binary.LittleEndian.Uint64(ParamBytes(offset)) } -// Allocate bytes and return the offset in Extism memory +// ResultBytes allocates bytes and returns the offset in Extism host memory. func ResultBytes(d []byte) uint64 { mem := AllocateBytes(d) return mem.Offset() } -// Allocate a string and return the offset in Extism memory +// ResultString allocates a UTF-8 string and returns the offset in Extism host memory. func ResultString(s string) uint64 { mem := AllocateString(s) return mem.Offset() } -// Allocate a uint32 and return the offset in Extism memory +// ResultU32 allocates a uint32 and returns the offset in Extism host memory. func ResultU32(d uint32) uint64 { mem := AllocateBytes(binary.LittleEndian.AppendUint32([]byte{}, d)) return mem.Offset() } -// Allocate a uint64 and return the offset in Extism memory +// ResultU64 allocates a uint64 and returns the offset in Extism host memory. func ResultU64(d uint64) uint64 { mem := AllocateBytes(binary.LittleEndian.AppendUint64([]byte{}, d)) return mem.Offset() diff --git a/wasi-reactor/extism_pdk_reactor.go b/wasi-reactor/extism_pdk_reactor.go index 5a5bd4a..a7f5107 100644 --- a/wasi-reactor/extism_pdk_reactor.go +++ b/wasi-reactor/extism_pdk_reactor.go @@ -1,9 +1,9 @@ package reactor //export __wasm_call_ctors -func __wasm_call_ctors() +func wasmCallCtors() //export _initialize -func _initialize() { - __wasm_call_ctors() +func initialize() { + wasmCallCtors() }