refactor: use extracted protocol code

This commit is contained in:
Peter 2023-08-23 18:44:02 +02:00
parent d22bb0808e
commit 19c20245ba
Signed by: prskr
GPG key ID: C1DB5D2E8DB512F9
10 changed files with 76 additions and 1161 deletions

27
.editorconfig Normal file
View file

@ -0,0 +1,27 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
tab_width = 4
indent_style = space
insert_final_newline = false
max_line_length = 120
trim_trailing_whitespace = true
[*.go]
indent_style = tab
ij_smart_tabs = true
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true
ij_go_group_stdlib_imports = true
ij_go_import_sorting = goimports
ij_go_local_group_mode = project
ij_go_move_all_imports_in_one_declaration = true
ij_go_move_all_stdlib_imports_in_one_group = true
ij_go_remove_redundant_import_aliases = true
[*.{yml,yaml}]
indent_size = 2
tab_width = 2
insert_final_newline = true

View file

@ -7,8 +7,8 @@ import (
_ "github.com/tetratelabs/tinymem"
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
"code.icb4dc0.de/buildr/common/protocol"
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
"code.icb4dc0.de/buildr/wasi-module-sdk-go/protocol"
)
var defaultRegistry = NewTypeRegistry()
@ -22,9 +22,23 @@ func Inventory() uint64 {
var inventory rpcv1.PluginInventory
for _, t := range defaultRegistry.List() {
inventory.Modules = append(inventory.Modules, &rpcv1.ModuleReference{
ModuleCategory: t.Category,
ModuleType: t.Type,
m := defaultRegistry.Get(t.Category, t.Type)
spec, err := protocol.Marshal(m)
if err != nil {
panic(err)
}
data, err := spec.MarshalVT()
if err != nil {
panic(err)
}
inventory.Specs = append(inventory.Specs, &rpcv1.PluginInventory_InventorySpec{
ModuleRef: &rpcv1.ModuleReference{
ModuleCategory: t.Category,
ModuleType: t.Type,
},
EmptySpec: data,
})
}

5
go.mod
View file

@ -3,7 +3,8 @@ module code.icb4dc0.de/buildr/wasi-module-sdk-go
go 1.21
require (
code.icb4dc0.de/buildr/api v0.0.0-20230816163132-0ed4d13b0082
code.icb4dc0.de/buildr/api v0.0.0-20230823165833-52d0bd9e9741
code.icb4dc0.de/buildr/common v0.0.0-20230823164419-5a757074eaa3
github.com/tetratelabs/tinymem v0.1.0
)
@ -12,7 +13,7 @@ require (
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.57.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)

14
go.sum
View file

@ -1,5 +1,11 @@
code.icb4dc0.de/buildr/api v0.0.0-20230816163132-0ed4d13b0082 h1:oO//6VUwvXstu6rNWis3CDgapUrcPvrdRZvcsY/7PBw=
code.icb4dc0.de/buildr/api v0.0.0-20230816163132-0ed4d13b0082/go.mod h1:Bxt+fw/9hH7/WESz3asYBIWPr81UlUMEleXJGTqX6ys=
code.icb4dc0.de/buildr/api v0.0.0-20230817151157-dbc0adad8f8b h1:pZDHPJCMRw3UM1Jd0y7XnPZaDOzZ5du17EvW09mgb4g=
code.icb4dc0.de/buildr/api v0.0.0-20230817151157-dbc0adad8f8b/go.mod h1:Bxt+fw/9hH7/WESz3asYBIWPr81UlUMEleXJGTqX6ys=
code.icb4dc0.de/buildr/api v0.0.0-20230823165833-52d0bd9e9741 h1:uG2ECI+OT5oA5x2+G7upBhwKsYBS49xBIX+qlZrs9gw=
code.icb4dc0.de/buildr/api v0.0.0-20230823165833-52d0bd9e9741/go.mod h1:eCeIvmMgYp/04nar7GnBDORMdO4jHYQl/RMiNJhyGf8=
code.icb4dc0.de/buildr/common v0.0.0-20230823164147-3bae637e78ae h1:j2qiEThFUogxgU/zhuYTA/k0gp6pbTiYMSglwVKz47w=
code.icb4dc0.de/buildr/common v0.0.0-20230823164147-3bae637e78ae/go.mod h1:u0PEZjq0l3ICCUgARZObM6Hc0ZlqkcjK0PBEozhAdNQ=
code.icb4dc0.de/buildr/common v0.0.0-20230823164419-5a757074eaa3 h1:jWQCCE56vhfNoOuJj4JlCEcfXUB/40iR8L59j5xeh+s=
code.icb4dc0.de/buildr/common v0.0.0-20230823164419-5a757074eaa3/go.mod h1:u0PEZjq0l3ICCUgARZObM6Hc0ZlqkcjK0PBEozhAdNQ=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@ -15,8 +21,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=

View file

@ -1,21 +0,0 @@
package protocol
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Integer interface {
Signed | Unsigned
}
type Float interface {
~float32 | ~float64
}
type Numeric interface {
Integer | Float
}

View file

@ -1,189 +0,0 @@
package protocol
import (
"errors"
"fmt"
"reflect"
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
)
var ErrExpectedStruct = errors.New("expected struct")
func Marshal(in any) (*rpcv1.ModuleSpec, error) {
val := reflect.ValueOf(in)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return nil, fmt.Errorf("%w: got %T", ErrExpectedStruct, in)
}
return marshal(val)
}
func marshal(in reflect.Value) (*rpcv1.ModuleSpec, error) {
numField := in.NumField()
out := &rpcv1.ModuleSpec{
Values: make(map[string]*rpcv1.ModuleSpec_Value, numField),
}
inputType := in.Type()
for i := 0; i < numField; i++ {
structField := inputType.Field(i)
if !structField.IsExported() {
continue
}
out.Values[structField.Name] = mapReflectValueToSpecValue(in.Field(i))
}
return out, nil
}
func mapReflectValueToSpecValue(in reflect.Value) *rpcv1.ModuleSpec_Value {
switch in.Kind() {
case reflect.Bool:
return boolValue(in.Bool())
case reflect.String:
return stringValue(in.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intValue(in.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return intValue(in.Uint())
case reflect.Float32, reflect.Float64:
return floatValue(in.Float())
case reflect.Struct:
return structValue(in)
case reflect.Slice, reflect.Array:
switch in.Type().Elem().Kind() {
case reflect.Bool:
return boolSliceValue(in)
case reflect.String:
return stringSliceValue(in)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intSliceValue(in, reflect.Value.Int)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return intSliceValue(in, func(v reflect.Value) int64 {
return int64(v.Uint())
})
case reflect.Float32, reflect.Float64:
return floatSliceValue(in)
default:
return nil
}
default:
return nil
}
}
func structValue(in reflect.Value) *rpcv1.ModuleSpec_Value {
inputType := in.Type()
numFields := inputType.NumField()
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeObject,
ComplexValue: make(map[string]*rpcv1.ModuleSpec_Value, numFields),
}
for i := 0; i < numFields; i++ {
structField := inputType.Field(i)
if !structField.IsExported() {
continue
}
result.ComplexValue[structField.Name] = mapReflectValueToSpecValue(in.Field(i))
}
return result
}
func boolValue(in bool) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_BoolValue{
BoolValue: in,
},
}
}
func boolSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeBoolSlice,
BoolValues: make([]bool, 0, val.Len()),
}
for i := 0; i < val.Len(); i++ {
result.BoolValues = append(result.BoolValues, val.Index(i).Bool())
}
return result
}
func intValue[T Integer](in T) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_IntValue{
IntValue: int64(in),
},
}
}
func intSliceValue(val reflect.Value, selector func(v reflect.Value) int64) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeIntSlice,
IntValues: make([]int64, 0, val.Len()),
}
for i := 0; i < val.Len(); i++ {
result.IntValues = append(result.IntValues, selector(val.Index(i)))
}
return result
}
func floatValue[T Float](in T) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_DoubleValue{
DoubleValue: float64(in),
},
}
}
func floatSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeIntSlice,
DoubleValues: make([]float64, 0, val.Len()),
}
for i := 0; i < val.Len(); i++ {
result.DoubleValues = append(result.DoubleValues, val.Index(i).Float())
}
return result
}
func stringValue(in string) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: in,
},
}
}
func stringSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeIntSlice,
StringValues: make([]string, 0, val.Len()),
}
for i := 0; i < val.Len(); i++ {
result.StringValues = append(result.StringValues, val.Index(i).String())
}
return result
}

