feat: initial code migration
This commit is contained in:
parent
1b2b717168
commit
4d62828fa6
9 changed files with 1258 additions and 0 deletions
27
.editorconfig
Normal file
27
.editorconfig
Normal 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
|
17
go.mod
Normal file
17
go.mod
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
module code.icb4dc0.de/buildr/common
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.21.0
|
||||||
|
|
||||||
|
require code.icb4dc0.de/buildr/api v0.0.0-20230817151157-dbc0adad8f8b
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
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/grpc v1.57.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
)
|
11
protocol/api.go
Normal file
11
protocol/api.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
|
||||||
|
|
||||||
|
type SpecUnmarshaler interface {
|
||||||
|
UnmarshalModuleSpec(val *rpcv1.ModuleSpec) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpecValueUnmarshaler interface {
|
||||||
|
UnmarshalSpecValue(val *rpcv1.ModuleSpec_Value) error
|
||||||
|
}
|
21
protocol/constraints.go
Normal file
21
protocol/constraints.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
||||||
|
}
|
236
protocol/marshal.go
Normal file
236
protocol/marshal.go
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrExpectedStruct = errors.New("expected struct")
|
||||||
|
|
||||||
|
func Marshal(in any) (*rpcv1.ModuleSpec, error) {
|
||||||
|
cfg := marshalConfig{
|
||||||
|
TagName: "hcl",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 cfg.marshal(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
type marshalConfig struct {
|
||||||
|
TagName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg marshalConfig) marshal(in reflect.Value) (*rpcv1.ModuleSpec, error) {
|
||||||
|
numField := in.NumField()
|
||||||
|
|
||||||
|
out := &rpcv1.ModuleSpec{
|
||||||
|
Values: make(map[string]*rpcv1.ModuleSpec_Value, numField),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO consider tag for field name
|
||||||
|
|
||||||
|
inputType := in.Type()
|
||||||
|
|
||||||
|
for i := 0; i < numField; i++ {
|
||||||
|
structField := inputType.Field(i)
|
||||||
|
if !structField.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName := cfg.fieldName(structField)
|
||||||
|
|
||||||
|
out.Values[fieldName] = cfg.mapReflectValueToSpecValue(in.Field(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg marshalConfig) mapReflectValueToSpecValue(in reflect.Value) *rpcv1.ModuleSpec_Value {
|
||||||
|
switch in.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
return cfg.mapValue(in)
|
||||||
|
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 cfg.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 (cfg marshalConfig) 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg marshalConfig) mapValue(in reflect.Value) *rpcv1.ModuleSpec_Value {
|
||||||
|
result := &rpcv1.ModuleSpec_Value{
|
||||||
|
Type: rpcv1.ModuleSpec_ValueTypeMap,
|
||||||
|
ComplexValue: make(map[string]*rpcv1.ModuleSpec_Value, in.Len()),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := in.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
result.ComplexValue[iter.Key().String()] = cfg.mapReflectValueToSpecValue(iter.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg marshalConfig) 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] = cfg.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_ValueTypeDoubleSlice,
|
||||||
|
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_ValueTypeStringSlice,
|
||||||
|
StringValues: make([]string, 0, val.Len()),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
result.StringValues = append(result.StringValues, val.Index(i).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
220
protocol/marshal_test.go
Normal file
220
protocol/marshal_test.go
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
package protocol_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
|
||||||
|
"code.icb4dc0.de/buildr/common/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)
|
||||||
|
}
|
||||||
|
}
|
193
protocol/unmarshal.go
Normal file
193
protocol/unmarshal.go
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
if u, ok := into.(SpecUnmarshaler); ok {
|
||||||
|
return u.UnmarshalModuleSpec(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.ValueOf(into)
|
||||||
|
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
default:
|
||||||
|
return errors.New("into value must be a pointer")
|
||||||
|
}
|
||||||
|
return unmarshal(input.Values, val.Elem(), reflect.TypeOf(into).Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := input[strings.ToLower(tf.Name)]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if into.Type().Kind() == reflect.Pointer {
|
||||||
|
if u, ok := into.Elem().Field(i).Interface().(SpecValueUnmarshaler); ok {
|
||||||
|
if err := u.UnmarshalSpecValue(val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if u, ok := into.Field(i).Interface().(SpecValueUnmarshaler); ok {
|
||||||
|
if err := u.UnmarshalSpecValue(val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mapped, err := mapSpecValueTo(val, tf.Type); err != nil {
|
||||||
|
return err
|
||||||
|
} else if into.Type().Kind() == reflect.Pointer {
|
||||||
|
into.Elem().Field(i).Set(mapped)
|
||||||
|
} else {
|
||||||
|
into.Field(i).Set(mapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapSpecValueTo(val *rpcv1.ModuleSpec_Value, targetType reflect.Type) (reflect.Value, error) {
|
||||||
|
switch val.Type {
|
||||||
|
case rpcv1.ModuleSpec_ValueTypeUnknown:
|
||||||
|
return reflect.Value{}, fmt.Errorf("%w: expected %s", ErrUnmatchingType, targetType.String())
|
||||||
|
case rpcv1.ModuleSpec_ValueTypeObject:
|
||||||
|
if targetType.Kind() == reflect.Struct {
|
||||||
|
structVal := reflect.New(targetType)
|
||||||
|
if err := 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 := unmarshal(val.ComplexValue, structVal, targetType.Elem()); err != nil {
|
||||||
|
return reflect.Value{}, err
|
||||||
|
} else {
|
||||||
|
return structVal, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return reflect.Value{}, fmt.Errorf("%w: expected struct, got %s", ErrUnmatchingType, val.Type)
|
||||||
|
}
|
||||||
|
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 := 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
515
protocol/unmarshal_test.go
Normal file
515
protocol/unmarshal_test.go
Normal file
|
@ -0,0 +1,515 @@
|
||||||
|
package protocol_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
|
||||||
|
"code.icb4dc0.de/buildr/common/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
18
renovate.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue