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 }