Merge branch 'v1-development' into v1

pull/16/head
Dean Karn 10 years ago
commit 6c939e24a8
  1. 25
      baked_in.go
  2. 208
      doc.go
  3. 14
      validator.go
  4. 34
      validator_test.go

@ -3,16 +3,14 @@ package validator
import ( import (
"log" "log"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
) )
var bakedInValidators = map[string]ValidationFunc{ var BakedInValidators = map[string]ValidationFunc{
"required": required, "required": required,
"len": length, "len": length,
"min": min, "min": min,
"max": max, "max": max,
"regex": regex,
} }
func required(field interface{}, param string) bool { func required(field interface{}, param string) bool {
@ -151,27 +149,6 @@ func max(field interface{}, param string) bool {
} }
} }
// regex is the builtin validation function that checks
// whether the string variable matches a regular expression
func regex(field interface{}, param string) bool {
s, ok := field.(string)
if !ok {
log.Fatalf("Bad field type %s\n", field)
}
re, err := regexp.Compile(param)
if err != nil {
log.Fatalf("Bad regex param %s\n", param)
}
if !re.MatchString(s) {
return false
}
return true
}
// asInt retuns the parameter as a int64 // asInt retuns the parameter as a int64
// or panics if it can't convert // or panics if it can't convert
func asInt(param string) int64 { func asInt(param string) int64 {

208
doc.go

@ -1,24 +1,186 @@
// Package validator implements value validations /*
// Package validator implements value validations for structs and individual fields based on tags.
// The MIT License (MIT)
// Built In Validator
// Copyright (c) 2015 Dean Karn
// The package contains a built in Validator instance for use,
// Permission is hereby granted, free of charge, to any person obtaining a copy but you may also create a new instance if needed.
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights // built in
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell errs := validator.ValidateStruct(//your struct)
// copies of the Software, and to permit persons to whom the Software is errs := validator.ValidateFieldByTag(field, "omitempty,min=1,max=10")
// furnished to do so, subject to the following conditions:
// // new
// The above copyright notice and this permission notice shall be included in all newValidator = validator.New("struct tag name", validator.BakedInFunctions)
// copies or substantial portions of the Software.
// A simple example usage:
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, type UserDetail {
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE Details string `validate:"-"`
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER }
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE type User struct {
// SOFTWARE. Name string `validate:"required,max=60"`
PreferedName string `validate:"omitempty,max=60"`
Sub UserDetail
}
user := &User {
Name: "",
}
// errs will contain a hierarchical list of errors
// using the StructValidationErrors struct
// or nil if no errors exist
errs := validator.ValidateStruct(user)
// in this case 1 error Name is required
errs.Struct will be "User"
errs.StructErrors will be empty <-- fields that were structs
errs.Errors will have 1 error of type FieldValidationError
Error Handling
The error can be used like so
fieldErr, _ := errs["Name"]
fieldErr.Field // "Name"
fieldErr.ErrorTag // "required"
Both StructValidationErrors and FieldValidationError implement the Error interface but it's
intended use is for development + debugging, not a production error message.
fieldErr.Error() // Field validation for "Name" failed on the "required" tag
errs.Error()
// Struct: User
// Field validation for "Name" failed on the "required" tag
Why not a better error message? because this library intends for you to handle your own error messages
Why should I handle my own errors? Many reasons, for me building and internationalized application
I needed to know the field and what validation failed so that I could provide an error in the users specific language.
if fieldErr.Field == "Name" {
switch fieldErr.ErrorTag
case "required":
return "Translated string based on field + error"
default:
return "Translated string based on field"
}
The hierarchical structure is hard to work with sometimes.. Agreed Flatten function to the rescue!
Flatten will return a map of FieldValidationError's but the field name will be namespaced.
// if UserDetail Details field failed validation
Field will be "Sub.Details"
// for Name
Field will be "Name"
Custom Functions
Custom functions can be added
//Structure
func customFunc(field interface{}, param string) bool {
if whatever {
return false
}
return true
}
validator.AddFunction("custom tag name", customFunc)
// NOTE: using the same tag name as an existing function
// will overwrite the existing one
Custom Tag Name
A custom tag name can be set to avoid conficts, or just have a shorter name
validator.SetTag("valid")
Multiple Validators
Multiple validators on a field will process in the order defined
type Test struct {
Field `validate:"max=10,min=1"`
}
// max will be checked then min
Bad Validator definitions are not handled by the library
type Test struct {
Field `validate:"min=10,max=0"`
}
// this definition of min max will never validate
Baked In Validators
Here is a list of the current built in validators:
-
Tells the validation to skip this struct field; this is particularily
handy in ignoring embedded structs from being validated. (Usage: -)
omitempty
Allows conitional validation, for example if a field is not set with
a value (Determined by the required validator) then other validation
such as min or max won't run, but if a value is set validation will run.
(Usage: omitempty)
len
For numbers, max will ensure that the value is
equal to the parameter given. For strings, it checks that
the string length is exactly that number of characters. For slices,
arrays, and maps, validates the number of items. (Usage: len=10)
max
For numbers, max will ensure that the value is
less than or equal to the parameter given. For strings, it checks
that the string length is at most that number of characters. For
slices, arrays, and maps, validates the number of items. (Usage: max=10)
min
For numbers, min will ensure that the value is
greater or equal to the parameter given. For strings, it checks that
the string length is at least that number of characters. For slices,
arrays, and maps, validates the number of items. (Usage: min=10)
required
This validates that the value is not the data types default value.
For numbers ensures value is not zero. For strings ensures value is
not "". For slices, arrays, and maps, ensures the length is not zero.
(Usage: required)
Validator notes:
regex
a regex validator won't be added because commas and = signs can be part of
a regex which conflict with the validation definitions, although workarounds
can be made, they take away from using pure regex's. Furthermore it's quick
and dirty but the regex's become harder to maintain and are not reusable, so
it's as much as a programming philosiphy as anything.
In place of this new validator functions should be created; a regex can be
used within the validator function and even be precompiled for better efficiency.
And the best reason, you can sumit a pull request and we can keep on adding to the
validation library of this package!
Panics
This package panics when bad input is provided, this is by design, bad code like that should not make it to production.
type Test struct {
TestField string `validate:"nonexistantfunction=1"`
}
t := &Test{
TestField: "Test"
}
validator.ValidateStruct(t) // this will panic
*/
package validator package validator

@ -108,7 +108,7 @@ type Validator struct {
// var bakedInValidators = map[string]ValidationFunc{} // var bakedInValidators = map[string]ValidationFunc{}
var internalValidator = NewValidator(defaultTagName, bakedInValidators) var internalValidator = NewValidator(defaultTagName, BakedInValidators)
// NewValidator creates a new Validator instance // NewValidator creates a new Validator instance
// NOTE: it is not necessary to create a new validator as the internal one will do in 99.9% of cases, but the option is there. // NOTE: it is not necessary to create a new validator as the internal one will do in 99.9% of cases, but the option is there.
@ -178,7 +178,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
return v.ValidateStruct(structValue.Elem().Interface()) return v.ValidateStruct(structValue.Elem().Interface())
} }
if structValue.Kind() != reflect.Struct { if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface {
panic("interface passed for validation is not a struct") panic("interface passed for validation is not a struct")
} }
@ -200,13 +200,13 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
} }
// if no validation and not a struct (which may containt fields for validation) // if no validation and not a struct (which may containt fields for validation)
if tag == "" && valueField.Kind() != reflect.Struct { if tag == "" && valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface {
continue continue
} }
switch valueField.Kind() { switch valueField.Kind() {
case reflect.Struct: case reflect.Struct, reflect.Interface:
if !unicode.IsUpper(rune(typeField.Name[0])) { if !unicode.IsUpper(rune(typeField.Name[0])) {
continue continue
@ -283,10 +283,12 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
panic("Invalid field passed to ValidateFieldWithTag") panic("Invalid field passed to ValidateFieldWithTag")
} }
// TODO: validate commas in regex's
valTags := strings.Split(tag, ",") valTags := strings.Split(tag, ",")
for _, valTag := range valTags { for _, valTag := range valTags {
// TODO: validate = in regex's
vals := strings.Split(valTag, "=") vals := strings.Split(valTag, "=")
key := strings.Trim(vals[0], " ") key := strings.Trim(vals[0], " ")
@ -299,8 +301,8 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
continue continue
} }
valFunc := v.validationFuncs[key] valFunc, ok := v.validationFuncs[key]
if valFunc == nil { if !ok {
panic(fmt.Sprintf("Undefined validation function on field %s", name)) panic(fmt.Sprintf("Undefined validation function on field %s", name))
} }

@ -8,10 +8,26 @@ import (
. "gopkg.in/check.v1" . "gopkg.in/check.v1"
) )
type I interface {
Foo() string
}
type Impl struct {
F string `validate:"len=3"`
}
func (i *Impl) Foo() string {
return i.F
}
type SubTest struct { type SubTest struct {
Test string `validate:"required"` Test string `validate:"required"`
} }
type TestInterface struct {
Iface I
}
type TestString struct { type TestString struct {
Required string `validate:"required"` Required string `validate:"required"`
Len string `validate:"len=10"` Len string `validate:"len=10"`
@ -24,6 +40,7 @@ type TestString struct {
Anonymous struct { Anonymous struct {
A string `validate:"required"` A string `validate:"required"`
} }
Iface I
} }
type TestInt32 struct { type TestInt32 struct {
@ -116,6 +133,9 @@ func (ms *MySuite) TestFlattening(c *C) {
}{ }{
A: "1", A: "1",
}, },
Iface: &Impl{
F: "123",
},
} }
err1 := validator.ValidateStruct(tSuccess).Flatten() err1 := validator.ValidateStruct(tSuccess).Flatten()
@ -136,6 +156,9 @@ func (ms *MySuite) TestFlattening(c *C) {
}{ }{
A: "", A: "",
}, },
Iface: &Impl{
F: "12",
},
} }
err2 := validator.ValidateStruct(tFail).Flatten() err2 := validator.ValidateStruct(tFail).Flatten()
@ -151,6 +174,9 @@ func (ms *MySuite) TestFlattening(c *C) {
// Assert Anonymous Struct Field // Assert Anonymous Struct Field
AssertMapFieldError(err2, "Anonymous.A", "required", c) AssertMapFieldError(err2, "Anonymous.A", "required", c)
// Assert Interface Field
AssertMapFieldError(err2, "Iface.F", "len", c)
} }
func (ms *MySuite) TestStructStringValidation(c *C) { func (ms *MySuite) TestStructStringValidation(c *C) {
@ -173,6 +199,9 @@ func (ms *MySuite) TestStructStringValidation(c *C) {
}{ }{
A: "1", A: "1",
}, },
Iface: &Impl{
F: "123",
},
} }
err := validator.ValidateStruct(tSuccess) err := validator.ValidateStruct(tSuccess)
@ -193,6 +222,9 @@ func (ms *MySuite) TestStructStringValidation(c *C) {
}{ }{
A: "", A: "",
}, },
Iface: &Impl{
F: "12",
},
} }
err = validator.ValidateStruct(tFail) err = validator.ValidateStruct(tFail)
@ -201,7 +233,7 @@ func (ms *MySuite) TestStructStringValidation(c *C) {
c.Assert(err, NotNil) c.Assert(err, NotNil)
c.Assert(err.Struct, Equals, "TestString") c.Assert(err.Struct, Equals, "TestString")
c.Assert(len(err.Errors), Equals, 6) c.Assert(len(err.Errors), Equals, 6)
c.Assert(len(err.StructErrors), Equals, 2) c.Assert(len(err.StructErrors), Equals, 3)
// Assert Fields // Assert Fields
AssertFieldError(err, "Required", "required", c) AssertFieldError(err, "Required", "required", c)

Loading…
Cancel
Save