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 }