feat: unify marshal behavior

This commit is contained in:
Peter 2023-08-23 18:41:47 +02:00
parent 4d62828fa6
commit 5a757074ea
Signed by: prskr
GPG key ID: C1DB5D2E8DB512F9
5 changed files with 165 additions and 16 deletions

23
go.sum Normal file
View file

@ -0,0 +1,23 @@
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=
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=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
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/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=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

71
hcl/tag.go Normal file
View file

@ -0,0 +1,71 @@
package hcl
import (
"errors"
"fmt"
"reflect"
"strings"
)
const (
ElementTypeAttribute ElementType = "attr"
ElementTypeBlock ElementType = "block"
ElementTypeLabel ElementType = "label"
ElementTypeRemain ElementType = "remain"
)
var ErrNoHclTag = errors.New("no hcl tag")
type ElementType string
type FieldMeta struct {
Name string
Type ElementType
Optional bool
Ignore bool
}
func ExtractFieldMeta(t reflect.StructField, readTagName, writeTagName string) (FieldMeta, error) {
hclTag, present := t.Tag.Lookup(readTagName)
if !present {
return FieldMeta{}, ErrNoHclTag
}
cfg := FieldMeta{
Name: t.Name,
Type: ElementTypeAttribute,
}
if hclWriteTag, present := t.Tag.Lookup(writeTagName); present && hclWriteTag == "-" {
cfg.Ignore = true
return cfg, nil
}
split := strings.Split(hclTag, ",")
if len(split) > 0 && split[0] != "" {
cfg.Name = split[0]
}
if len(split) > 1 {
switch split[1] {
case "":
fallthrough
case "attr":
cfg.Type = ElementTypeAttribute
case "optional":
cfg.Optional = true
case "label":
cfg.Type = ElementTypeLabel
case "block":
cfg.Type = ElementTypeBlock
case "remain":
cfg.Type = ElementTypeRemain
default:
return FieldMeta{}, fmt.Errorf("unknown element type %q", split[1])
}
}
return cfg, nil
}

26
protocol/config.go Normal file
View file

@ -0,0 +1,26 @@
package protocol
var (
_ marshalConfigOption = WithTagName("hcl")
_ unmarshalConfigOption = WithTagName("hcl")
)
type WithTagName string
func (o WithTagName) applyToMarshalConfig(cfg *marshalConfig) {
cfg.TagName = string(o)
}
func (o WithTagName) applyToUnmarshalConfig(cfg *unmarshalConfig) {
cfg.TagName = string(o)
}
func defaultConfig() protocolConfig {
return protocolConfig{
TagName: "hcl",
}
}
type protocolConfig struct {
TagName string
}

View file