View file

@ -1,220 +0,0 @@
package protocol_test
import (
"testing"
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
"code.icb4dc0.de/buildr/wasi-module-sdk-go/protocol"
)
func TestMarshal_Bool_Success(t *testing.T) {
input := struct {
IsDeleted bool
}{
IsDeleted: true,
}
spec, err := protocol.Marshal(input)
if err != nil {
t.Fatal(err)
}
nameVal, ok := spec.Values["IsDeleted"]
if !ok {
t.Fatal("IsDeleted not found")
}
if nameVal.Type != rpcv1.ModuleSpec_ValueTypeSingle {
t.Fatalf("Expected single value type, got %v", nameVal.Type)
}
if s, ok := nameVal.SingleValue.(*rpcv1.ModuleSpec_Value_BoolValue); !ok {
t.Fatalf("Expected string value, got %v", nameVal.SingleValue)
} else if !s.BoolValue {
t.Errorf("Expected bool value to be true, got %t", s.BoolValue)
}
}
func TestMarshal_BoolSlice_Success(t *testing.T) {
input := struct {
IsDeleted []bool
}{
IsDeleted: []bool{true},
}
spec, err := protocol.Marshal(input)
if err != nil {
t.Fatal(err)
}
nameVal, ok := spec.Values["IsDeleted"]
if !ok {
t.Fatal("IsDeleted not found")
}
if nameVal.Type != rpcv1.ModuleSpec_ValueTypeBoolSlice {
t.Fatalf("Expected bool slice value type, got %v", nameVal.Type)
}
if len(nameVal.BoolValues) < 1 {
t.Fatalf("Expected at least one bool value, got %d", len(nameVal.BoolValues))
}
if !nameVal.BoolValues[0] {
t.Errorf("Expected bool value to be true, got %t", nameVal.BoolValues[0])
}
}
func TestMarshal_Int_Success(t *testing.T) {
input := struct {
Age int
}{
Age: 42,
}
spec, err := protocol.Marshal(input)
if err != nil {
t.Fatal(err)
}
nameVal, ok := spec.Values["Age"]
if !ok {
t.Fatal("Age not found")
}
if nameVal.Type != rpcv1.ModuleSpec_ValueTypeSingle {
t.Fatalf("Expected single value type, got %v", nameVal.Type)
}
if s, ok := nameVal.SingleValue.(*rpcv1.ModuleSpec_Value_IntValue); !ok {
t.Fatalf("Expected string value, got %v", nameVal.SingleValue)
} else if s.IntValue != 42 {
t.Errorf("Expected int value to be 42, got %d", s.IntValue)
}
}
func TestMarshal_IntSlice_Success(t *testing.T) {
input := struct {
Ages []int
}{
Ages: []int{42},
}
spec, err := protocol.Marshal(input)
if err != nil {
t.Fatal(err)
}
nameVal, ok := spec.Values["Ages"]
if !ok {
t.Fatal("Ages not found")
}
if nameVal.Type != rpcv1.ModuleSpec_ValueTypeIntSlice {
t.Fatalf("Expected int slice value type, got %v", nameVal.Type)
}
if len(nameVal.IntValues) < 1 {
t.Fatalf("Expected at least one bool value, got %d", len(nameVal.BoolValues))
}
if nameVal.IntValues[0] != 42 {
t.Errorf("Expected int value to be 52, got %d", nameVal.IntValues[0])
}
}
func TestMarshal_StringField_Success(t *testing.T) {
input := struct {
Name string
}{
Name: "John Doe",
}
spec, err := protocol.Marshal(input)
if err != nil {
t.Fatal(err)
}
nameVal, ok := spec.Values["Name"]
if !ok {
t.Fatal("Name not found")
}
if nameVal.Type != rpcv1.ModuleSpec_ValueTypeSingle {
t.Fatalf("Expected single value type, got %v", nameVal.Type)
}
if s, ok := nameVal.SingleValue.(*rpcv1.ModuleSpec_Value_StringValue); !ok {
t.Fatalf("Expected string value, got %v", nameVal.SingleValue)
} else if s.StringValue != "John Doe" {
t.Errorf("Expected string value to be John Doe, got %s", s.StringValue)
}
}
func TestMarshal_Float64_Success(t *testing.T) {
input := struct {
Pi float64
}{
Pi: 3.14,
}
spec, err := protocol.Marshal(input)
if err != nil {
t.Fatal(err)
}
nameVal, ok := spec.Values["Pi"]
if !ok {
t.Fatal("Pi not found")
}
if nameVal.Type != rpcv1.ModuleSpec_ValueTypeSingle {
t.Fatalf("Expected single value type, got %v", nameVal.Type)
}
if s, ok := nameVal.SingleValue.(*rpcv1.ModuleSpec_Value_DoubleValue); !ok {
t.Fatalf("Expected double value, got %v", nameVal.SingleValue)
} else if s.DoubleValue-3.14 > 0.000001 {
t.Errorf("Expected double value to be 3.14, got %f", s.DoubleValue)
}
}
func TestMarshal_NestedStruct_Success(t *testing.T) {
type Address struct {
City string
}
input := struct {
Address Address
}{
Address: Address{
City: "New York",
},
}
spec, err := protocol.Marshal(input)
if err != nil {
t.Fatal(err)
}
addressVal, ok := spec.Values["Address"]
if !ok {
t.Fatal("Address not found")
}
if addressVal.Type != rpcv1.ModuleSpec_ValueTypeObject {
t.Fatalf("Expected object value type, got %v", addressVal.Type)
}
cityVal, ok := addressVal.ComplexValue["City"]
if !ok {
t.Fatal("City not found")
}
if cityVal.Type != rpcv1.ModuleSpec_ValueTypeSingle {
t.Fatalf("Expected single value type, got %v", cityVal.Type)
}
if cityVal.SingleValue.(*rpcv1.ModuleSpec_Value_StringValue).StringValue != "New York" {
t.Errorf("Expected string value to be New York, got %s", cityVal.SingleValue.(*rpcv1.ModuleSpec_Value_StringValue).StringValue)
}
}

View file

@ -1,206 +0,0 @@
package protocol
import (
"errors"
"fmt"
"reflect"
"strings"
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
)
var ErrUnmatchingType = errors.New("field type does not match wire value")
func Unmarshal(input *rpcv1.ModuleSpec, into any) error {
cfg := unmarshalConfig{
TagName: "hcl",
}
val := reflect.ValueOf(into)
if val.Kind() != reflect.Ptr {
return errors.New("into value must be a pointer")
}
return cfg.unmarshal(input.Values, val.Elem(), reflect.TypeOf(into).Elem())
}
type unmarshalConfig struct {
TagName string
}
func (cfg unmarshalConfig) unmarshal(input map[string]*rpcv1.ModuleSpec_Value, into reflect.Value, intoType reflect.Type) error {
for i := 0; i < intoType.NumField(); i++ {
tf := intoType.Field(i)
if !tf.IsExported() {
continue
}
fieldName := cfg.fieldName(tf)
val, ok := input[fieldName]
if !ok {
continue
}
if mapped, err := cfg.mapSpecValueTo(val, tf.Type); err != nil {
return fmt.Errorf("failed to map value for field %s: %w", tf.Name, err)
} else if into.Type().Kind() == reflect.Pointer {
into.Elem().Field(i).Set(mapped)
} else {
into.Field(i).Set(mapped)
}
delete(input, fieldName)
}
if len(input) > 0 {
keys := make([]string, 0, len(input))
for k := range input {
keys = append(keys, k)
}
return fmt.Errorf("unknown fields: %v", keys)
}
return nil
}
func (cfg unmarshalConfig) mapSpecValueTo(val *rpcv1.ModuleSpec_Value, targetType reflect.Type) (reflect.Value, error) {
switch val.Type {
case rpcv1.ModuleSpec_ValueTypeUnknown:
return reflect.Value{}, ErrUnmatchingType
case rpcv1.ModuleSpec_ValueTypeObject:
if targetType.Kind() == reflect.Struct {
structVal := reflect.New(targetType)
if err := cfg.unmarshal(val.ComplexValue, structVal, targetType); err != nil {
return reflect.Value{}, err
} else {
return structVal.Elem(), nil
}
} else if targetType.Kind() == reflect.Pointer && targetType.Elem().Kind() == reflect.Struct {
structVal := reflect.New(targetType.Elem())
if err := cfg.unmarshal(val.ComplexValue, structVal, targetType.Elem()); err != nil {
return reflect.Value{}, err
} else {
return structVal, nil
}
} else if targetType.Kind() != reflect.Map {
return reflect.Value{}, fmt.Errorf("%w: expected struct, got %s", ErrUnmatchingType, targetType.String())
}
fallthrough
case rpcv1.ModuleSpec_ValueTypeMap:
if targetType.Kind() != reflect.Map {
return reflect.Value{}, fmt.Errorf("%w: expected map, got %v", ErrUnmatchingType, targetType)
}
mapVal := reflect.MakeMap(targetType)
for k, v := range val.ComplexValue {
if mappedVal, err := cfg.mapSpecValueTo(v, targetType.Elem()); err != nil {
return reflect.Value{}, err
} else {
mapVal.SetMapIndex(reflect.ValueOf(k), mappedVal)
}
}
return mapVal, nil
case rpcv1.ModuleSpec_ValueTypeSingle:
switch sv := val.SingleValue.(type) {
case *rpcv1.ModuleSpec_Value_BoolValue:
if targetType.Kind() != reflect.Bool {
return reflect.Value{}, fmt.Errorf("%w: expected bool, got %v", ErrUnmatchingType, targetType)
}
return reflect.ValueOf(sv.BoolValue), nil
case *rpcv1.ModuleSpec_Value_StringValue:
if targetType.Kind() != reflect.String {
return reflect.Value{}, fmt.Errorf("%w: expected string, got %v", ErrUnmatchingType, targetType)
}
return reflect.ValueOf(sv.StringValue), nil
case *rpcv1.ModuleSpec_Value_IntValue:
if targetType.Kind() != reflect.Int {
return reflect.Value{}, fmt.Errorf("%w: expected int, got %v", ErrUnmatchingType, targetType)
}
return reflect.ValueOf(int(sv.IntValue)), nil
case *rpcv1.ModuleSpec_Value_DoubleValue:
if targetType.Kind() != reflect.Float64 {
return reflect.Value{}, fmt.Errorf("%w: expected float64, got %v", ErrUnmatchingType, targetType)
}
return reflect.ValueOf(sv.DoubleValue), nil
}
case rpcv1.ModuleSpec_ValueTypeBoolSlice:
if targetType.Kind() != reflect.Slice {
return reflect.Value{}, fmt.Errorf("%w: expected slice, got %v", ErrUnmatchingType, targetType)
} else if targetType.Elem().Kind() != reflect.Bool {
return reflect.Value{}, fmt.Errorf("%w: expected bool, got %v", ErrUnmatchingType, targetType.Elem())
}
return reflect.ValueOf(val.BoolValues), nil
case rpcv1.ModuleSpec_ValueTypeStringSlice:
if targetType.Kind() != reflect.Slice {
return reflect.Value{}, fmt.Errorf("%w: expected slice, got %v", ErrUnmatchingType, targetType)
} else if targetType.Elem().Kind() != reflect.String {
return reflect.Value{}, fmt.Errorf("%w: expected string, got %v", ErrUnmatchingType, targetType.Elem())
}
return reflect.ValueOf(val.StringValues), nil
case rpcv1.ModuleSpec_ValueTypeIntSlice:
if targetType.Kind() != reflect.Slice {
return reflect.Value{}, fmt.Errorf("%w: expected slice, got %v", ErrUnmatchingType, targetType)
}
switch targetType.Elem().Kind() {
case reflect.Int:
return reflect.ValueOf(convertNumericSlice[int64, int](val.IntValues)), nil
case reflect.Int8:
return reflect.ValueOf(convertNumericSlice[int64, int8](val.IntValues)), nil
case reflect.Int16:
return reflect.ValueOf(convertNumericSlice[int64, int16](val.IntValues)), nil
case reflect.Int32:
return reflect.ValueOf(convertNumericSlice[int64, int32](val.IntValues)), nil
case reflect.Int64:
return reflect.ValueOf(val.IntValues), nil
default:
return reflect.Value{}, fmt.Errorf("%w: expected int, got %v", ErrUnmatchingType, targetType.Elem())
}
case rpcv1.ModuleSpec_ValueTypeDoubleSlice:
if targetType.Kind() != reflect.Slice {
return reflect.Value{}, fmt.Errorf("%w: expected slice, got %v", ErrUnmatchingType, targetType)
}
switch targetType.Elem().Kind() {
case reflect.Float32:
return reflect.ValueOf(convertNumericSlice[float64, float32](val.DoubleValues)), nil
case reflect.Float64:
return reflect.ValueOf(val.DoubleValues), nil
default:
return reflect.Value{}, fmt.Errorf("%w: expected int, got %v", ErrUnmatchingType, targetType.Elem())
}
}
return reflect.Value{}, nil
}
func (cfg unmarshalConfig) fieldName(field reflect.StructField) string {
tagVal, ok := field.Tag.Lookup(cfg.TagName)
if !ok {
return field.Name
}
split := strings.Split(tagVal, ",")
switch {
case len(split) == 0:
fallthrough
case split[0] == "":
return strings.ToLower(field.Name)
default:
return strings.ToLower(split[0])
}
}
type numeric interface {
Integer | Float
}
func convertNumericSlice[TIn numeric, TOut numeric](input []TIn) []TOut {
output := make([]TOut, len(input))
for i, v := range input {
output[i] = TOut(v)
}
return output
}

