diff --git a/g/internal/structs/structs_tag.go b/g/internal/structs/structs_tag.go index 9040e9560..52167b3cf 100644 --- a/g/internal/structs/structs_tag.go +++ b/g/internal/structs/structs_tag.go @@ -40,7 +40,19 @@ func TagMapField(pointer interface{}, priority []string, recursive bool) map[str if v, ok := pointer.(reflect.Value); ok { fields = structs.Fields(v.Interface()) } else { - fields = structs.Fields(pointer) + rv := reflect.ValueOf(pointer) + kind := rv.Kind() + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + // If pointer is type of **struct and nil, then automatically create a temporary struct, + // which is used for structs.Fields. + if kind == reflect.Ptr && (!rv.IsValid() || rv.IsNil()) { + fields = structs.Fields(reflect.New(rv.Type().Elem()).Elem().Interface()) + } else { + fields = structs.Fields(pointer) + } } tag := "" name := "" diff --git a/g/net/ghttp/ghttp_func.go b/g/net/ghttp/ghttp_func.go index ff09907d2..a4474ed90 100644 --- a/g/net/ghttp/ghttp_func.go +++ b/g/net/ghttp/ghttp_func.go @@ -7,29 +7,32 @@ package ghttp import ( + "strings" + "github.com/gogf/gf/g/encoding/gurl" "github.com/gogf/gf/g/util/gconv" - "strings" ) // 构建请求参数,参数支持任意数据类型,常见参数类型为string/map。 -// 如果参数为map类型,参数值将会进行urlencode编码。 -func BuildParams(params interface{}) (encodedParamStr string) { - m := gconv.Map(params) +// 如果参数为map类型,参数值将会进行urlencode编码;可以通过 noUrlEncode:true 参数取消编码。 +func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr string) { + m, urlEncode := gconv.Map(params), true if len(m) == 0 { return gconv.String(params) } + if len(noUrlEncode) == 1 { + urlEncode = !noUrlEncode[0] + } s := "" for k, v := range m { if len(encodedParamStr) > 0 { encodedParamStr += "&" } s = gconv.String(v) - if len(s) > 6 && strings.Compare(s[0:6], "@file:") == 0 { - encodedParamStr += k + "=" + s - } else { - encodedParamStr += k + "=" + gurl.Encode(s) + if urlEncode && len(s) > 6 && strings.Compare(s[0:6], "@file:") != 0 { + s = gurl.Encode(s) } + encodedParamStr += k + "=" + s } return } diff --git a/g/net/ghttp/ghttp_unit_param_struct_test.go b/g/net/ghttp/ghttp_unit_param_struct_test.go new file mode 100644 index 000000000..382f24642 --- /dev/null +++ b/g/net/ghttp/ghttp_unit_param_struct_test.go @@ -0,0 +1,67 @@ +// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp_test + +import ( + "fmt" + "testing" + "time" + + "github.com/gogf/gf/g/util/gvalid" + + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/test/gtest" +) + +func Test_Params_Struct(t *testing.T) { + type User struct { + Id int + Name string + Pass1 string `params:"password1"` + Pass2 string `params:"password2" gvalid:"passwd1 @required|length:2,20|password3||密码强度不足"` + } + p := ports.PopRand() + s := g.Server(p) + s.BindHandler("/struct1", func(r *ghttp.Request) { + if m := r.GetMap(); len(m) > 0 { + user := new(User) + r.GetToStruct(user) + r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2) + } + }) + s.BindHandler("/struct2", func(r *ghttp.Request) { + if m := r.GetMap(); len(m) > 0 { + user := (*User)(nil) + r.GetToStruct(&user) + r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2) + } + }) + s.BindHandler("/struct-valid", func(r *ghttp.Request) { + if m := r.GetMap(); len(m) > 0 { + user := new(User) + r.GetToStruct(user) + err := gvalid.CheckStruct(user, nil) + r.Response.Write(err.Maps()) + } + }) + s.SetPort(p) + s.SetDumpRouteMap(false) + s.Start() + defer s.Shutdown() + + // 等待启动完成 + time.Sleep(time.Second) + gtest.Case(t, func() { + client := ghttp.NewClient() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + gtest.Assert(client.GetContent("/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`) + gtest.Assert(client.PostContent("/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`) + gtest.Assert(client.PostContent("/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`) + gtest.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `{"passwd1":{"length":"字段长度为2到20个字符","password3":"密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符"}}`) + }) +} diff --git a/g/os/gfcache/gfcache_z_unit_test.go b/g/os/gfcache/gfcache_z_unit_test.go new file mode 100755 index 000000000..b45a1fe13 --- /dev/null +++ b/g/os/gfcache/gfcache_z_unit_test.go @@ -0,0 +1,92 @@ +// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" -benchmem + +package gfcache_test + +import ( + "io/ioutil" + "os" + "testing" + "time" + + "github.com/gogf/gf/g/os/gfcache" + "github.com/gogf/gf/g/os/gfile" + "github.com/gogf/gf/g/test/gtest" +) + +func TestGetContents(t *testing.T) { + gtest.Case(t, func() { + + var f *os.File + var err error + fileName := "test" + strTest := "123" + + if !gfile.Exists(fileName) { + f, err = ioutil.TempFile("", fileName) + if err != nil { + t.Error("create file fail") + } + } + + defer f.Close() + defer os.Remove(f.Name()) + + if gfile.Exists(f.Name()) { + + f, err = gfile.OpenFile(f.Name(), os.O_APPEND|os.O_WRONLY, os.ModeAppend) + if err != nil { + t.Error("file open fail", err) + } + + err = gfile.PutContents(f.Name(), strTest) + if err != nil { + t.Error("write error", err) + } + + cache := gfcache.GetContents(f.Name(), 1) + gtest.Assert(cache, strTest) + } + }) + + gtest.Case(t, func() { + + var f *os.File + var err error + fileName := "test2" + strTest := "123" + + if !gfile.Exists(fileName) { + f, err = ioutil.TempFile("", fileName) + if err != nil { + t.Error("create file fail") + } + } + + defer f.Close() + defer os.Remove(f.Name()) + + if gfile.Exists(f.Name()) { + cache := gfcache.GetContents(f.Name()) + + f, err = gfile.OpenFile(f.Name(), os.O_APPEND|os.O_WRONLY, os.ModeAppend) + if err != nil { + t.Error("file open fail", err) + } + + err = gfile.PutContents(f.Name(), strTest) + if err != nil { + t.Error("write error", err) + } + + gtest.Assert(cache, "") + + time.Sleep(100 * time.Millisecond) + } + }) +} diff --git a/g/util/gvalid/gvalid_check.go b/g/util/gvalid/gvalid_check.go index 5e3460c9d..10a46ee75 100644 --- a/g/util/gvalid/gvalid_check.go +++ b/g/util/gvalid/gvalid_check.go @@ -138,11 +138,11 @@ func Check(value interface{}, rules string, msgs interface{}, params ...interfac for i := 0; ; { array := strings.Split(ruleItems[i], ":") if _, ok := allSupportedRules[array[0]]; !ok { - if i > 0 { + if i > 0 && ruleItems[i-1][:5] == "regex" { ruleItems[i-1] += "|" + ruleItems[i] ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) } else { - return newErrorStr("invalid_rules", "invalid rules:"+rules) + return newErrorStr("parse_error", "invalid rules:"+rules) } } else { i++ diff --git a/g/util/gvalid/gvalid_unit_basic_all_test.go b/g/util/gvalid/gvalid_unit_basic_all_test.go old mode 100644 new mode 100755 index 6d9642b2e..7345e69de --- a/g/util/gvalid/gvalid_unit_basic_all_test.go +++ b/g/util/gvalid/gvalid_unit_basic_all_test.go @@ -7,12 +7,29 @@ package gvalid_test import ( + "testing" + "github.com/gogf/gf/g" "github.com/gogf/gf/g/test/gtest" "github.com/gogf/gf/g/util/gvalid" - "testing" ) +func Test_Check(t *testing.T) { + + gtest.Case(t, func() { + rule := "abc:6,16" + val1 := 0 + val2 := 7 + val3 := 20 + err1 := gvalid.Check(val1, rule, nil) + err2 := gvalid.Check(val2, rule, nil) + err3 := gvalid.Check(val3, rule, nil) + gtest.Assert(err1, "invalid rules:abc:6,16") + gtest.Assert(err2, "invalid rules:abc:6,16") + gtest.Assert(err3, "invalid rules:abc:6,16") + }) +} + func Test_Required(t *testing.T) { if m := gvalid.Check("1", "required", nil); m != nil { t.Error(m) @@ -540,22 +557,44 @@ func Test_Length(t *testing.T) { func Test_MinLength(t *testing.T) { rule := "min-length:6" + msgs := map[string]string{ + "min-length": "地址长度至少为:min位", + } if m := gvalid.Check("123456", rule, nil); m != nil { t.Error(m) } if m := gvalid.Check("12345", rule, nil); m == nil { t.Error("长度校验失败") } + if m := gvalid.Check("12345", rule, msgs); m == nil { + t.Error("长度校验失败") + } + + rule2 := "min-length:abc" + if m := gvalid.Check("123456", rule2, nil); m == nil { + t.Error("长度校验失败") + } } func Test_MaxLength(t *testing.T) { rule := "max-length:6" + msgs := map[string]string{ + "max-length": "地址长度至大为:max位", + } if m := gvalid.Check("12345", rule, nil); m != nil { t.Error(m) } if m := gvalid.Check("1234567", rule, nil); m == nil { t.Error("长度校验失败") } + if m := gvalid.Check("1234567", rule, msgs); m == nil { + t.Error("长度校验失败") + } + + rule2 := "max-length:abc" + if m := gvalid.Check("123456", rule2, nil); m == nil { + t.Error("长度校验失败") + } } func Test_Between(t *testing.T) { @@ -566,6 +605,9 @@ func Test_Between(t *testing.T) { if m := gvalid.Check(10.02, rule, nil); m == nil { t.Error("大小范围校验失败") } + if m := gvalid.Check("a", rule, nil); m == nil { + t.Error("大小范围校验失败") + } } func Test_Min(t *testing.T) { @@ -575,14 +617,21 @@ func Test_Min(t *testing.T) { val2 := "99" val3 := "100" val4 := "1000" + val5 := "a" err1 := gvalid.Check(val1, rule, nil) err2 := gvalid.Check(val2, rule, nil) err3 := gvalid.Check(val3, rule, nil) err4 := gvalid.Check(val4, rule, nil) + err5 := gvalid.Check(val5, rule, nil) gtest.AssertNE(err1, nil) gtest.AssertNE(err2, nil) gtest.Assert(err3, nil) gtest.Assert(err4, nil) + gtest.AssertNE(err5, nil) + + rule2 := "min:a" + err6 := gvalid.Check(val1, rule2, nil) + gtest.AssertNE(err6, nil) }) } @@ -593,14 +642,21 @@ func Test_Max(t *testing.T) { val2 := "99" val3 := "100" val4 := "1000" + val5 := "a" err1 := gvalid.Check(val1, rule, nil) err2 := gvalid.Check(val2, rule, nil) err3 := gvalid.Check(val3, rule, nil) err4 := gvalid.Check(val4, rule, nil) + err5 := gvalid.Check(val5, rule, nil) gtest.Assert(err1, nil) gtest.Assert(err2, nil) gtest.Assert(err3, nil) gtest.AssertNE(err4, nil) + gtest.AssertNE(err5, nil) + + rule2 := "max:a" + err6 := gvalid.Check(val1, rule2, nil) + gtest.AssertNE(err6, nil) }) } diff --git a/g/util/gvalid/gvalid_unit_checkmap_test.go b/g/util/gvalid/gvalid_unit_checkmap_test.go old mode 100644 new mode 100755 index 76a75dfb8..05b519d94 --- a/g/util/gvalid/gvalid_unit_checkmap_test.go +++ b/g/util/gvalid/gvalid_unit_checkmap_test.go @@ -7,12 +7,19 @@ package gvalid_test import ( + "testing" + "github.com/gogf/gf/g/test/gtest" "github.com/gogf/gf/g/util/gvalid" - "testing" ) func Test_CheckMap(t *testing.T) { + + var params interface{} + if m := gvalid.CheckMap(params, nil, nil); m == nil { + t.Error("CheckMap校验失败") + } + kvmap := map[string]interface{}{ "id": "0", "name": "john", @@ -50,6 +57,82 @@ func Test_CheckMap(t *testing.T) { if m := gvalid.CheckMap(kvmap, rules, msgs); m != nil { t.Error(m) } + + kvmap = map[string]interface{}{ + "id": "1", + "name": "john", + } + rules = map[string]string{ + "id": "", + "name": "", + } + msgs = map[string]interface{}{ + "id": "ID不能为空|ID范围应当为:min到:max", + "name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为:min到:max个字符", + }, + } + if m := gvalid.CheckMap(kvmap, rules, msgs); m != nil { + t.Error(m) + } + + kvmap = map[string]interface{}{ + "id": "1", + "name": "john", + } + rules2 := []string{ + "@required|between:1,100", + "@required|length:4,16", + } + msgs = map[string]interface{}{ + "id": "ID不能为空|ID范围应当为:min到:max", + "name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为:min到:max个字符", + }, + } + if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + t.Error(m) + } + + kvmap = map[string]interface{}{ + "id": "1", + "name": "john", + } + rules2 = []string{ + "id@required|between:1,100", + "name@required|length:4,16#名称不能为空|", + } + msgs = map[string]interface{}{ + "id": "ID不能为空|ID范围应当为:min到:max", + "name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为:min到:max个字符", + }, + } + if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + t.Error(m) + } + + kvmap = map[string]interface{}{ + "id": "1", + "name": "john", + } + rules2 = []string{ + "id@required|between:1,100", + "name@required|length:4,16#名称不能为空", + } + msgs = map[string]interface{}{ + "id": "ID不能为空|ID范围应当为:min到:max", + "name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为:min到:max个字符", + }, + } + if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + t.Error(m) + } } // 如果值为nil,并且不需要require*验证时,其他验证失效 diff --git a/g/util/gvalid/gvalid_unit_checkstruct_test.go b/g/util/gvalid/gvalid_unit_checkstruct_test.go old mode 100644 new mode 100755 index b13d508dc..a02fc555b --- a/g/util/gvalid/gvalid_unit_checkstruct_test.go +++ b/g/util/gvalid/gvalid_unit_checkstruct_test.go @@ -16,6 +16,77 @@ import ( ) func Test_CheckStruct(t *testing.T) { + gtest.Case(t, func() { + type Object struct { + Name string + Age int + } + rules := []string{ + "@required|length:6,16", + "@between:18,30", + } + msgs := map[string]interface{}{ + "Name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为:min到:max个字符", + }, + "Age": "年龄为18到30周岁", + } + obj := &Object{"john", 16} + err := gvalid.CheckStruct(obj, rules, msgs) + gtest.Assert(err, nil) + }) + + gtest.Case(t, func() { + type Object struct { + Name string + Age int + } + rules := []string{ + "Name@required|length:6,16#名称不能为空", + "Age@between:18,30", + } + msgs := map[string]interface{}{ + "Name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为:min到:max个字符", + }, + "Age": "年龄为18到30周岁", + } + obj := &Object{"john", 16} + err := gvalid.CheckStruct(obj, rules, msgs) + gtest.AssertNE(err, nil) + gtest.Assert(len(err.Maps()), 2) + gtest.Assert(err.Maps()["Name"]["required"], "") + gtest.Assert(err.Maps()["Name"]["length"], "名称长度为6到16个字符") + gtest.Assert(err.Maps()["Age"]["between"], "年龄为18到30周岁") + }) + + gtest.Case(t, func() { + type Object struct { + Name string + Age int + } + rules := []string{ + "Name@required|length:6,16#名称不能为空|", + "Age@between:18,30", + } + msgs := map[string]interface{}{ + "Name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为:min到:max个字符", + }, + "Age": "年龄为18到30周岁", + } + obj := &Object{"john", 16} + err := gvalid.CheckStruct(obj, rules, msgs) + gtest.AssertNE(err, nil) + gtest.Assert(len(err.Maps()), 2) + gtest.Assert(err.Maps()["Name"]["required"], "") + gtest.Assert(err.Maps()["Name"]["length"], "名称长度为6到16个字符") + gtest.Assert(err.Maps()["Age"]["between"], "年龄为18到30周岁") + }) + gtest.Case(t, func() { type Object struct { Name string @@ -54,6 +125,27 @@ func Test_CheckStruct(t *testing.T) { gtest.Assert(err.Maps()["password"]["required"], "登录密码不能为空") }) + gtest.Case(t, func() { + type LoginRequest struct { + Username string `json:"username" gvalid:"@required#用户名不能为空"` + Password string `json:"password" gvalid:"@required#登录密码不能为空"` + } + var login LoginRequest + err := gvalid.CheckStruct(login, nil) + gtest.Assert(err, nil) + }) + + gtest.Case(t, func() { + type LoginRequest struct { + username string `json:"username" gvalid:"username@required#用户名不能为空"` + Password string `json:"password" gvalid:"password@required#登录密码不能为空"` + } + var login LoginRequest + err := gvalid.CheckStruct(login, nil) + gtest.AssertNE(err, nil) + gtest.Assert(err.Maps()["password"]["required"], "登录密码不能为空") + }) + // gvalid tag gtest.Case(t, func() { type User struct { @@ -73,6 +165,46 @@ func Test_CheckStruct(t *testing.T) { gtest.Assert(err.Maps()["uid"]["min"], "ID不能为空") }) + gtest.Case(t, func() { + type User struct { + Id int `gvalid:"uid@required|min:10#|ID不能为空"` + Age int `gvalid:"age@required#年龄不能为空"` + Username string `json:"username" gvalid:"username@required#用户名不能为空"` + Password string `json:"password" gvalid:"password@required#登录密码不能为空"` + } + user := &User{ + Id: 1, + Username: "john", + Password: "123456", + } + + rules := []string{ + "username@required#用户名不能为空", + } + + err := gvalid.CheckStruct(user, rules) + gtest.AssertNE(err, nil) + gtest.Assert(len(err.Maps()), 1) + gtest.Assert(err.Maps()["uid"]["min"], "ID不能为空") + }) + + gtest.Case(t, func() { + type User struct { + Id int `gvalid:"uid@required|min:10#ID不能为空"` + Age int `gvalid:"age@required#年龄不能为空"` + Username string `json:"username" gvalid:"username@required#用户名不能为空"` + Password string `json:"password" gvalid:"password@required#登录密码不能为空"` + } + user := &User{ + Id: 1, + Username: "john", + Password: "123456", + } + err := gvalid.CheckStruct(user, nil) + gtest.AssertNE(err, nil) + gtest.Assert(len(err.Maps()), 1) + }) + // valid tag gtest.Case(t, func() { type User struct { diff --git a/g/util/gvalid/gvalid_unit_customerror_test.go b/g/util/gvalid/gvalid_unit_customerror_test.go old mode 100644 new mode 100755 index e6c7bf3fc..c7cea0db8 --- a/g/util/gvalid/gvalid_unit_customerror_test.go +++ b/g/util/gvalid/gvalid_unit_customerror_test.go @@ -7,11 +7,33 @@ package gvalid_test import ( - "github.com/gogf/gf/g/util/gvalid" "strings" "testing" + + "github.com/gogf/gf/g/test/gtest" + "github.com/gogf/gf/g/util/gvalid" ) +func Test_Map(t *testing.T) { + rule := "ipv4" + val := "0.0.0" + msg := map[string]string{ + "ipv4": "IPv4地址格式不正确", + } + + err := gvalid.Check(val, rule, nil) + gtest.Assert(err.Map(), msg) +} + +func Test_FirstString(t *testing.T) { + rule := "ipv4" + val := "0.0.0" + + err := gvalid.Check(val, rule, nil) + n := err.FirstString() + gtest.Assert(n, "IPv4地址格式不正确") +} + func Test_SetDefaultErrorMsgs(t *testing.T) { rule := "integer|length:6,16" msgs := map[string]string{ diff --git a/geg/util/gvalid/gvalid_struct3.go b/geg/util/gvalid/gvalid_struct3.go index cfe55a33e..7b0b9e356 100644 --- a/geg/util/gvalid/gvalid_struct3.go +++ b/geg/util/gvalid/gvalid_struct3.go @@ -8,14 +8,12 @@ import ( // same校验 func main() { type User struct { - Password string `gvalid:"password@password"` - ConfirmPassword string `gvalid:"confirm_password@password|same:password#|密码与确认密码不一致"` + Pass string `gvalid:"passwd1 @required|length:2,20|password3||密码强度不足"` } user := &User{ - Password: "123456", - ConfirmPassword: "", + Pass: "1", } - g.Dump(gvalid.CheckStruct(user, nil)) + g.Dump(gvalid.CheckStruct(user, nil).Maps()) }