@ -11,9 +11,17 @@ import (
var ErrExpectedStruct = errors.New("expected struct")
func Marshal(in any) (*rpcv1.ModuleSpec, error) {
type marshalConfigOption interface {
applyToMarshalConfig(cfg *marshalConfig)
}
func Marshal(in any, opts ...marshalConfigOption) (*rpcv1.ModuleSpec, error) {
cfg := marshalConfig{
TagName: "hcl",
protocolConfig: defaultConfig(),
}
for _, opt := range opts {
opt.applyToMarshalConfig(&cfg)
}
val := reflect.ValueOf(in)
@ -29,7 +37,7 @@ func Marshal(in any) (*rpcv1.ModuleSpec, error) {
}
type marshalConfig struct {
TagName string
protocolConfig
}
func (cfg marshalConfig) marshal(in reflect.Value) (*rpcv1.ModuleSpec, error) {
@ -39,8 +47,6 @@ func (cfg marshalConfig) marshal(in reflect.Value) (*rpcv1.ModuleSpec, error) {
Values: make(map[string]*rpcv1.ModuleSpec_Value, numField),
}
// TODO consider tag for field name
inputType := in.Type()
for i := 0; i < numField; i++ {
@ -116,6 +122,7 @@ func (cfg marshalConfig) fieldName(field reflect.StructField) string {
func (cfg marshalConfig) mapValue(in reflect.Value) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeMap,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
ComplexValue: make(map[string]*rpcv1.ModuleSpec_Value, in.Len()),
}
@ -132,6 +139,7 @@ func (cfg marshalConfig) structValue(in reflect.Value) *rpcv1.ModuleSpec_Value {
numFields := inputType.NumField()
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeObject,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
ComplexValue: make(map[string]*rpcv1.ModuleSpec_Value, numFields),
}
@ -150,6 +158,7 @@ func (cfg marshalConfig) structValue(in reflect.Value) *rpcv1.ModuleSpec_Value {
func boolValue(in bool) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
SingleValue: &rpcv1.ModuleSpec_Value_BoolValue{
BoolValue: in,
},
@ -159,6 +168,7 @@ func boolValue(in bool) *rpcv1.ModuleSpec_Value {
func boolSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeBoolSlice,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
BoolValues: make([]bool, 0, val.Len()),
}
@ -172,6 +182,7 @@ func boolSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
func intValue[T Integer](in T) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
SingleValue: &rpcv1.ModuleSpec_Value_IntValue{
IntValue: int64(in),
},
@ -181,6 +192,7 @@ func intValue[T Integer](in T) *rpcv1.ModuleSpec_Value {
func intSliceValue(val reflect.Value, selector func(v reflect.Value) int64) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeIntSlice,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
IntValues: make([]int64, 0, val.Len()),
}
@ -194,6 +206,7 @@ func intSliceValue(val reflect.Value, selector func(v reflect.Value) int64) *rpc
func floatValue[T Float](in T) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
SingleValue: &rpcv1.ModuleSpec_Value_DoubleValue{
DoubleValue: float64(in),
},
@ -203,6 +216,7 @@ func floatValue[T Float](in T) *rpcv1.ModuleSpec_Value {
func floatSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeDoubleSlice,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
DoubleValues: make([]float64, 0, val.Len()),
}
@ -216,6 +230,7 @@ func floatSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
func stringValue(in string) *rpcv1.ModuleSpec_Value {
return &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeSingle,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
SingleValue: &rpcv1.ModuleSpec_Value_StringValue{
StringValue: in,
},
@ -225,6 +240,7 @@ func stringValue(in string) *rpcv1.ModuleSpec_Value {
func stringSliceValue(val reflect.Value) *rpcv1.ModuleSpec_Value {
result := &rpcv1.ModuleSpec_Value{
Type: rpcv1.ModuleSpec_ValueTypeStringSlice,
Kind: rpcv1.ModuleSpec_ValueKindAttribute,
StringValues: make([]string, 0, val.Len()),
}

View file

@ -9,16 +9,25 @@ import (
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
)
var (
ErrUnmatchingType = errors.New("field type does not match wire value")
)
var ErrUnmatchingType = errors.New("field type does not match wire value")
func Unmarshal(input *rpcv1.ModuleSpec, into any) error {
type unmarshalConfigOption interface {
applyToUnmarshalConfig(cfg *unmarshalConfig)
}
func Unmarshal(input *rpcv1.ModuleSpec, into any, opts ...unmarshalConfigOption) error {
if u, ok := into.(SpecUnmarshaler); ok {
return u.UnmarshalModuleSpec(input)
}
cfg := unmarshalConfig{
protocolConfig: defaultConfig(),
}
for _, opt := range opts {
opt.applyToUnmarshalConfig(&cfg)
}
val := reflect.ValueOf(into)
switch val.Kind() {
@ -26,10 +35,14 @@ func Unmarshal(input *rpcv1.ModuleSpec, into any) error {
default:
return errors.New("into value must be a pointer")
}
return unmarshal(input.Values, val.Elem(), reflect.TypeOf(into).Elem())
return cfg.unmarshal(input.Values, val.Elem(), reflect.TypeOf(into).Elem())
}
func unmarshal(input map[string]*rpcv1.ModuleSpec_Value, into reflect.Value, intoType reflect.Type) error {
type unmarshalConfig struct {
protocolConfig
}
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() {
@ -57,7 +70,7 @@ func unmarshal(input map[string]*rpcv1.ModuleSpec_Value, into reflect.Value, int
}
}
if mapped, err := mapSpecValueTo(val, tf.Type); err != nil {
if mapped, err := cfg.mapSpecValueTo(val, tf.Type); err != nil {
return err
} else if into.Type().Kind() == reflect.Pointer {
into.Elem().Field(i).Set(mapped)
@ -69,21 +82,21 @@ func unmarshal(input map[string]*rpcv1.ModuleSpec_Value, into reflect.Value, int
return nil
}
func mapSpecValueTo(val *rpcv1.ModuleSpec_Value, targetType reflect.Type) (reflect.Value, error) {
func (cfg unmarshalConfig) 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 {
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 := unmarshal(val.ComplexValue, structVal, targetType.Elem()); err != nil {
if err := cfg.unmarshal(val.ComplexValue, structVal, targetType.Elem()); err != nil {
return reflect.Value{}, err
} else {
return structVal, nil
@ -99,7 +112,7 @@ func mapSpecValueTo(val *rpcv1.ModuleSpec_Value, targetType reflect.Type) (refle
mapVal := reflect.MakeMap(targetType)
for k, v := range val.ComplexValue {
if mappedVal, err := mapSpecValueTo(v, targetType.Elem()); err != nil {
if mappedVal, err := cfg.mapSpecValueTo(v, targetType.Elem()); err != nil {
return reflect.Value{}, err
} else {
mapVal.SetMapIndex(reflect.ValueOf(k), mappedVal)