package apimiddleware import ( "encoding/base64" "fmt" "math/big" "reflect" "strconv" "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/wealdtech/go-bytesutil" ) // processField calls each processor function on any field that has the matching tag set. // It is a recursive function. func processField(s interface{}, processors []fieldProcessor) error { kind := reflect.TypeOf(s).Kind() if kind != reflect.Ptr && kind != reflect.Slice && kind != reflect.Array { return fmt.Errorf("processing fields of kind '%v' is unsupported", kind) } t := reflect.TypeOf(s).Elem() v := reflect.Indirect(reflect.ValueOf(s)) for i := 0; i < t.NumField(); i++ { switch v.Field(i).Kind() { case reflect.Slice: sliceElem := t.Field(i).Type.Elem() kind := sliceElem.Kind() // Recursively process slices to struct pointers. switch { case kind == reflect.Ptr && sliceElem.Elem().Kind() == reflect.Struct: for j := 0; j < v.Field(i).Len(); j++ { if err := processField(v.Field(i).Index(j).Interface(), processors); err != nil { return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name) } } // Process each string in string slices. case kind == reflect.String: for _, proc := range processors { _, hasTag := t.Field(i).Tag.Lookup(proc.tag) if !hasTag { continue } for j := 0; j < v.Field(i).Len(); j++ { if err := proc.f(v.Field(i).Index(j)); err != nil { return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name) } } } } // Recursively process struct pointers. case reflect.Ptr: if v.Field(i).Elem().Kind() == reflect.Struct { if err := processField(v.Field(i).Interface(), processors); err != nil { return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name) } } default: field := t.Field(i) for _, proc := range processors { if _, hasTag := field.Tag.Lookup(proc.tag); hasTag { if err := proc.f(v.Field(i)); err != nil { return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name) } } } } } return nil } func hexToBase64Processor(v reflect.Value) error { if v.String() == "0x" { v.SetString("") return nil } b, err := bytesutil.FromHexString(v.String()) if err != nil { return err } v.SetString(base64.StdEncoding.EncodeToString(b)) return nil } func base64ToHexProcessor(v reflect.Value) error { if v.String() == "" { // Empty hex values are represented as "0x". v.SetString("0x") return nil } b, err := base64.StdEncoding.DecodeString(v.String()) if err != nil { return err } v.SetString(hexutil.Encode(b)) return nil } func base64ToChecksumAddressProcessor(v reflect.Value) error { if v.String() == "" { // Empty hex values are represented as "0x". v.SetString("0x") return nil } b, err := base64.StdEncoding.DecodeString(v.String()) if err != nil { return err } v.SetString(common.BytesToAddress(b).Hex()) return nil } func base64ToUint256Processor(v reflect.Value) error { if v.String() == "" { return nil } littleEndian, err := base64.StdEncoding.DecodeString(v.String()) if err != nil { return err } if len(littleEndian) != 32 { return errors.New("invalid length for Uint256") } // Integers are stored as little-endian, but // big.Int expects big-endian. So we need to reverse // the byte order before decoding. var bigEndian [32]byte for i := 0; i < len(littleEndian); i++ { bigEndian[i] = littleEndian[len(littleEndian)-1-i] } var uint256 big.Int uint256.SetBytes(bigEndian[:]) v.SetString(uint256.String()) return nil } func uint256ToBase64Processor(v reflect.Value) error { if v.String() == "" { return nil } uint256, ok := new(big.Int).SetString(v.String(), 10) if !ok { return fmt.Errorf("could not parse Uint256") } bigEndian := uint256.Bytes() if len(bigEndian) > 32 { return fmt.Errorf("number too big for Uint256") } // Integers are stored as little-endian, but // big.Int gives big-endian. So we need to reverse // the byte order before encoding. var littleEndian [32]byte for i := 0; i < len(bigEndian); i++ { littleEndian[i] = bigEndian[len(bigEndian)-1-i] } v.SetString(base64.StdEncoding.EncodeToString(littleEndian[:])) return nil } func enumToLowercaseProcessor(v reflect.Value) error { v.SetString(strings.ToLower(v.String())) return nil } func timeToUnixProcessor(v reflect.Value) error { t, err := time.Parse(time.RFC3339, v.String()) if err != nil { return err } v.SetString(strconv.FormatUint(uint64(t.Unix()), 10)) return nil }