From 535b0fd4a4849a3e3976d96314737c39ff206e23 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Fri, 20 Oct 2023 12:10:16 -0700 Subject: [PATCH] feat(sdk/go): Refactor the key/value Go SDK to be more idiomatic - Introduces a client struct responsible for handling the connection - Remove exposed C types Signed-off-by: Adam Reese --- examples/tinygo-key-value/main.go | 19 ++-- sdk/go/Makefile | 6 +- sdk/go/key_value/key-value.go | 128 ------------------------- sdk/go/{key_value => kv}/key-value.c | 0 sdk/go/{key_value => kv}/key-value.h | 0 sdk/go/kv/kv.go | 135 +++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 143 deletions(-) delete mode 100644 sdk/go/key_value/key-value.go rename sdk/go/{key_value => kv}/key-value.c (100%) rename sdk/go/{key_value => kv}/key-value.h (100%) create mode 100644 sdk/go/kv/kv.go diff --git a/examples/tinygo-key-value/main.go b/examples/tinygo-key-value/main.go index 3b4cc7641f..885cf0379d 100644 --- a/examples/tinygo-key-value/main.go +++ b/examples/tinygo-key-value/main.go @@ -5,18 +5,14 @@ import ( "net/http" spin_http "github.com/fermyon/spin/sdk/go/http" - "github.com/fermyon/spin/sdk/go/key_value" + "github.com/fermyon/spin/sdk/go/kv" ) func init() { // handler for the http trigger spin_http.Handle(func(w http.ResponseWriter, r *http.Request) { - store, err := key_value.Open("default") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer key_value.Close(store) + store := kv.OpenStore("default") + defer store.Close() body, err := io.ReadAll(r.Body) if err != nil { @@ -26,7 +22,7 @@ func init() { switch r.Method { case http.MethodPost: - err := key_value.Set(store, r.URL.Path, body) + err := store.Set(r.URL.Path, body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -34,7 +30,7 @@ func init() { w.WriteHeader(http.StatusOK) case http.MethodGet: - value, err := key_value.Get(store, r.URL.Path) + value, err := store.Get(r.URL.Path) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -43,15 +39,14 @@ func init() { w.WriteHeader(http.StatusOK) w.Write(value) case http.MethodDelete: - err := key_value.Delete(store, r.URL.Path) - if err != nil { + if err := store.Delete(r.URL.Path); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) case http.MethodHead: - exists, err := key_value.Exists(store, r.URL.Path) + exists, err := store.Exists(r.URL.Path) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/sdk/go/Makefile b/sdk/go/Makefile index 7187e67d0d..0104f69093 100644 --- a/sdk/go/Makefile +++ b/sdk/go/Makefile @@ -41,7 +41,7 @@ GENERATED_OUTBOUND_HTTP = http/wasi-outbound-http.c http/wasi-outbound-http.h GENERATED_SPIN_HTTP = http/spin-http.c http/spin-http.h GENERATED_OUTBOUND_REDIS = redis/outbound-redis.c redis/outbound-redis.h GENERATED_SPIN_REDIS = redis/spin-redis.c redis/spin-redis.h -GENERATED_KEY_VALUE = key_value/key-value.c key_value/key-value.h +GENERATED_KEY_VALUE = kv/key-value.c kv/key-value.h GENERATED_SQLITE = sqlite/sqlite.c sqlite/sqlite.h GENERATED_LLM = llm/llm.c llm/llm.h @@ -49,7 +49,7 @@ SDK_VERSION_SOURCE_FILE = sdk_version/sdk-version-go-template.c # NOTE: Please update this list if you add a new directory to the SDK: SDK_VERSION_DEST_FILES = config/sdk-version-go.c http/sdk-version-go.c \ - key_value/sdk-version-go.c redis/sdk-version-go.c \ + kv/sdk-version-go.c redis/sdk-version-go.c \ sqlite/sdk-version-go.c llm/sdk-version-go.c # NOTE: To generate the C bindings you need to install a forked version of wit-bindgen. @@ -84,7 +84,7 @@ $(GENERATED_SPIN_REDIS): wit-bindgen c --export ../../wit/ephemeral/spin-redis.wit --out-dir ./redis $(GENERATED_KEY_VALUE): - wit-bindgen c --import ../../wit/ephemeral/key-value.wit --out-dir ./key_value + wit-bindgen c --import ../../wit/ephemeral/key-value.wit --out-dir ./kv $(GENERATED_SQLITE): wit-bindgen c --import ../../wit/ephemeral/sqlite.wit --out-dir ./sqlite diff --git a/sdk/go/key_value/key-value.go b/sdk/go/key_value/key-value.go deleted file mode 100644 index b0e8485ea4..0000000000 --- a/sdk/go/key_value/key-value.go +++ /dev/null @@ -1,128 +0,0 @@ -// Package key_value provides access to key value stores within Spin -// components. -package key_value - -// #include "key-value.h" -import "C" -import ( - "errors" - "fmt" - "unsafe" -) - -type Store uint32 - -const ( - errorKindStoreTableFull = iota - errorKindNoSuchStore - errorKindAccessDenied - errorKindInvalidStore - errorKindNoSuchKey - errorKindIo -) - -func Open(name string) (Store, error) { - cname := toCStr(name) - var ret C.key_value_expected_store_error_t - C.key_value_open(&cname, &ret) - if ret.is_err { - return 0xFFFF_FFFF, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) - } - return *(*Store)(unsafe.Pointer(&ret.val)), nil -} - -func Get(store Store, key string) ([]byte, error) { - ckey := toCStr(key) - var ret C.key_value_expected_list_u8_error_t - C.key_value_get(C.uint32_t(store), &ckey, &ret) - if ret.is_err { - return []byte{}, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) - } - list := (*C.key_value_list_u8_t)(unsafe.Pointer(&ret.val)) - return C.GoBytes(unsafe.Pointer(list.ptr), C.int(list.len)), nil -} - -func Set(store Store, key string, value []byte) error { - ckey := toCStr(key) - cbytes := toCBytes(value) - var ret C.key_value_expected_unit_error_t - C.key_value_set(C.uint32_t(store), &ckey, &cbytes, &ret) - if ret.is_err { - return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) - } - return nil -} - -func Delete(store Store, key string) error { - ckey := toCStr(key) - var ret C.key_value_expected_unit_error_t - C.key_value_delete(C.uint32_t(store), &ckey, &ret) - if ret.is_err { - return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) - } - return nil -} - -func Exists(store Store, key string) (bool, error) { - ckey := toCStr(key) - var ret C.key_value_expected_bool_error_t - C.key_value_exists(C.uint32_t(store), &ckey, &ret) - if ret.is_err { - return false, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) - } - return *(*bool)(unsafe.Pointer(&ret.val)), nil -} - -func GetKeys(store Store) ([]string, error) { - var ret C.key_value_expected_list_string_error_t - C.key_value_get_keys(C.uint32_t(store), &ret) - if ret.is_err { - return []string{}, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) - } - return fromCStrList((*C.key_value_list_string_t)(unsafe.Pointer(&ret.val))), nil -} - -func Close(store Store) { - C.key_value_close(C.uint32_t(store)) -} - -func toCBytes(x []byte) C.key_value_list_u8_t { - return C.key_value_list_u8_t{ptr: (*C.uint8_t)(unsafe.Pointer(&x[0])), len: C.size_t(len(x))} -} - -func toCStr(x string) C.key_value_string_t { - return C.key_value_string_t{ptr: C.CString(x), len: C.size_t(len(x))} -} - -func fromCStrList(list *C.key_value_list_string_t) []string { - listLen := int(list.len) - var result []string - - slice := unsafe.Slice(list.ptr, listLen) - for i := 0; i < listLen; i++ { - str := slice[i] - result = append(result, C.GoStringN(str.ptr, C.int(str.len))) - } - - return result -} - -func toErr(error *C.key_value_error_t) error { - switch error.tag { - case errorKindStoreTableFull: - return errors.New("store table full") - case errorKindNoSuchStore: - return errors.New("no such store") - case errorKindAccessDenied: - return errors.New("access denied") - case errorKindInvalidStore: - return errors.New("invalid store") - case errorKindNoSuchKey: - return errors.New("no such key") - case errorKindIo: - str := (*C.key_value_string_t)(unsafe.Pointer(&error.val)) - return errors.New(fmt.Sprintf("io error: %s", C.GoStringN(str.ptr, C.int(str.len)))) - default: - return errors.New(fmt.Sprintf("unrecognized error: %v", error.tag)) - } -} diff --git a/sdk/go/key_value/key-value.c b/sdk/go/kv/key-value.c similarity index 100% rename from sdk/go/key_value/key-value.c rename to sdk/go/kv/key-value.c diff --git a/sdk/go/key_value/key-value.h b/sdk/go/kv/key-value.h similarity index 100% rename from sdk/go/key_value/key-value.h rename to sdk/go/kv/key-value.h diff --git a/sdk/go/kv/kv.go b/sdk/go/kv/kv.go new file mode 100644 index 0000000000..3fa2747f0f --- /dev/null +++ b/sdk/go/kv/kv.go @@ -0,0 +1,135 @@ +// Package kv provides access to key value stores within Spin +// components. +package kv + +// #include "key-value.h" +import "C" +import ( + "errors" + "fmt" + "unsafe" +) + +// Store is the Key/Value backend storage. +type Store struct { + name string + active bool + ptr C.key_value_store_t +} + +// OpenStore creates a new instance of Store and opens a connection. +func OpenStore(name string) *Store { + s := &Store{name: name} + s.open() + return s +} + +// Close terminates the connection to Store. +func (s *Store) Close() { + if s.active { + C.key_value_close(C.uint32_t(s.ptr)) + } + s.active = false +} + +// Get retrieves a value from Store. +func (s *Store) Get(key string) ([]byte, error) { + ckey := toCStr(key) + var ret C.key_value_expected_list_u8_error_t + C.key_value_get(C.uint32_t(s.ptr), &ckey, &ret) + if ret.is_err { + return nil, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) + } + list := (*C.key_value_list_u8_t)(unsafe.Pointer(&ret.val)) + return C.GoBytes(unsafe.Pointer(list.ptr), C.int(list.len)), nil +} + +// Delete removes a value from Store. +func (s *Store) Delete(key string) error { + ckey := toCStr(key) + var ret C.key_value_expected_unit_error_t + C.key_value_delete(C.uint32_t(s.ptr), &ckey, &ret) + if ret.is_err { + return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) + } + return nil +} + +// Set creates a new key/value in Store. +func (s *Store) Set(key string, value []byte) error { + ckey := toCStr(key) + cbytes := toCBytes(value) + var ret C.key_value_expected_unit_error_t + C.key_value_set(C.uint32_t(s.ptr), &ckey, &cbytes, &ret) + if ret.is_err { + return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) + } + return nil +} + +// Exists checks if a key exists within Store. +func (s *Store) Exists(key string) (bool, error) { + ckey := toCStr(key) + var ret C.key_value_expected_bool_error_t + C.key_value_exists(C.uint32_t(s.ptr), &ckey, &ret) + if ret.is_err { + return false, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) + } + return *(*bool)(unsafe.Pointer(&ret.val)), nil +} + +func (s *Store) open() error { + if s.active { + return nil + } + cname := toCStr(s.name) + var ret C.key_value_expected_store_error_t + C.key_value_open(&cname, &ret) + if ret.is_err { + return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) + } + s.ptr = *(*C.key_value_store_t)(unsafe.Pointer(&ret.val)) + s.active = true + return nil +} + +func toCBytes(x []byte) C.key_value_list_u8_t { + return C.key_value_list_u8_t{ptr: (*C.uint8_t)(unsafe.Pointer(&x[0])), len: C.size_t(len(x))} +} + +func toCStr(x string) C.key_value_string_t { + return C.key_value_string_t{ptr: C.CString(x), len: C.size_t(len(x))} +} + +func fromCStrList(list *C.key_value_list_string_t) []string { + var result []string + + listLen := int(list.len) + slice := unsafe.Slice(list.ptr, listLen) + for i := 0; i < listLen; i++ { + str := slice[i] + result = append(result, C.GoStringN(str.ptr, C.int(str.len))) + } + + return result +} + +func toErr(err *C.key_value_error_t) error { + switch err.tag { + case 0: + return errors.New("store table full") + case 1: + return errors.New("no such store") + case 2: + return errors.New("access denied") + case 3: + return errors.New("invalid store") + case 4: + return errors.New("no such key") + case 5: + str := (*C.key_value_string_t)(unsafe.Pointer(&err.val)) + return fmt.Errorf("io error: %s", C.GoStringN(str.ptr, C.int(str.len))) + default: + return fmt.Errorf("unrecognized error: %v", err.tag) + } +}