View file

@ -1,515 +0,0 @@
package protocol_test
import (
"testing"
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
"code.icb4dc0.de/buildr/wasi-module-sdk-go/protocol"
)
func TestUnmarshal_Bool_Success(t *testing.T) {
target := struct {
Delete bool
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Delete": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_BoolValue{
BoolValue: true,
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if !target.Delete {
t.Errorf("Expected Delete to be true")
}
}
func TestUnmarshal_Bool_Err(t *testing.T) {
target := struct {
Delete string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Delete": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_BoolValue{
BoolValue: true,
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_Bool_Slice_Success(t *testing.T) {
target := struct {
Delete []bool
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Delete": {
Type: rpcv1.ModuleSpec_ValueTypeBoolSlice,
BoolValues: []bool{true},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if len(target.Delete) < 1 {
t.Errorf("Expected Delete to have at least one element")
} else if !target.Delete[0] {
t.Errorf("Expected Delete[0] to be true")
}
}
func TestUnmarshal_Bool_Slice_Err(t *testing.T) {
target := struct {
Delete []string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Delete": {
Type: rpcv1.ModuleSpec_ValueTypeBoolSlice,
BoolValues: []bool{true},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_String_Success(t *testing.T) {
target := struct {
Name string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Name": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: "Ted",
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if target.Name != "Ted" {
t.Errorf("Expected Name to be 'Ted'")
}
}
func TestUnmarshal_String_Err(t *testing.T) {
target := struct {
Name int
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Name": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: "Ted",
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_String_Slice_Success(t *testing.T) {
target := struct {
Names []string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Names": {
Type: rpcv1.ModuleSpec_ValueTypeStringSlice,
StringValues: []string{"Ted"},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if len(target.Names) < 1 {
t.Errorf("Expected Names to have at least one element")
} else if target.Names[0] != "Ted" {
t.Errorf("Expected Names[0] to be 'Ted'")
}
}
func TestUnmarshal_String_Slice_Err(t *testing.T) {
target := struct {
Names []int
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Names": {
Type: rpcv1.ModuleSpec_ValueTypeStringSlice,
StringValues: []string{"Ted"},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_Int_Success(t *testing.T) {
target := struct {
Age int
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Age": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_IntValue{
IntValue: 42,
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if target.Age != 42 {
t.Errorf("Expected Age to be 42")
}
}
func TestUnmarshal_Int_Err(t *testing.T) {
target := struct {
Age string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Age": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_IntValue{
IntValue: 42,
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_Int_Slice_Success(t *testing.T) {
target := struct {
Ages []int
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Ages": {
Type: rpcv1.ModuleSpec_ValueTypeIntSlice,
IntValues: []int64{42},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if len(target.Ages) < 1 {
t.Errorf("Expected Ages to have at least one element")
} else if target.Ages[0] != 42 {
t.Errorf("Expected Ages[0] to be 42")
}
}
func TestUnmarshal_Int_Slice_Err(t *testing.T) {
target := struct {
Ages []string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Ages": {
Type: rpcv1.ModuleSpec_ValueTypeIntSlice,
IntValues: []int64{42},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_Double_Success(t *testing.T) {
target := struct {
Pi float64
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Pi": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_DoubleValue{
DoubleValue: 3.14,
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if target.Pi-3.14 > 0.0000001 {
t.Errorf("Expected Pi to be 3.14")
}
}
func TestUnmarshal_Double_Err(t *testing.T) {
target := struct {
Pi string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Pi": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_DoubleValue{
DoubleValue: 3.14,
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_Double_Slice_Success(t *testing.T) {
target := struct {
Pis []float64
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Pis": {
Type: rpcv1.ModuleSpec_ValueTypeDoubleSlice,
DoubleValues: []float64{3.14},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if len(target.Pis) < 1 {
t.Errorf("Expected Pis to have at least one element")
} else if target.Pis[0]-3.14 > 0.0000001 {
t.Errorf("Expected Pis[0] to be 3.14")
}
}
func TestUnmarshal_Double_Slice_Err(t *testing.T) {
target := struct {
Pis []string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Pis": {
Type: rpcv1.ModuleSpec_ValueTypeDoubleSlice,
DoubleValues: []float64{3.14},
},
},
}
if err := protocol.Unmarshal(spec, &target); err == nil {
t.Errorf("Expected error")
} else {
t.Log(err.Error())
}
}
func TestUnmarshal_NestedStruct_Success(t *testing.T) {
target := struct {
Address struct {
City string
}
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Address": {
Type: rpcv1.ModuleSpec_ValueTypeObject,
ComplexValue: map[string]*rpcv1.ModuleSpec_Value{
"City": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: "New York",
},
},
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if target.Address.City != "New York" {
t.Errorf("Expected City to be 'New York'")
}
}
func TestUnmarshal_NestedStructPointer_Success(t *testing.T) {
target := struct {
Address *struct {
City string
}
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Address": {
Type: rpcv1.ModuleSpec_ValueTypeObject,
ComplexValue: map[string]*rpcv1.ModuleSpec_Value{
"City": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: "New York",
},
},
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if target.Address.City != "New York" {
t.Errorf("Expected City to be 'New York'")
}
}
func TestUnmarshal_Map_Success(t *testing.T) {
target := struct {
Values map[string]string
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"Values": {
Type: rpcv1.ModuleSpec_ValueTypeMap,
ComplexValue: map[string]*rpcv1.ModuleSpec_Value{
"City": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: "New York",
},
},
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if target.Values["City"] != "New York" {
t.Errorf("Expected City to be 'New York'")
}
}
func TestUnmarshal_NestedMap_Success(t *testing.T) {
target := struct {
City struct {
Labels map[string]string
}
}{}
spec := &rpcv1.ModuleSpec{
Values: map[string]*rpcv1.ModuleSpec_Value{
"City": {
Type: rpcv1.ModuleSpec_ValueTypeObject,
ComplexValue: map[string]*rpcv1.ModuleSpec_Value{
"Labels": {
Type: rpcv1.ModuleSpec_ValueTypeMap,
ComplexValue: map[string]*rpcv1.ModuleSpec_Value{
"Region": {
Type: rpcv1.ModuleSpec_ValueTypeSingle,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: "west",
},
},
},
},
},
},
},
}
if err := protocol.Unmarshal(spec, &target); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if target.City.Labels["Region"] != "west" {
t.Errorf("Expected 'Region' to be 'west'")
}
}

18
renovate.json Normal file
View file

@ -0,0 +1,18 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
":dependencyDashboard",
":ignoreModulesAndTests",
":semanticPrefixFixDepsChoreOthers",
":autodetectPinVersions",
":prHourlyLimit2",
":prConcurrentLimit10",
"group:monorepos",
"group:recommended",
"workarounds:all"
],
"postUpdateOptions": [
"gomodTidy1.17",
"gomodUpdateImportPaths"
]
}