diff --git a/baked_in.go b/baked_in.go index 464969c..ea21093 100644 --- a/baked_in.go +++ b/baked_in.go @@ -3,16 +3,14 @@ package validator import ( "log" "reflect" - "regexp" "strconv" ) -var bakedInValidators = map[string]ValidationFunc{ +var BakedInValidators = map[string]ValidationFunc{ "required": required, "len": length, "min": min, "max": max, - "regex": regex, } 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 // or panics if it can't convert func asInt(param string) int64 { diff --git a/doc.go b/doc.go index 952bdda..a200e2a 100644 --- a/doc.go +++ b/doc.go @@ -1,24 +1,186 @@ -// Package validator implements value validations -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Dean Karn -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// 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 -// SOFTWARE. +/* +Package validator implements value validations for structs and individual fields based on tags. + +Built In Validator + +The package contains a built in Validator instance for use, +but you may also create a new instance if needed. + + // built in + errs := validator.ValidateStruct(//your struct) + errs := validator.ValidateFieldByTag(field, "omitempty,min=1,max=10") + + // new + newValidator = validator.New("struct tag name", validator.BakedInFunctions) + +A simple example usage: + + type UserDetail { + Details string `validate:"-"` + } + + type User struct { + 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 diff --git a/validator.go b/validator.go index 5b53206..26bfcf2 100644 --- a/validator.go +++ b/validator.go @@ -108,7 +108,7 @@ type Validator struct { // var bakedInValidators = map[string]ValidationFunc{} -var internalValidator = NewValidator(defaultTagName, bakedInValidators) +var internalValidator = NewValidator(defaultTagName, BakedInValidators) // 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. @@ -178,7 +178,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { 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") } @@ -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 tag == "" && valueField.Kind() != reflect.Struct { + if tag == "" && valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface { continue } switch valueField.Kind() { - case reflect.Struct: + case reflect.Struct, reflect.Interface: if !unicode.IsUpper(rune(typeField.Name[0])) { continue @@ -283,10 +283,12 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st panic("Invalid field passed to ValidateFieldWithTag") } + // TODO: validate commas in regex's valTags := strings.Split(tag, ",") for _, valTag := range valTags { + // TODO: validate = in regex's vals := strings.Split(valTag, "=") key := strings.Trim(vals[0], " ") @@ -299,8 +301,8 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st continue } - valFunc := v.validationFuncs[key] - if valFunc == nil { + valFunc, ok := v.validationFuncs[key] + if !ok { panic(fmt.Sprintf("Undefined validation function on field %s", name)) } diff --git a/validator_test.go b/validator_test.go index e3a1074..6731db2 100644 --- a/validator_test.go +++ b/validator_test.go @@ -8,10 +8,26 @@ import ( . "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 { Test string `validate:"required"` } +type TestInterface struct { + Iface I +} + type TestString struct { Required string `validate:"required"` Len string `validate:"len=10"` @@ -24,6 +40,7 @@ type TestString struct { Anonymous struct { A string `validate:"required"` } + Iface I } type TestInt32 struct { @@ -116,6 +133,9 @@ func (ms *MySuite) TestFlattening(c *C) { }{ A: "1", }, + Iface: &Impl{ + F: "123", + }, } err1 := validator.ValidateStruct(tSuccess).Flatten() @@ -136,6 +156,9 @@ func (ms *MySuite) TestFlattening(c *C) { }{ A: "", }, + Iface: &Impl{ + F: "12", + }, } err2 := validator.ValidateStruct(tFail).Flatten() @@ -151,6 +174,9 @@ func (ms *MySuite) TestFlattening(c *C) { // Assert Anonymous Struct Field AssertMapFieldError(err2, "Anonymous.A", "required", c) + + // Assert Interface Field + AssertMapFieldError(err2, "Iface.F", "len", c) } func (ms *MySuite) TestStructStringValidation(c *C) { @@ -173,6 +199,9 @@ func (ms *MySuite) TestStructStringValidation(c *C) { }{ A: "1", }, + Iface: &Impl{ + F: "123", + }, } err := validator.ValidateStruct(tSuccess) @@ -193,6 +222,9 @@ func (ms *MySuite) TestStructStringValidation(c *C) { }{ A: "", }, + Iface: &Impl{ + F: "12", + }, } err = validator.ValidateStruct(tFail) @@ -201,7 +233,7 @@ func (ms *MySuite) TestStructStringValidation(c *C) { c.Assert(err, NotNil) c.Assert(err.Struct, Equals, "TestString") c.Assert(len(err.Errors), Equals, 6) - c.Assert(len(err.StructErrors), Equals, 2) + c.Assert(len(err.StructErrors), Equals, 3) // Assert Fields AssertFieldError(err, "Required", "required", c)