From 0fbc3228e88f1433babdd9a3101df370ef705247 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 8 Jun 2015 20:42:16 -0400 Subject: [PATCH 1/3] rework code to allow handling of comma (,) and = within the params i.e. excludesall=,= add test cases for comma and = validation within params add documentation stating how to include a comma within the parameters for #67 --- doc.go | 5 +++++ validator.go | 5 +++-- validator_test.go | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/doc.go b/doc.go index a4cf673..5282cf5 100644 --- a/doc.go +++ b/doc.go @@ -143,6 +143,11 @@ NOTE: Baked In Cross field validation only compares fields on the same struct, if cross field + cross struct validation is needed your own custom validator should be implemented. +NOTE2: comma is the default separator of validation tags, if you wish to have a comma +included within the parameter i.e. excludesall=, you will need to use the UTF-8 hex +representation 0x2C, which is replaced in the code as a comma, so the above will +become excludesall=0x2C + Here is a list of the current built in validators: - diff --git a/validator.go b/validator.go index c44ad1a..8dd373c 100644 --- a/validator.go +++ b/validator.go @@ -20,6 +20,7 @@ import ( ) const ( + utf8HexComma = "0x2C" tagSeparator = "," orSeparator = "|" noValidationTag = "-" @@ -428,7 +429,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f cField.tags = append(cField.tags, cTag) for i, val := range orVals { - vals := strings.Split(val, tagKeySeparator) + vals := strings.SplitN(val, tagKeySeparator, 2) key := strings.TrimSpace(vals[0]) @@ -438,7 +439,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f param := "" if len(vals) > 1 { - param = vals[1] + param = strings.Replace(vals[1], utf8HexComma, ",", -1) } cTag.keyVals[i] = []string{key, param} diff --git a/validator_test.go b/validator_test.go index 30adc0a..b9ff595 100644 --- a/validator_test.go +++ b/validator_test.go @@ -281,6 +281,16 @@ func TestExcludesAllValidation(t *testing.T) { err := validate.Field(username, "excludesall=@ ") NotEqual(t, err, nil) + + excluded := "," + + err = validate.Field(excluded, "excludesall=!@#$%^&*()_+.0x2C?") + NotEqual(t, err, nil) + + excluded = "=" + + err = validate.Field(excluded, "excludesall=!@#$%^&*()_+.0x2C=?") + NotEqual(t, err, nil) } func TestExcludesValidation(t *testing.T) { From fcbf6b65e44e7b9d7fc785e8f21709316b39c534 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 8 Jun 2015 21:27:00 -0400 Subject: [PATCH 2/3] add caching pool of StructErrors to reuse objects, reduce garbage collection and reduce memory allocations for #56 --- validator.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/validator.go b/validator.go index 8dd373c..33110f6 100644 --- a/validator.go +++ b/validator.go @@ -31,6 +31,48 @@ const ( structErrMsg = "Struct:%s\n" ) +var structPool *pool + +// Pool holds a channelStructErrors. +type pool struct { + pool chan *StructErrors +} + +// NewPool creates a new pool of Clients. +func newPool(max int) *pool { + return &pool{ + pool: make(chan *StructErrors, max), + } +} + +// Borrow a StructErrors from the pool. +func (p *pool) Borrow() *StructErrors { + var c *StructErrors + + select { + case c = <-p.pool: + default: + c = &StructErrors{ + Errors: map[string]*FieldError{}, + StructErrors: map[string]*StructErrors{}, + } + } + + return c +} + +// Return returns a StructErrors to the pool. +func (p *pool) Return(c *StructErrors) { + + // c.Struct = "" + + select { + case p.pool <- c: + default: + // let it go, let it go... + } +} + type cachedTags struct { keyVals [][]string isOrVal bool @@ -188,6 +230,9 @@ type Validate struct { // New creates a new Validate instance for use. func New(tagName string, funcs map[string]Func) *Validate { + + structPool = newPool(10) + return &Validate{ tagName: tagName, validationFuncs: funcs, @@ -201,6 +246,16 @@ func (v *Validate) SetTag(tagName string) { v.tagName = tagName } +// SetStructPoolMax sets the struct pools max size. this may be usefull for fine grained +// performance tuning towards your application, however, the default should be fine for +// nearly all cases. only increase if you have a deeply nested struct structure. +// NOTE: this method is not thread-safe +// NOTE: this is only here to keep compatibility with v5, in v6 the method will be removed +// and the max pool size will be passed into the New function +func (v *Validate) SetMaxStructPoolSize(max int) { + structPool = newPool(max) +} + // AddFunction adds a validation Func to a Validate's map of validators denoted by the key // NOTE: if the key already exists, it will get replaced. // NOTE: this method is not thread-safe @@ -260,11 +315,8 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter structCache.Set(structType, cs) } - validationErrors := &StructErrors{ - Struct: structName, - Errors: map[string]*FieldError{}, - StructErrors: map[string]*StructErrors{}, - } + validationErrors := structPool.Borrow() + validationErrors.Struct = structName for i := 0; i < numFields; i++ { @@ -360,6 +412,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter } if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 { + structPool.Return(validationErrors) return nil } From f4837a073b044868f79c4320b753ca2781b95f29 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 8 Jun 2015 21:46:08 -0400 Subject: [PATCH 3/3] add call to SetMaxStructPoolSize in test to ensure continued 100% test coverage --- validator_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/validator_test.go b/validator_test.go index b9ff595..aa8c749 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2044,6 +2044,8 @@ func TestFlattening(t *testing.T) { func TestStructStringValidation(t *testing.T) { + validate.SetMaxStructPoolSize(11) + tSuccess := &TestString{ Required: "Required", Len: "length==10",