From a06f5353030563395f4b779c89c086f83124dac7 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Thu, 1 Apr 2021 00:30:15 +0300 Subject: [PATCH] util/reflect: add StructURLValues func Signed-off-by: Vasiliy Tolstov --- util/reflect/reflect.go | 79 ++++++++++++++++++++++++++++++++++++ util/reflect/reflect_test.go | 18 ++++++++ 2 files changed, 97 insertions(+) diff --git a/util/reflect/reflect.go b/util/reflect/reflect.go index 06cf1087..05659e82 100644 --- a/util/reflect/reflect.go +++ b/util/reflect/reflect.go @@ -173,6 +173,79 @@ func CopyFrom(a, b interface{}) { } } +func StructURLValues(src interface{}, pref string, tags []string) (url.Values, error) { + data := url.Values{} + + sv := reflect.ValueOf(src) + if sv.Kind() == reflect.Ptr { + sv = sv.Elem() + } + if sv.Kind() != reflect.Struct { + return nil, ErrInvalidStruct + } + + typ := sv.Type() + for idx := 0; idx < typ.NumField(); idx++ { + fld := typ.Field(idx) + val := sv.Field(idx) + if !val.CanSet() || len(fld.PkgPath) != 0 || !val.IsValid() { + continue + } + + var t *tag + for _, tn := range tags { + ts, ok := fld.Tag.Lookup(tn) + if !ok { + continue + } + + tp := strings.Split(ts, ",") + // special + switch tn { + case "protobuf": // special + t = &tag{key: tn, name: tp[3][5:], opts: append(tp[:3], tp[4:]...)} + default: + t = &tag{key: tn, name: tp[0], opts: tp[1:]} + } + if t.name != "" { + break + } + } + + if t.name == "" { + // fallback to lowercase + t.name = strings.ToLower(fld.Name) + } + if pref != "" { + t.name = pref + "." + t.name + } + + switch val.Kind() { + case reflect.Struct, reflect.Ptr: + if val.IsNil() { + continue + } + ndata, err := StructURLValues(val.Interface(), t.name, tags) + if err != nil { + return ndata, err + } + for k, v := range ndata { + data[k] = v + } + default: + switch val.Kind() { + case reflect.Slice: + for i := 0; i < val.Len(); i++ { + data.Add(t.name, fmt.Sprintf("%v", val.Index(i))) + } + default: + data.Set(t.name, fmt.Sprintf("%v", val)) + } + } + } + return data, nil +} + // URLMap returns map of url query params func URLMap(query string) (map[string]interface{}, error) { var ( @@ -624,3 +697,9 @@ func mergeSliceIface(a []interface{}, b []interface{}) []interface{} { a = append(a, b...) return a } + +type tag struct { + key string + name string + opts []string +} diff --git a/util/reflect/reflect_test.go b/util/reflect/reflect_test.go index 7b3e95ae..745943de 100644 --- a/util/reflect/reflect_test.go +++ b/util/reflect/reflect_test.go @@ -5,6 +5,24 @@ import ( "testing" ) +func TestStructURLValues(t *testing.T) { + type Str struct { + Name string `json:"name"` + Args []int `json:"args"` + Str *Str `json:"str"` + } + + val := &Str{Name: "test_name", Args: []int{1, 2, 3}, Str: &Str{Name: "nested_name"}} + data, err := StructURLValues(val, "", []string{"json"}) + if err != nil { + t.Fatal(err) + } + + if data.Get("name") != "test_name" { + t.Fatalf("invalid data: %v", data) + } +} + func TestURLSliceVars(t *testing.T) { u, err := url.Parse("http://localhost/v1/test/call/my_name?key=arg1&key=arg2&key=arg3") if err != nil {