From 4e4a2a2b8e9edc1828623203d83a18bdc6cae45a Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Wed, 25 Feb 2015 15:08:22 -0500 Subject: [PATCH] issue-#1 add url validation and test cases add uri validation and test cases --- baked_in.go | 122 +++++++++++++++++++++++++++++++--------------- doc.go | 8 +++ regexes.go | 2 + validator.go | 2 +- validator_test.go | 106 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 41 deletions(-) diff --git a/baked_in.go b/baked_in.go index a0285b3..d4ad943 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2,6 +2,7 @@ package validator import ( "fmt" + "net/url" "reflect" "strconv" ) @@ -9,28 +10,69 @@ import ( // BakedInValidators is the map of ValidationFunc used internally // but can be used with any new Validator if desired var BakedInValidators = map[string]ValidationFunc{ - "required": required, - "len": length, - "min": min, - "max": max, - "lt": lt, - "lte": lte, - "gt": gt, - "gte": gte, - "alpha": alpha, - "alphanum": alphanum, - "numeric": numeric, - "number": number, - "hexadecimal": hexadecimal, - "hexcolor": hexcolor, - "rgb": rgb, - "rgba": rgba, - "hsl": hsl, - "hsla": hsla, - "email": email, + "required": hasValue, + "len": hasLengthOf, + "min": hasMinOf, + "max": hasMaxOf, + "lt": isLt, + "lte": isLte, + "gt": isGt, + "gte": isGte, + "alpha": isAlpha, + "alphanum": isAlphanum, + "numeric": isNumeric, + "number": isNumber, + "hexadecimal": isHexadecimal, + "hexcolor": isHexcolor, + "rgb": isRgb, + "rgba": isRgba, + "hsl": isHsl, + "hsla": isHsla, + "email": isEmail, + "url": isURL, + "uri": isURI, } -func email(field interface{}, param string) bool { +func isURI(field interface{}, param string) bool { + + st := reflect.ValueOf(field) + + switch st.Kind() { + + case reflect.String: + _, err := url.ParseRequestURI(field.(string)) + + return err == nil + default: + panic(fmt.Sprintf("Bad field type %T", field)) + } +} + +func isURL(field interface{}, param string) bool { + + st := reflect.ValueOf(field) + + switch st.Kind() { + + case reflect.String: + url, err := url.ParseRequestURI(field.(string)) + + if err != nil { + return false + } + + if len(url.Scheme) == 0 { + return false + } + + return err == nil + + default: + panic(fmt.Sprintf("Bad field type %T", field)) + } +} + +func isEmail(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -43,7 +85,7 @@ func email(field interface{}, param string) bool { } } -func hsla(field interface{}, param string) bool { +func isHsla(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -56,7 +98,7 @@ func hsla(field interface{}, param string) bool { } } -func hsl(field interface{}, param string) bool { +func isHsl(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -69,7 +111,7 @@ func hsl(field interface{}, param string) bool { } } -func rgba(field interface{}, param string) bool { +func isRgba(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -82,7 +124,7 @@ func rgba(field interface{}, param string) bool { } } -func rgb(field interface{}, param string) bool { +func isRgb(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -95,7 +137,7 @@ func rgb(field interface{}, param string) bool { } } -func hexcolor(field interface{}, param string) bool { +func isHexcolor(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -108,7 +150,7 @@ func hexcolor(field interface{}, param string) bool { } } -func hexadecimal(field interface{}, param string) bool { +func isHexadecimal(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -121,7 +163,7 @@ func hexadecimal(field interface{}, param string) bool { } } -func number(field interface{}, param string) bool { +func isNumber(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -134,7 +176,7 @@ func number(field interface{}, param string) bool { } } -func numeric(field interface{}, param string) bool { +func isNumeric(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -147,7 +189,7 @@ func numeric(field interface{}, param string) bool { } } -func alphanum(field interface{}, param string) bool { +func isAlphanum(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -160,7 +202,7 @@ func alphanum(field interface{}, param string) bool { } } -func alpha(field interface{}, param string) bool { +func isAlpha(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -173,7 +215,7 @@ func alpha(field interface{}, param string) bool { } } -func required(field interface{}, param string) bool { +func hasValue(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -187,7 +229,7 @@ func required(field interface{}, param string) bool { } } -func gte(field interface{}, param string) bool { +func isGte(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -223,7 +265,7 @@ func gte(field interface{}, param string) bool { } } -func gt(field interface{}, param string) bool { +func isGt(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -262,7 +304,7 @@ func gt(field interface{}, param string) bool { // length tests whether a variable's length is equal to a given // value. For strings it tests the number of characters whereas // for maps and slices it tests the number of items. -func length(field interface{}, param string) bool { +func hasLengthOf(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -302,12 +344,12 @@ func length(field interface{}, param string) bool { // number. For number types, it's a simple lesser-than test; for // strings it tests the number of characters whereas for maps // and slices it tests the number of items. -func min(field interface{}, param string) bool { +func hasMinOf(field interface{}, param string) bool { - return gte(field, param) + return isGte(field, param) } -func lte(field interface{}, param string) bool { +func isLte(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -343,7 +385,7 @@ func lte(field interface{}, param string) bool { } } -func lt(field interface{}, param string) bool { +func isLt(field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -383,9 +425,9 @@ func lt(field interface{}, param string) bool { // value. For numbers, it's a simple lesser-than test; for // strings it tests the number of characters whereas for maps // and slices it tests the number of items. -func max(field interface{}, param string) bool { +func hasMaxOf(field interface{}, param string) bool { - return lte(field, param) + return isLte(field, param) } // asInt retuns the parameter as a int64 diff --git a/doc.go b/doc.go index 8a4e074..dac6b23 100644 --- a/doc.go +++ b/doc.go @@ -227,6 +227,14 @@ Here is a list of the current built in validators: This may not conform to all possibilities of any rfc standard, but neither does any email provider accept all posibilities... (Usage: email) + url + This validates that a strings value contains a valid url + This will accept any url the golang request uri accepts but must contain + a schema for example http:// or rtmp:// + (Usage: url) + uri + This validates that a strings value contains a valid uri + This will accept any uri the golang request uri accepts (Usage: uri) Validator notes: diff --git a/regexes.go b/regexes.go index 692157e..881d57f 100644 --- a/regexes.go +++ b/regexes.go @@ -14,6 +14,8 @@ const ( hslRegexString = "^hsl\\(\\s*(0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*\\)$" hslaRegexString = "^hsla\\(\\s*(0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0.[1-9]*)|[01])\\s*\\)$" emailRegexString = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + // urlRegexString = `^((ftp|http|https):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|((www\.)?)?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?_?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?)|localhost)(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$` + // urlRegexString = "^(?:(?:https?|ftp):\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!10(?:\\.\\d{1,3}){3})(?!127(?:\\.\\d{1,3}){3})(?!169\\.254(?:\\.\\d{1,3}){2})(?!192\\.168(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\\.(?:[a-z\u00a1-\uffff]{2,})))(?::\\d{2,5})?(?:\\/[^\\s]*)?$" ) var ( diff --git a/validator.go b/validator.go index 4b14f8b..53cc651 100644 --- a/validator.go +++ b/validator.go @@ -267,7 +267,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st return nil } - if strings.Contains(tag, omitempty) && !required(f, "") { + if strings.Contains(tag, omitempty) && !hasValue(f, "") { return nil } diff --git a/validator_test.go b/validator_test.go index 6940dfb..685cc57 100644 --- a/validator_test.go +++ b/validator_test.go @@ -121,6 +121,112 @@ func AssertMapFieldError(s map[string]*validator.FieldValidationError, field str c.Assert(val.ErrorTag, Equals, expectedTag) } +func (ms *MySuite) TestUrl(c *C) { + + var tests = []struct { + param string + expected bool + }{ + {"http://foo.bar#com", true}, + {"http://foobar.com", true}, + {"https://foobar.com", true}, + {"foobar.com", false}, + {"http://foobar.coffee/", true}, + {"http://foobar.中文网/", true}, + {"http://foobar.org/", true}, + {"http://foobar.org:8080/", true}, + {"ftp://foobar.ru/", true}, + {"http://user:pass@www.foobar.com/", true}, + {"http://127.0.0.1/", true}, + {"http://duckduckgo.com/?q=%2F", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com/?foo=bar#baz=qux", true}, + {"http://foobar.com?foo=bar", true}, + {"http://www.xn--froschgrn-x9a.net/", true}, + {"", false}, + {"xyz://foobar.com", true}, + {"invalid.", false}, + {".com", false}, + {"rtmp://foobar.com", true}, + {"http://www.foo_bar.com/", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com#baz=qux", true}, + {"http://foobar.com/t$-_.+!*\\'(),", true}, + {"http://www.foobar.com/~foobar", true}, + {"http://www.-foobar.com/", true}, + {"http://www.foo---bar.com/", true}, + {"mailto:someone@example.com", true}, + {"irc://irc.server.org/channel", true}, + {"irc://#channel@network", true}, + {"/abs/test/dir", false}, + {"./rel/test/dir", false}, + } + for _, test := range tests { + + err := validator.ValidateFieldByTag(test.param, "url") + + if test.expected == true { + c.Assert(err, IsNil) + } else { + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "url") + } + } +} + +func (ms *MySuite) TestUri(c *C) { + + var tests = []struct { + param string + expected bool + }{ + {"http://foo.bar#com", true}, + {"http://foobar.com", true}, + {"https://foobar.com", true}, + {"foobar.com", false}, + {"http://foobar.coffee/", true}, + {"http://foobar.中文网/", true}, + {"http://foobar.org/", true}, + {"http://foobar.org:8080/", true}, + {"ftp://foobar.ru/", true}, + {"http://user:pass@www.foobar.com/", true}, + {"http://127.0.0.1/", true}, + {"http://duckduckgo.com/?q=%2F", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com/?foo=bar#baz=qux", true}, + {"http://foobar.com?foo=bar", true}, + {"http://www.xn--froschgrn-x9a.net/", true}, + {"", false}, + {"xyz://foobar.com", true}, + {"invalid.", false}, + {".com", false}, + {"rtmp://foobar.com", true}, + {"http://www.foo_bar.com/", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com#baz=qux", true}, + {"http://foobar.com/t$-_.+!*\\'(),", true}, + {"http://www.foobar.com/~foobar", true}, + {"http://www.-foobar.com/", true}, + {"http://www.foo---bar.com/", true}, + {"mailto:someone@example.com", true}, + {"irc://irc.server.org/channel", true}, + {"irc://#channel@network", true}, + {"/abs/test/dir", true}, + {"./rel/test/dir", false}, + } + for _, test := range tests { + + err := validator.ValidateFieldByTag(test.param, "uri") + + if test.expected == true { + c.Assert(err, IsNil) + } else { + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "uri") + } + } +} + func (ms *MySuite) TestOrTag(c *C) { s := "rgba(0,31,255,0.5)" err := validator.ValidateFieldByTag(s, "rgb|rgba")