Skip to content

Commit cc3f9f9

Browse files
authored
Merge pull request #1845 from adamreese/feat/sdk-go-kv-v2
feat(sdk/go): Refactor the key/value Go SDK to be more idiomatic
2 parents 7396a78 + aca5ef3 commit cc3f9f9

File tree

6 files changed

+147
-139
lines changed

6 files changed

+147
-139
lines changed

examples/tinygo-key-value/main.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ import (
55
"net/http"
66

77
spin_http "github.com/fermyon/spin/sdk/go/http"
8-
"github.com/fermyon/spin/sdk/go/key_value"
8+
"github.com/fermyon/spin/sdk/go/kv"
99
)
1010

1111
func init() {
1212
// handler for the http trigger
1313
spin_http.Handle(func(w http.ResponseWriter, r *http.Request) {
14-
store, err := key_value.Open("default")
14+
store, err := kv.OpenStore("default")
1515
if err != nil {
1616
http.Error(w, err.Error(), http.StatusInternalServerError)
1717
return
1818
}
19-
defer key_value.Close(store)
19+
defer store.Close()
2020

2121
body, err := io.ReadAll(r.Body)
2222
if err != nil {
@@ -26,15 +26,15 @@ func init() {
2626

2727
switch r.Method {
2828
case http.MethodPost:
29-
err := key_value.Set(store, r.URL.Path, body)
29+
err := store.Set(r.URL.Path, body)
3030
if err != nil {
3131
http.Error(w, err.Error(), http.StatusInternalServerError)
3232
return
3333
}
3434

3535
w.WriteHeader(http.StatusOK)
3636
case http.MethodGet:
37-
value, err := key_value.Get(store, r.URL.Path)
37+
value, err := store.Get(r.URL.Path)
3838
if err != nil {
3939
http.Error(w, err.Error(), http.StatusInternalServerError)
4040
return
@@ -43,15 +43,14 @@ func init() {
4343
w.WriteHeader(http.StatusOK)
4444
w.Write(value)
4545
case http.MethodDelete:
46-
err := key_value.Delete(store, r.URL.Path)
47-
if err != nil {
46+
if err := store.Delete(r.URL.Path); err != nil {
4847
http.Error(w, err.Error(), http.StatusInternalServerError)
4948
return
5049
}
5150

5251
w.WriteHeader(http.StatusOK)
5352
case http.MethodHead:
54-
exists, err := key_value.Exists(store, r.URL.Path)
53+
exists, err := store.Exists(r.URL.Path)
5554
if err != nil {
5655
http.Error(w, err.Error(), http.StatusInternalServerError)
5756
return

sdk/go/Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ GENERATED_OUTBOUND_HTTP = http/wasi-outbound-http.c http/wasi-outbound-http.h
4444
GENERATED_SPIN_HTTP = http/spin-http.c http/spin-http.h
4545
GENERATED_OUTBOUND_REDIS = redis/outbound-redis.c redis/outbound-redis.h
4646
GENERATED_SPIN_REDIS = redis/spin-redis.c redis/spin-redis.h
47-
GENERATED_KEY_VALUE = key_value/key-value.c key_value/key-value.h
47+
GENERATED_KEY_VALUE = kv/key-value.c kv/key-value.h
4848
GENERATED_SQLITE = sqlite/sqlite.c sqlite/sqlite.h
4949
GENERATED_LLM = llm/llm.c llm/llm.h
5050
GENERATED_OUTBOUND_MYSQL = mysql/outbound-mysql.c mysql/outbound-mysql.h
@@ -54,7 +54,7 @@ SDK_VERSION_SOURCE_FILE = sdk_version/sdk-version-go-template.c
5454

5555
# NOTE: Please update this list if you add a new directory to the SDK:
5656
SDK_VERSION_DEST_FILES = config/sdk-version-go.c http/sdk-version-go.c \
57-
key_value/sdk-version-go.c redis/sdk-version-go.c \
57+
kv/sdk-version-go.c redis/sdk-version-go.c \
5858
sqlite/sdk-version-go.c llm/sdk-version-go.c
5959

6060
# NOTE: To generate the C bindings you need to install a forked version of wit-bindgen.
@@ -90,7 +90,7 @@ $(GENERATED_SPIN_REDIS):
9090
wit-bindgen c --export ../../wit/ephemeral/spin-redis.wit --out-dir ./redis
9191

9292
$(GENERATED_KEY_VALUE):
93-
wit-bindgen c --import ../../wit/ephemeral/key-value.wit --out-dir ./key_value
93+
wit-bindgen c --import ../../wit/ephemeral/key-value.wit --out-dir ./kv
9494

9595
$(GENERATED_SQLITE):
9696
wit-bindgen c --import ../../wit/ephemeral/sqlite.wit --out-dir ./sqlite

sdk/go/key_value/key-value.go

Lines changed: 0 additions & 128 deletions
This file was deleted.
File renamed without changes.
File renamed without changes.

sdk/go/kv/kv.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Package kv provides access to key value stores within Spin
2+
// components.
3+
package kv
4+
5+
// #include "key-value.h"
6+
import "C"
7+
import (
8+
"errors"
9+
"fmt"
10+
"unsafe"
11+
)
12+
13+
// Store is the Key/Value backend storage.
14+
type Store struct {
15+
name string
16+
active bool
17+
ptr C.key_value_store_t
18+
}
19+
20+
// OpenStore creates a new instance of Store and opens a connection.
21+
func OpenStore(name string) (*Store, error) {
22+
s := &Store{name: name}
23+
if err := s.open(); err != nil {
24+
return nil, err
25+
}
26+
return s, nil
27+
}
28+
29+
// Close terminates the connection to Store.
30+
func (s *Store) Close() {
31+
if s.active {
32+
C.key_value_close(C.uint32_t(s.ptr))
33+
}
34+
s.active = false
35+
}
36+
37+
// Get retrieves a value from Store.
38+
func (s *Store) Get(key string) ([]byte, error) {
39+
ckey := toCStr(key)
40+
var ret C.key_value_expected_list_u8_error_t
41+
C.key_value_get(C.uint32_t(s.ptr), &ckey, &ret)
42+
if ret.is_err {
43+
return nil, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val)))
44+
}
45+
list := (*C.key_value_list_u8_t)(unsafe.Pointer(&ret.val))
46+
return C.GoBytes(unsafe.Pointer(list.ptr), C.int(list.len)), nil
47+
}
48+
49+
// Delete removes a value from Store.
50+
func (s *Store) Delete(key string) error {
51+
ckey := toCStr(key)
52+
var ret C.key_value_expected_unit_error_t
53+
C.key_value_delete(C.uint32_t(s.ptr), &ckey, &ret)
54+
if ret.is_err {
55+
return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val)))
56+
}
57+
return nil
58+
}
59+
60+
// Set creates a new key/value in Store.
61+
func (s *Store) Set(key string, value []byte) error {
62+
ckey := toCStr(key)
63+
cbytes := toCBytes(value)
64+
var ret C.key_value_expected_unit_error_t
65+
C.key_value_set(C.uint32_t(s.ptr), &ckey, &cbytes, &ret)
66+
if ret.is_err {
67+
return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val)))
68+
}
69+
return nil
70+
}
71+
72+
// Exists checks if a key exists within Store.
73+
func (s *Store) Exists(key string) (bool, error) {
74+
ckey := toCStr(key)
75+
var ret C.key_value_expected_bool_error_t
76+
C.key_value_exists(C.uint32_t(s.ptr), &ckey, &ret)
77+
if ret.is_err {
78+
return false, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val)))
79+
}
80+
return *(*bool)(unsafe.Pointer(&ret.val)), nil
81+
}
82+
83+
func (s *Store) open() error {
84+
if s.active {
85+
return nil
86+
}
87+
cname := toCStr(s.name)
88+
var ret C.key_value_expected_store_error_t
89+
C.key_value_open(&cname, &ret)
90+
if ret.is_err {
91+
return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val)))
92+
}
93+
s.ptr = *(*C.key_value_store_t)(unsafe.Pointer(&ret.val))
94+
s.active = true
95+
return nil
96+
}
97+
98+
func toCBytes(x []byte) C.key_value_list_u8_t {
99+
return C.key_value_list_u8_t{ptr: (*C.uint8_t)(unsafe.Pointer(&x[0])), len: C.size_t(len(x))}
100+
}
101+
102+
func toCStr(x string) C.key_value_string_t {
103+
return C.key_value_string_t{ptr: C.CString(x), len: C.size_t(len(x))}
104+
}
105+
106+
func fromCStrList(list *C.key_value_list_string_t) []string {
107+
var result []string
108+
109+
listLen := int(list.len)
110+
slice := unsafe.Slice(list.ptr, listLen)
111+
for i := 0; i < listLen; i++ {
112+
str := slice[i]
113+
result = append(result, C.GoStringN(str.ptr, C.int(str.len)))
114+
}
115+
116+
return result
117+
}
118+
119+
func toErr(err *C.key_value_error_t) error {
120+
switch err.tag {
121+
case 0:
122+
return errors.New("store table full")
123+
case 1:
124+
return errors.New("no such store")
125+
case 2:
126+
return errors.New("access denied")
127+
case 3:
128+
return errors.New("invalid store")
129+
case 4:
130+
return errors.New("no such key")
131+
case 5:
132+
str := (*C.key_value_string_t)(unsafe.Pointer(&err.val))
133+
return fmt.Errorf("io error: %s", C.GoStringN(str.ptr, C.int(str.len)))
134+
default:
135+
return fmt.Errorf("unrecognized error: %v", err.tag)
136+
}
137+
}

0 commit comments

Comments
 (0)