From cbd51616f17361706060c8e7d1dab4265e519d8c Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Wed, 2 Sep 2020 23:23:48 +0800 Subject: [PATCH] adapter: validation module --- pkg/adapter/validation/util.go | 62 +++ pkg/adapter/validation/validation.go | 274 ++++++++++ pkg/adapter/validation/validation_test.go | 609 ++++++++++++++++++++++ pkg/adapter/validation/validators.go | 512 ++++++++++++++++++ 4 files changed, 1457 insertions(+) create mode 100644 pkg/adapter/validation/util.go create mode 100644 pkg/adapter/validation/validation.go create mode 100644 pkg/adapter/validation/validation_test.go create mode 100644 pkg/adapter/validation/validators.go diff --git a/pkg/adapter/validation/util.go b/pkg/adapter/validation/util.go new file mode 100644 index 00000000..729712e0 --- /dev/null +++ b/pkg/adapter/validation/util.go @@ -0,0 +1,62 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validation + +import ( + "reflect" + + "github.com/astaxie/beego/pkg/infrastructure/validation" +) + +const ( + // ValidTag struct tag + ValidTag = validation.ValidTag + + LabelTag = validation.LabelTag +) + +var ( + ErrInt64On32 = validation.ErrInt64On32 +) + +// CustomFunc is for custom validate function +type CustomFunc func(v *Validation, obj interface{}, key string) + +// AddCustomFunc Add a custom function to validation +// The name can not be: +// Clear +// HasErrors +// ErrorMap +// Error +// Check +// Valid +// NoMatch +// If the name is same with exists function, it will replace the origin valid function +func AddCustomFunc(name string, f CustomFunc) error { + return validation.AddCustomFunc(name, func(v *validation.Validation, obj interface{}, key string) { + f((*Validation)(v), obj, key) + }) +} + +// ValidFunc Valid function type +type ValidFunc validation.ValidFunc + +// Funcs Validate function map +type Funcs validation.Funcs + +// Call validate values with named type string +func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) { + return (validation.Funcs(f)).Call(name, params...) +} diff --git a/pkg/adapter/validation/validation.go b/pkg/adapter/validation/validation.go new file mode 100644 index 00000000..1cdb8dda --- /dev/null +++ b/pkg/adapter/validation/validation.go @@ -0,0 +1,274 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package validation for validations +// +// import ( +// "github.com/astaxie/beego/validation" +// "log" +// ) +// +// type User struct { +// Name string +// Age int +// } +// +// func main() { +// u := User{"man", 40} +// valid := validation.Validation{} +// valid.Required(u.Name, "name") +// valid.MaxSize(u.Name, 15, "nameMax") +// valid.Range(u.Age, 0, 140, "age") +// if valid.HasErrors() { +// // validation does not pass +// // print invalid message +// for _, err := range valid.Errors { +// log.Println(err.Key, err.Message) +// } +// } +// // or use like this +// if v := valid.Max(u.Age, 140, "ageMax"); !v.Ok { +// log.Println(v.Error.Key, v.Error.Message) +// } +// } +// +// more info: http://beego.me/docs/mvc/controller/validation.md +package validation + +import ( + "fmt" + "regexp" + + "github.com/astaxie/beego/pkg/infrastructure/validation" +) + +// ValidFormer valid interface +type ValidFormer interface { + Valid(*Validation) +} + +// Error show the error +type Error validation.Error + +// String Returns the Message. +func (e *Error) String() string { + if e == nil { + return "" + } + return e.Message +} + +// Implement Error interface. +// Return e.String() +func (e *Error) Error() string { return e.String() } + +// Result is returned from every validation method. +// It provides an indication of success, and a pointer to the Error (if any). +type Result validation.Result + +// Key Get Result by given key string. +func (r *Result) Key(key string) *Result { + if r.Error != nil { + r.Error.Key = key + } + return r +} + +// Message Set Result message by string or format string with args +func (r *Result) Message(message string, args ...interface{}) *Result { + if r.Error != nil { + if len(args) == 0 { + r.Error.Message = message + } else { + r.Error.Message = fmt.Sprintf(message, args...) + } + } + return r +} + +// A Validation context manages data validation and error messages. +type Validation validation.Validation + +// Clear Clean all ValidationError. +func (v *Validation) Clear() { + (*validation.Validation)(v).Clear() +} + +// HasErrors Has ValidationError nor not. +func (v *Validation) HasErrors() bool { + return (*validation.Validation)(v).HasErrors() +} + +// ErrorMap Return the errors mapped by key. +// If there are multiple validation errors associated with a single key, the +// first one "wins". (Typically the first validation will be the more basic). +func (v *Validation) ErrorMap() map[string][]*Error { + newErrors := (*validation.Validation)(v).ErrorMap() + res := make(map[string][]*Error, len(newErrors)) + for n, es := range newErrors { + errs := make([]*Error, 0, len(es)) + + for _, e := range es { + errs = append(errs, (*Error)(e)) + } + + res[n] = errs + } + return res +} + +// Error Add an error to the validation context. +func (v *Validation) Error(message string, args ...interface{}) *Result { + return (*Result)((*validation.Validation)(v).Error(message, args...)) +} + +// Required Test that the argument is non-nil and non-empty (if string or list) +func (v *Validation) Required(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Required(obj, key)) +} + +// Min Test that the obj is greater than min if obj's type is int +func (v *Validation) Min(obj interface{}, min int, key string) *Result { + return (*Result)((*validation.Validation)(v).Min(obj, min, key)) +} + +// Max Test that the obj is less than max if obj's type is int +func (v *Validation) Max(obj interface{}, max int, key string) *Result { + return (*Result)((*validation.Validation)(v).Max(obj, max, key)) +} + +// Range Test that the obj is between mni and max if obj's type is int +func (v *Validation) Range(obj interface{}, min, max int, key string) *Result { + return (*Result)((*validation.Validation)(v).Range(obj, min, max, key)) +} + +// MinSize Test that the obj is longer than min size if type is string or slice +func (v *Validation) MinSize(obj interface{}, min int, key string) *Result { + return (*Result)((*validation.Validation)(v).MinSize(obj, min, key)) +} + +// MaxSize Test that the obj is shorter than max size if type is string or slice +func (v *Validation) MaxSize(obj interface{}, max int, key string) *Result { + return (*Result)((*validation.Validation)(v).MaxSize(obj, max, key)) +} + +// Length Test that the obj is same length to n if type is string or slice +func (v *Validation) Length(obj interface{}, n int, key string) *Result { + return (*Result)((*validation.Validation)(v).Length(obj, n, key)) +} + +// Alpha Test that the obj is [a-zA-Z] if type is string +func (v *Validation) Alpha(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Alpha(obj, key)) +} + +// Numeric Test that the obj is [0-9] if type is string +func (v *Validation) Numeric(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Numeric(obj, key)) +} + +// AlphaNumeric Test that the obj is [0-9a-zA-Z] if type is string +func (v *Validation) AlphaNumeric(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).AlphaNumeric(obj, key)) +} + +// Match Test that the obj matches regexp if type is string +func (v *Validation) Match(obj interface{}, regex *regexp.Regexp, key string) *Result { + return (*Result)((*validation.Validation)(v).Match(obj, regex, key)) +} + +// NoMatch Test that the obj doesn't match regexp if type is string +func (v *Validation) NoMatch(obj interface{}, regex *regexp.Regexp, key string) *Result { + return (*Result)((*validation.Validation)(v).NoMatch(obj, regex, key)) +} + +// AlphaDash Test that the obj is [0-9a-zA-Z_-] if type is string +func (v *Validation) AlphaDash(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).AlphaDash(obj, key)) +} + +// Email Test that the obj is email address if type is string +func (v *Validation) Email(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Email(obj, key)) +} + +// IP Test that the obj is IP address if type is string +func (v *Validation) IP(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).IP(obj, key)) +} + +// Base64 Test that the obj is base64 encoded if type is string +func (v *Validation) Base64(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Base64(obj, key)) +} + +// Mobile Test that the obj is chinese mobile number if type is string +func (v *Validation) Mobile(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Mobile(obj, key)) +} + +// Tel Test that the obj is chinese telephone number if type is string +func (v *Validation) Tel(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Tel(obj, key)) +} + +// Phone Test that the obj is chinese mobile or telephone number if type is string +func (v *Validation) Phone(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Phone(obj, key)) +} + +// ZipCode Test that the obj is chinese zip code if type is string +func (v *Validation) ZipCode(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).ZipCode(obj, key)) +} + +// key must like aa.bb.cc or aa.bb. +// AddError adds independent error message for the provided key +func (v *Validation) AddError(key, message string) { + (*validation.Validation)(v).AddError(key, message) +} + +// SetError Set error message for one field in ValidationError +func (v *Validation) SetError(fieldName string, errMsg string) *Error { + return (*Error)((*validation.Validation)(v).SetError(fieldName, errMsg)) +} + +// Check Apply a group of validators to a field, in order, and return the +// ValidationResult from the first one that fails, or the last one that +// succeeds. +func (v *Validation) Check(obj interface{}, checks ...Validator) *Result { + vldts := make([]validation.Validator, 0, len(checks)) + for _, v := range checks { + vldts = append(vldts, validation.Validator(v)) + } + return (*Result)((*validation.Validation)(v).Check(obj, vldts...)) +} + +// Valid Validate a struct. +// the obj parameter must be a struct or a struct pointer +func (v *Validation) Valid(obj interface{}) (b bool, err error) { + return (*validation.Validation)(v).Valid(obj) +} + +// RecursiveValid Recursively validate a struct. +// Step1: Validate by v.Valid +// Step2: If pass on step1, then reflect obj's fields +// Step3: Do the Recursively validation to all struct or struct pointer fields +func (v *Validation) RecursiveValid(objc interface{}) (bool, error) { + return (*validation.Validation)(v).RecursiveValid(objc) +} + +func (v *Validation) CanSkipAlso(skipFunc string) { + (*validation.Validation)(v).CanSkipAlso(skipFunc) +} diff --git a/pkg/adapter/validation/validation_test.go b/pkg/adapter/validation/validation_test.go new file mode 100644 index 00000000..b4b5b1b6 --- /dev/null +++ b/pkg/adapter/validation/validation_test.go @@ -0,0 +1,609 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validation + +import ( + "regexp" + "testing" + "time" +) + +func TestRequired(t *testing.T) { + valid := Validation{} + + if valid.Required(nil, "nil").Ok { + t.Error("nil object should be false") + } + if !valid.Required(true, "bool").Ok { + t.Error("Bool value should always return true") + } + if !valid.Required(false, "bool").Ok { + t.Error("Bool value should always return true") + } + if valid.Required("", "string").Ok { + t.Error("\"'\" string should be false") + } + if valid.Required(" ", "string").Ok { + t.Error("\" \" string should be false") // For #2361 + } + if valid.Required("\n", "string").Ok { + t.Error("new line string should be false") // For #2361 + } + if !valid.Required("astaxie", "string").Ok { + t.Error("string should be true") + } + if valid.Required(0, "zero").Ok { + t.Error("Integer should not be equal 0") + } + if !valid.Required(1, "int").Ok { + t.Error("Integer except 0 should be true") + } + if !valid.Required(time.Now(), "time").Ok { + t.Error("time should be true") + } + if valid.Required([]string{}, "emptySlice").Ok { + t.Error("empty slice should be false") + } + if !valid.Required([]interface{}{"ok"}, "slice").Ok { + t.Error("slice should be true") + } +} + +func TestMin(t *testing.T) { + valid := Validation{} + + if valid.Min(-1, 0, "min0").Ok { + t.Error("-1 is less than the minimum value of 0 should be false") + } + if !valid.Min(1, 0, "min0").Ok { + t.Error("1 is greater or equal than the minimum value of 0 should be true") + } +} + +func TestMax(t *testing.T) { + valid := Validation{} + + if valid.Max(1, 0, "max0").Ok { + t.Error("1 is greater than the minimum value of 0 should be false") + } + if !valid.Max(-1, 0, "max0").Ok { + t.Error("-1 is less or equal than the maximum value of 0 should be true") + } +} + +func TestRange(t *testing.T) { + valid := Validation{} + + if valid.Range(-1, 0, 1, "range0_1").Ok { + t.Error("-1 is between 0 and 1 should be false") + } + if !valid.Range(1, 0, 1, "range0_1").Ok { + t.Error("1 is between 0 and 1 should be true") + } +} + +func TestMinSize(t *testing.T) { + valid := Validation{} + + if valid.MinSize("", 1, "minSize1").Ok { + t.Error("the length of \"\" is less than the minimum value of 1 should be false") + } + if !valid.MinSize("ok", 1, "minSize1").Ok { + t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true") + } + if valid.MinSize([]string{}, 1, "minSize1").Ok { + t.Error("the length of empty slice is less than the minimum value of 1 should be false") + } + if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok { + t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true") + } +} + +func TestMaxSize(t *testing.T) { + valid := Validation{} + + if valid.MaxSize("ok", 1, "maxSize1").Ok { + t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false") + } + if !valid.MaxSize("", 1, "maxSize1").Ok { + t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true") + } + if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok { + t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false") + } + if !valid.MaxSize([]string{}, 1, "maxSize1").Ok { + t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true") + } +} + +func TestLength(t *testing.T) { + valid := Validation{} + + if valid.Length("", 1, "length1").Ok { + t.Error("the length of \"\" must equal 1 should be false") + } + if !valid.Length("1", 1, "length1").Ok { + t.Error("the length of \"1\" must equal 1 should be true") + } + if valid.Length([]string{}, 1, "length1").Ok { + t.Error("the length of empty slice must equal 1 should be false") + } + if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok { + t.Error("the length of [\"ok\"] must equal 1 should be true") + } +} + +func TestAlpha(t *testing.T) { + valid := Validation{} + + if valid.Alpha("a,1-@ $", "alpha").Ok { + t.Error("\"a,1-@ $\" are valid alpha characters should be false") + } + if !valid.Alpha("abCD", "alpha").Ok { + t.Error("\"abCD\" are valid alpha characters should be true") + } +} + +func TestNumeric(t *testing.T) { + valid := Validation{} + + if valid.Numeric("a,1-@ $", "numeric").Ok { + t.Error("\"a,1-@ $\" are valid numeric characters should be false") + } + if !valid.Numeric("1234", "numeric").Ok { + t.Error("\"1234\" are valid numeric characters should be true") + } +} + +func TestAlphaNumeric(t *testing.T) { + valid := Validation{} + + if valid.AlphaNumeric("a,1-@ $", "alphaNumeric").Ok { + t.Error("\"a,1-@ $\" are valid alpha or numeric characters should be false") + } + if !valid.AlphaNumeric("1234aB", "alphaNumeric").Ok { + t.Error("\"1234aB\" are valid alpha or numeric characters should be true") + } +} + +func TestMatch(t *testing.T) { + valid := Validation{} + + if valid.Match("suchuangji@gmail", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok { + t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false") + } + if !valid.Match("suchuangji@gmail.com", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok { + t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true") + } +} + +func TestNoMatch(t *testing.T) { + valid := Validation{} + + if valid.NoMatch("123@gmail", regexp.MustCompile(`[^\w\d]`), "nomatch").Ok { + t.Error("\"123@gmail\" not match \"[^\\w\\d]\" should be false") + } + if !valid.NoMatch("123gmail", regexp.MustCompile(`[^\w\d]`), "match").Ok { + t.Error("\"123@gmail\" not match \"[^\\w\\d@]\" should be true") + } +} + +func TestAlphaDash(t *testing.T) { + valid := Validation{} + + if valid.AlphaDash("a,1-@ $", "alphaDash").Ok { + t.Error("\"a,1-@ $\" are valid alpha or numeric or dash(-_) characters should be false") + } + if !valid.AlphaDash("1234aB-_", "alphaDash").Ok { + t.Error("\"1234aB\" are valid alpha or numeric or dash(-_) characters should be true") + } +} + +func TestEmail(t *testing.T) { + valid := Validation{} + + if valid.Email("not@a email", "email").Ok { + t.Error("\"not@a email\" is a valid email address should be false") + } + if !valid.Email("suchuangji@gmail.com", "email").Ok { + t.Error("\"suchuangji@gmail.com\" is a valid email address should be true") + } + if valid.Email("@suchuangji@gmail.com", "email").Ok { + t.Error("\"@suchuangji@gmail.com\" is a valid email address should be false") + } + if valid.Email("suchuangji@gmail.com ok", "email").Ok { + t.Error("\"suchuangji@gmail.com ok\" is a valid email address should be false") + } +} + +func TestIP(t *testing.T) { + valid := Validation{} + + if valid.IP("11.255.255.256", "IP").Ok { + t.Error("\"11.255.255.256\" is a valid ip address should be false") + } + if !valid.IP("01.11.11.11", "IP").Ok { + t.Error("\"suchuangji@gmail.com\" is a valid ip address should be true") + } +} + +func TestBase64(t *testing.T) { + valid := Validation{} + + if valid.Base64("suchuangji@gmail.com", "base64").Ok { + t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false") + } + if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok { + t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true") + } +} + +func TestMobile(t *testing.T) { + valid := Validation{} + + validMobiles := []string{ + "19800008888", + "18800008888", + "18000008888", + "8618300008888", + "+8614700008888", + "17300008888", + "+8617100008888", + "8617500008888", + "8617400008888", + "16200008888", + "16500008888", + "16600008888", + "16700008888", + "13300008888", + "14900008888", + "15300008888", + "17300008888", + "17700008888", + "18000008888", + "18900008888", + "19100008888", + "19900008888", + "19300008888", + "13000008888", + "13100008888", + "13200008888", + "14500008888", + "15500008888", + "15600008888", + "16600008888", + "17100008888", + "17500008888", + "17600008888", + "18500008888", + "18600008888", + "13400008888", + "13500008888", + "13600008888", + "13700008888", + "13800008888", + "13900008888", + "14700008888", + "15000008888", + "15100008888", + "15200008888", + "15800008888", + "15900008888", + "17200008888", + "17800008888", + "18200008888", + "18300008888", + "18400008888", + "18700008888", + "18800008888", + "19800008888", + } + + for _, m := range validMobiles { + if !valid.Mobile(m, "mobile").Ok { + t.Error(m + " is a valid mobile phone number should be true") + } + } +} + +func TestTel(t *testing.T) { + valid := Validation{} + + if valid.Tel("222-00008888", "telephone").Ok { + t.Error("\"222-00008888\" is a valid telephone number should be false") + } + if !valid.Tel("022-70008888", "telephone").Ok { + t.Error("\"022-70008888\" is a valid telephone number should be true") + } + if !valid.Tel("02270008888", "telephone").Ok { + t.Error("\"02270008888\" is a valid telephone number should be true") + } + if !valid.Tel("70008888", "telephone").Ok { + t.Error("\"70008888\" is a valid telephone number should be true") + } +} + +func TestPhone(t *testing.T) { + valid := Validation{} + + if valid.Phone("222-00008888", "phone").Ok { + t.Error("\"222-00008888\" is a valid phone number should be false") + } + if !valid.Mobile("+8614700008888", "phone").Ok { + t.Error("\"+8614700008888\" is a valid phone number should be true") + } + if !valid.Tel("02270008888", "phone").Ok { + t.Error("\"02270008888\" is a valid phone number should be true") + } +} + +func TestZipCode(t *testing.T) { + valid := Validation{} + + if valid.ZipCode("", "zipcode").Ok { + t.Error("\"00008888\" is a valid zipcode should be false") + } + if !valid.ZipCode("536000", "zipcode").Ok { + t.Error("\"536000\" is a valid zipcode should be true") + } +} + +func TestValid(t *testing.T) { + type user struct { + ID int + Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` + Age int `valid:"Required;Range(1, 140)"` + } + valid := Validation{} + + u := user{Name: "test@/test/;com", Age: 40} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Error("validation should be passed") + } + + uptr := &user{Name: "test", Age: 40} + valid.Clear() + b, err = valid.Valid(uptr) + if err != nil { + t.Fatal(err) + } + if b { + t.Error("validation should not be passed") + } + if len(valid.Errors) != 1 { + t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) + } + if valid.Errors[0].Key != "Name.Match" { + t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key) + } + + u = user{Name: "test@/test/;com", Age: 180} + valid.Clear() + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Error("validation should not be passed") + } + if len(valid.Errors) != 1 { + t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) + } + if valid.Errors[0].Key != "Age.Range." { + t.Errorf("Message key should be `Age.Range` but got %s", valid.Errors[0].Key) + } +} + +func TestRecursiveValid(t *testing.T) { + type User struct { + ID int + Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` + Age int `valid:"Required;Range(1, 140)"` + } + + type AnonymouseUser struct { + ID2 int + Name2 string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` + Age2 int `valid:"Required;Range(1, 140)"` + } + + type Account struct { + Password string `valid:"Required"` + U User + AnonymouseUser + } + valid := Validation{} + + u := Account{Password: "abc123_", U: User{}} + b, err := valid.RecursiveValid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Error("validation should not be passed") + } +} + +func TestSkipValid(t *testing.T) { + type User struct { + ID int + + Email string `valid:"Email"` + ReqEmail string `valid:"Required;Email"` + + IP string `valid:"IP"` + ReqIP string `valid:"Required;IP"` + + Mobile string `valid:"Mobile"` + ReqMobile string `valid:"Required;Mobile"` + + Tel string `valid:"Tel"` + ReqTel string `valid:"Required;Tel"` + + Phone string `valid:"Phone"` + ReqPhone string `valid:"Required;Phone"` + + ZipCode string `valid:"ZipCode"` + ReqZipCode string `valid:"Required;ZipCode"` + } + + u := User{ + ReqEmail: "a@a.com", + ReqIP: "127.0.0.1", + ReqMobile: "18888888888", + ReqTel: "02088888888", + ReqPhone: "02088888888", + ReqZipCode: "510000", + } + + valid := Validation{} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + valid = Validation{RequiredFirst: true} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Fatal("validation should be passed") + } +} + +func TestPointer(t *testing.T) { + type User struct { + ID int + + Email *string `valid:"Email"` + ReqEmail *string `valid:"Required;Email"` + } + + u := User{ + ReqEmail: nil, + Email: nil, + } + + valid := Validation{} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + validEmail := "a@a.com" + u = User{ + ReqEmail: &validEmail, + Email: nil, + } + + valid = Validation{RequiredFirst: true} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Fatal("validation should be passed") + } + + u = User{ + ReqEmail: &validEmail, + Email: nil, + } + + valid = Validation{} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + invalidEmail := "a@a" + u = User{ + ReqEmail: &validEmail, + Email: &invalidEmail, + } + + valid = Validation{RequiredFirst: true} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + u = User{ + ReqEmail: &validEmail, + Email: &invalidEmail, + } + + valid = Validation{} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } +} + +func TestCanSkipAlso(t *testing.T) { + type User struct { + ID int + + Email string `valid:"Email"` + ReqEmail string `valid:"Required;Email"` + MatchRange int `valid:"Range(10, 20)"` + } + + u := User{ + ReqEmail: "a@a.com", + Email: "", + MatchRange: 0, + } + + valid := Validation{RequiredFirst: true} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + valid = Validation{RequiredFirst: true} + valid.CanSkipAlso("Range") + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Fatal("validation should be passed") + } + +} diff --git a/pkg/adapter/validation/validators.go b/pkg/adapter/validation/validators.go new file mode 100644 index 00000000..1a063749 --- /dev/null +++ b/pkg/adapter/validation/validators.go @@ -0,0 +1,512 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validation + +import ( + "sync" + + "github.com/astaxie/beego/pkg/infrastructure/validation" +) + +// CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty +var CanSkipFuncs = validation.CanSkipFuncs + +// MessageTmpls store commond validate template +var MessageTmpls = map[string]string{ + "Required": "Can not be empty", + "Min": "Minimum is %d", + "Max": "Maximum is %d", + "Range": "Range is %d to %d", + "MinSize": "Minimum size is %d", + "MaxSize": "Maximum size is %d", + "Length": "Required length is %d", + "Alpha": "Must be valid alpha characters", + "Numeric": "Must be valid numeric characters", + "AlphaNumeric": "Must be valid alpha or numeric characters", + "Match": "Must match %s", + "NoMatch": "Must not match %s", + "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", + "Email": "Must be a valid email address", + "IP": "Must be a valid ip address", + "Base64": "Must be valid base64 characters", + "Mobile": "Must be valid mobile number", + "Tel": "Must be valid telephone number", + "Phone": "Must be valid telephone or mobile phone number", + "ZipCode": "Must be valid zipcode", +} + +var once sync.Once + +// SetDefaultMessage set default messages +// if not set, the default messages are +// "Required": "Can not be empty", +// "Min": "Minimum is %d", +// "Max": "Maximum is %d", +// "Range": "Range is %d to %d", +// "MinSize": "Minimum size is %d", +// "MaxSize": "Maximum size is %d", +// "Length": "Required length is %d", +// "Alpha": "Must be valid alpha characters", +// "Numeric": "Must be valid numeric characters", +// "AlphaNumeric": "Must be valid alpha or numeric characters", +// "Match": "Must match %s", +// "NoMatch": "Must not match %s", +// "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", +// "Email": "Must be a valid email address", +// "IP": "Must be a valid ip address", +// "Base64": "Must be valid base64 characters", +// "Mobile": "Must be valid mobile number", +// "Tel": "Must be valid telephone number", +// "Phone": "Must be valid telephone or mobile phone number", +// "ZipCode": "Must be valid zipcode", +func SetDefaultMessage(msg map[string]string) { + validation.SetDefaultMessage(msg) +} + +// Validator interface +type Validator interface { + IsSatisfied(interface{}) bool + DefaultMessage() string + GetKey() string + GetLimitValue() interface{} +} + +// Required struct +type Required validation.Required + +// IsSatisfied judge whether obj has value +func (r Required) IsSatisfied(obj interface{}) bool { + return validation.Required(r).IsSatisfied(obj) +} + +// DefaultMessage return the default error message +func (r Required) DefaultMessage() string { + return validation.Required(r).DefaultMessage() +} + +// GetKey return the r.Key +func (r Required) GetKey() string { + return validation.Required(r).GetKey() +} + +// GetLimitValue return nil now +func (r Required) GetLimitValue() interface{} { + return validation.Required(r).GetLimitValue() +} + +// Min check struct +type Min validation.Min + +// IsSatisfied judge whether obj is valid +// not support int64 on 32-bit platform +func (m Min) IsSatisfied(obj interface{}) bool { + return validation.Min(m).IsSatisfied(obj) +} + +// DefaultMessage return the default min error message +func (m Min) DefaultMessage() string { + return validation.Min(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Min) GetKey() string { + return validation.Min(m).GetKey() +} + +// GetLimitValue return the limit value, Min +func (m Min) GetLimitValue() interface{} { + return validation.Min(m).GetLimitValue() +} + +// Max validate struct +type Max validation.Max + +// IsSatisfied judge whether obj is valid +// not support int64 on 32-bit platform +func (m Max) IsSatisfied(obj interface{}) bool { + return validation.Max(m).IsSatisfied(obj) +} + +// DefaultMessage return the default max error message +func (m Max) DefaultMessage() string { + return validation.Max(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Max) GetKey() string { + return validation.Max(m).GetKey() +} + +// GetLimitValue return the limit value, Max +func (m Max) GetLimitValue() interface{} { + return validation.Max(m).GetLimitValue() +} + +// Range Requires an integer to be within Min, Max inclusive. +type Range validation.Range + +// IsSatisfied judge whether obj is valid +// not support int64 on 32-bit platform +func (r Range) IsSatisfied(obj interface{}) bool { + return validation.Range(r).IsSatisfied(obj) +} + +// DefaultMessage return the default Range error message +func (r Range) DefaultMessage() string { + return validation.Range(r).DefaultMessage() +} + +// GetKey return the m.Key +func (r Range) GetKey() string { + return validation.Range(r).GetKey() +} + +// GetLimitValue return the limit value, Max +func (r Range) GetLimitValue() interface{} { + return validation.Range(r).GetLimitValue() +} + +// MinSize Requires an array or string to be at least a given length. +type MinSize validation.MinSize + +// IsSatisfied judge whether obj is valid +func (m MinSize) IsSatisfied(obj interface{}) bool { + return validation.MinSize(m).IsSatisfied(obj) +} + +// DefaultMessage return the default MinSize error message +func (m MinSize) DefaultMessage() string { + return validation.MinSize(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m MinSize) GetKey() string { + return validation.MinSize(m).GetKey() +} + +// GetLimitValue return the limit value +func (m MinSize) GetLimitValue() interface{} { + return validation.MinSize(m).GetLimitValue() +} + +// MaxSize Requires an array or string to be at most a given length. +type MaxSize validation.MaxSize + +// IsSatisfied judge whether obj is valid +func (m MaxSize) IsSatisfied(obj interface{}) bool { + return validation.MaxSize(m).IsSatisfied(obj) +} + +// DefaultMessage return the default MaxSize error message +func (m MaxSize) DefaultMessage() string { + return validation.MaxSize(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m MaxSize) GetKey() string { + return validation.MaxSize(m).GetKey() +} + +// GetLimitValue return the limit value +func (m MaxSize) GetLimitValue() interface{} { + return validation.MaxSize(m).GetLimitValue() +} + +// Length Requires an array or string to be exactly a given length. +type Length validation.Length + +// IsSatisfied judge whether obj is valid +func (l Length) IsSatisfied(obj interface{}) bool { + return validation.Length(l).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (l Length) DefaultMessage() string { + return validation.Length(l).DefaultMessage() +} + +// GetKey return the m.Key +func (l Length) GetKey() string { + return validation.Length(l).GetKey() +} + +// GetLimitValue return the limit value +func (l Length) GetLimitValue() interface{} { + return validation.Length(l).GetLimitValue() +} + +// Alpha check the alpha +type Alpha validation.Alpha + +// IsSatisfied judge whether obj is valid +func (a Alpha) IsSatisfied(obj interface{}) bool { + return validation.Alpha(a).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (a Alpha) DefaultMessage() string { + return validation.Alpha(a).DefaultMessage() +} + +// GetKey return the m.Key +func (a Alpha) GetKey() string { + return validation.Alpha(a).GetKey() +} + +// GetLimitValue return the limit value +func (a Alpha) GetLimitValue() interface{} { + return validation.Alpha(a).GetLimitValue() +} + +// Numeric check number +type Numeric validation.Numeric + +// IsSatisfied judge whether obj is valid +func (n Numeric) IsSatisfied(obj interface{}) bool { + return validation.Numeric(n).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (n Numeric) DefaultMessage() string { + return validation.Numeric(n).DefaultMessage() +} + +// GetKey return the n.Key +func (n Numeric) GetKey() string { + return validation.Numeric(n).GetKey() +} + +// GetLimitValue return the limit value +func (n Numeric) GetLimitValue() interface{} { + return validation.Numeric(n).GetLimitValue() +} + +// AlphaNumeric check alpha and number +type AlphaNumeric validation.AlphaNumeric + +// IsSatisfied judge whether obj is valid +func (a AlphaNumeric) IsSatisfied(obj interface{}) bool { + return validation.AlphaNumeric(a).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (a AlphaNumeric) DefaultMessage() string { + return validation.AlphaNumeric(a).DefaultMessage() +} + +// GetKey return the a.Key +func (a AlphaNumeric) GetKey() string { + return validation.AlphaNumeric(a).GetKey() +} + +// GetLimitValue return the limit value +func (a AlphaNumeric) GetLimitValue() interface{} { + return validation.AlphaNumeric(a).GetLimitValue() +} + +// Match Requires a string to match a given regex. +type Match validation.Match + +// IsSatisfied judge whether obj is valid +func (m Match) IsSatisfied(obj interface{}) bool { + return validation.Match(m).IsSatisfied(obj) +} + +// DefaultMessage return the default Match error message +func (m Match) DefaultMessage() string { + return validation.Match(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Match) GetKey() string { + return validation.Match(m).GetKey() +} + +// GetLimitValue return the limit value +func (m Match) GetLimitValue() interface{} { + return validation.Match(m).GetLimitValue() +} + +// NoMatch Requires a string to not match a given regex. +type NoMatch validation.NoMatch + +// IsSatisfied judge whether obj is valid +func (n NoMatch) IsSatisfied(obj interface{}) bool { + return validation.NoMatch(n).IsSatisfied(obj) +} + +// DefaultMessage return the default NoMatch error message +func (n NoMatch) DefaultMessage() string { + return validation.NoMatch(n).DefaultMessage() +} + +// GetKey return the n.Key +func (n NoMatch) GetKey() string { + return validation.NoMatch(n).GetKey() +} + +// GetLimitValue return the limit value +func (n NoMatch) GetLimitValue() interface{} { + return validation.NoMatch(n).GetLimitValue() +} + +// AlphaDash check not Alpha +type AlphaDash validation.AlphaDash + +// DefaultMessage return the default AlphaDash error message +func (a AlphaDash) DefaultMessage() string { + return validation.AlphaDash(a).DefaultMessage() +} + +// GetKey return the n.Key +func (a AlphaDash) GetKey() string { + return validation.AlphaDash(a).GetKey() +} + +// GetLimitValue return the limit value +func (a AlphaDash) GetLimitValue() interface{} { + return validation.AlphaDash(a).GetLimitValue() +} + +// Email check struct +type Email validation.Email + +// DefaultMessage return the default Email error message +func (e Email) DefaultMessage() string { + return validation.Email(e).DefaultMessage() +} + +// GetKey return the n.Key +func (e Email) GetKey() string { + return validation.Email(e).GetKey() +} + +// GetLimitValue return the limit value +func (e Email) GetLimitValue() interface{} { + return validation.Email(e).GetLimitValue() +} + +// IP check struct +type IP validation.IP + +// DefaultMessage return the default IP error message +func (i IP) DefaultMessage() string { + return validation.IP(i).DefaultMessage() +} + +// GetKey return the i.Key +func (i IP) GetKey() string { + return validation.IP(i).GetKey() +} + +// GetLimitValue return the limit value +func (i IP) GetLimitValue() interface{} { + return validation.IP(i).GetLimitValue() +} + +// Base64 check struct +type Base64 validation.Base64 + +// DefaultMessage return the default Base64 error message +func (b Base64) DefaultMessage() string { + return validation.Base64(b).DefaultMessage() +} + +// GetKey return the b.Key +func (b Base64) GetKey() string { + return validation.Base64(b).GetKey() +} + +// GetLimitValue return the limit value +func (b Base64) GetLimitValue() interface{} { + return validation.Base64(b).GetLimitValue() +} + +// Mobile check struct +type Mobile validation.Mobile + +// DefaultMessage return the default Mobile error message +func (m Mobile) DefaultMessage() string { + return validation.Mobile(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Mobile) GetKey() string { + return validation.Mobile(m).GetKey() +} + +// GetLimitValue return the limit value +func (m Mobile) GetLimitValue() interface{} { + return validation.Mobile(m).GetLimitValue() +} + +// Tel check telephone struct +type Tel validation.Tel + +// DefaultMessage return the default Tel error message +func (t Tel) DefaultMessage() string { + return validation.Tel(t).DefaultMessage() +} + +// GetKey return the t.Key +func (t Tel) GetKey() string { + return validation.Tel(t).GetKey() +} + +// GetLimitValue return the limit value +func (t Tel) GetLimitValue() interface{} { + return validation.Tel(t).GetLimitValue() +} + +// Phone just for chinese telephone or mobile phone number +type Phone validation.Phone + +// IsSatisfied judge whether obj is valid +func (p Phone) IsSatisfied(obj interface{}) bool { + return validation.Phone(p).IsSatisfied(obj) +} + +// DefaultMessage return the default Phone error message +func (p Phone) DefaultMessage() string { + return validation.Phone(p).DefaultMessage() +} + +// GetKey return the p.Key +func (p Phone) GetKey() string { + return validation.Phone(p).GetKey() +} + +// GetLimitValue return the limit value +func (p Phone) GetLimitValue() interface{} { + return validation.Phone(p).GetLimitValue() +} + +// ZipCode check the zip struct +type ZipCode validation.ZipCode + +// DefaultMessage return the default Zip error message +func (z ZipCode) DefaultMessage() string { + return validation.ZipCode(z).DefaultMessage() +} + +// GetKey return the z.Key +func (z ZipCode) GetKey() string { + return validation.ZipCode(z).GetKey() +} + +// GetLimitValue return the limit value +func (z ZipCode) GetLimitValue() interface{} { + return validation.ZipCode(z).GetLimitValue() +}