util/reflect: add StructURLValues func

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2021-04-01 00:30:15 +03:00
parent eba586a329
commit a06f535303
2 changed files with 97 additions and 0 deletions

View File

@ -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 // URLMap returns map of url query params
func URLMap(query string) (map[string]interface{}, error) { func URLMap(query string) (map[string]interface{}, error) {
var ( var (
@ -624,3 +697,9 @@ func mergeSliceIface(a []interface{}, b []interface{}) []interface{} {
a = append(a, b...) a = append(a, b...)
return a return a
} }
type tag struct {
key string
name string
opts []string
}

View File

@ -5,6 +5,24 @@ import (
"testing" "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) { func TestURLSliceVars(t *testing.T) {
u, err := url.Parse("http://localhost/v1/test/call/my_name?key=arg1&key=arg2&key=arg3") u, err := url.Parse("http://localhost/v1/test/call/my_name?key=arg1&key=arg2&key=arg3")
if err != nil { if err != nil {