Fnlb was moved to its own repo: fnproject/lb (#702)

* Fnlb was moved to its own repo: fnproject/lb

* Clean up fnlb leftovers

* Newer deps
This commit is contained in:
Denis Makogon
2018-01-23 00:17:29 +02:00
committed by Reed Allman
parent 4ffa3d5005
commit d3be603e54
8310 changed files with 457462 additions and 1749312 deletions

View File

@@ -0,0 +1,26 @@
#### Support
If you do have a contribution to the package, feel free to create a Pull Request or an Issue.
#### What to contribute
If you don't know what to do, there are some features and functions that need to be done
- [ ] Refactor code
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
- [ ] Create actual list of contributors and projects that currently using this package
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
- [ ] Implement benchmarking
- [ ] Implement batch of examples
- [ ] Look at forks for new features and fixes
#### Advice
Feel free to create what you want, but keep in mind when you implement new features:
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
- Public functions must be documented and described in source file and added to README.md to the list of available functions
- There are must be unit-tests for any new functions and improvements

View File

@@ -156,6 +156,7 @@ func IsPort(str string) bool
func IsPositive(value float64) bool
func IsPrintableASCII(str string) bool
func IsRFC3339(str string) bool
func IsRFC3339WithoutZone(str string) bool
func IsRGBcolor(str string) bool
func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool
@@ -269,56 +270,57 @@ For completely custom validators (interface-based), see below.
Here is a list of available validators for struct fields (validator - used function):
```go
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
```
Validators with parameters
@@ -409,7 +411,31 @@ Documentation is available here: [godoc.org](https://godoc.org/github.com/asaske
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator).
#### Support
If you do have a contribution for the package feel free to put up a Pull Request or open Issue.
If you do have a contribution to the package, feel free to create a Pull Request or an Issue.
#### What to contribute
If you don't know what to do, there are some features and functions that need to be done
- [ ] Refactor code
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
- [ ] Create actual list of contributors and projects that currently using this package
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
- [ ] Implement benchmarking
- [ ] Implement batch of examples
- [ ] Look at forks for new features and fixes
#### Advice
Feel free to create what you want, but keep in mind when you implement new features:
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
- Public functions must be documented and described in source file and added to README.md to the list of available functions
- There are must be unit-tests for any new functions and improvements
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors)
* [Daniel Lohse](https://github.com/annismckenzie)

View File

@@ -1,5 +1,7 @@
package govalidator
import "strings"
// Errors is an array of multiple errors and conforms to the error interface.
type Errors []error
@@ -9,11 +11,11 @@ func (es Errors) Errors() []error {
}
func (es Errors) Error() string {
var err string
var errs []string
for _, e := range es {
err += e.Error() + ";"
errs = append(errs, e.Error())
}
return err
return strings.Join(errs, ";")
}
// Error encapsulates a name, an error and whether there's a custom error message or not.
@@ -21,6 +23,9 @@ type Error struct {
Name string
Err error
CustomErrorMessageExists bool
// Validator indicates the name of the validator that failed
Validator string
}
func (e Error) Error() string {

View File

@@ -15,10 +15,10 @@ func TestErrorsToString(t *testing.T) {
expected string
}{
{Errors{}, ""},
{Errors{fmt.Errorf("Error 1")}, "Error 1;"},
{Errors{fmt.Errorf("Error 1"), fmt.Errorf("Error 2")}, "Error 1;Error 2;"},
{Errors{customErr, fmt.Errorf("Error 2")}, "Custom Error Name: stdlib error;Error 2;"},
{Errors{fmt.Errorf("Error 123"), customErrWithCustomErrorMessage}, "Error 123;Bad stuff happened;"},
{Errors{fmt.Errorf("Error 1")}, "Error 1"},
{Errors{fmt.Errorf("Error 1"), fmt.Errorf("Error 2")}, "Error 1;Error 2"},
{Errors{customErr, fmt.Errorf("Error 2")}, "Custom Error Name: stdlib error;Error 2"},
{Errors{fmt.Errorf("Error 123"), customErrWithCustomErrorMessage}, "Error 123;Bad stuff happened"},
}
for _, test := range tests {
actual := test.param1.Error()

View File

@@ -1,6 +1,9 @@
package govalidator
import "math"
import (
"math"
"reflect"
)
// Abs returns absolute value of number
func Abs(value float64) float64 {
@@ -39,13 +42,47 @@ func IsNonPositive(value float64) bool {
}
// InRange returns true if value lies between left and right border
func InRange(value, left, right float64) bool {
func InRangeInt(value, left, right int) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border
func InRangeFloat32(value, left, right float32) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border
func InRangeFloat64(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border, generic type to handle int, float32 or float64, all types must the same type
func InRange(value interface{}, left interface{}, right interface{}) bool {
reflectValue := reflect.TypeOf(value).Kind()
reflectLeft := reflect.TypeOf(left).Kind()
reflectRight := reflect.TypeOf(right).Kind()
if reflectValue == reflect.Int && reflectLeft == reflect.Int && reflectRight == reflect.Int {
return InRangeInt(value.(int), left.(int), right.(int))
} else if reflectValue == reflect.Float32 && reflectLeft == reflect.Float32 && reflectRight == reflect.Float32 {
return InRangeFloat32(value.(float32), left.(float32), right.(float32))
} else if reflectValue == reflect.Float64 && reflectLeft == reflect.Float64 && reflectRight == reflect.Float64 {
return InRangeFloat64(value.(float64), left.(float64), right.(float64))
} else {
return false
}
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return math.Remainder(value, 1) == 0

View File

@@ -177,7 +177,60 @@ func TestIsNatural(t *testing.T) {
}
}
}
func TestInRange(t *testing.T) {
func TestInRangeInt(t *testing.T) {
t.Parallel()
var tests = []struct {
param int
left int
right int
expected bool
}{
{0, 0, 0, true},
{1, 0, 0, false},
{-1, 0, 0, false},
{0, -1, 1, true},
{0, 0, 1, true},
{0, -1, 0, true},
{0, 0, -1, true},
{0, 10, 5, false},
}
for _, test := range tests {
actual := InRangeInt(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRangeInt(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)
}
}
}
func TestInRangeFloat32(t *testing.T) {
t.Parallel()
var tests = []struct {
param float32
left float32
right float32
expected bool
}{
{0, 0, 0, true},
{1, 0, 0, false},
{-1, 0, 0, false},
{0, -1, 1, true},
{0, 0, 1, true},
{0, -1, 0, true},
{0, 0, -1, true},
{0, 10, 5, false},
}
for _, test := range tests {
actual := InRangeFloat32(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRangeFloat32(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)
}
}
}
func TestInRangeFloat64(t *testing.T) {
t.Parallel()
var tests = []struct {
@@ -196,6 +249,98 @@ func TestInRange(t *testing.T) {
{0, 10, 5, false},
}
for _, test := range tests {
actual := InRangeFloat64(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRangeFloat64(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)
}
}
}
func TestInRange(t *testing.T) {
t.Parallel()
var testsInt = []struct {
param int
left int
right int
expected bool
}{
{0, 0, 0, true},
{1, 0, 0, false},
{-1, 0, 0, false},
{0, -1, 1, true},
{0, 0, 1, true},
{0, -1, 0, true},
{0, 0, -1, true},
{0, 10, 5, false},
}
for _, test := range testsInt {
actual := InRange(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRange(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)
}
}
var testsFloat32 = []struct {
param float32
left float32
right float32
expected bool
}{
{0, 0, 0, true},
{1, 0, 0, false},
{-1, 0, 0, false},
{0, -1, 1, true},
{0, 0, 1, true},
{0, -1, 0, true},
{0, 0, -1, true},
{0, 10, 5, false},
}
for _, test := range testsFloat32 {
actual := InRange(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRange(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)
}
}
var testsFloat64 = []struct {
param float64
left float64
right float64
expected bool
}{
{0, 0, 0, true},
{1, 0, 0, false},
{-1, 0, 0, false},
{0, -1, 1, true},
{0, 0, 1, true},
{0, -1, 0, true},
{0, 0, -1, true},
{0, 10, 5, false},
}
for _, test := range testsFloat64 {
actual := InRange(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRange(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)
}
}
var testsTypeMix = []struct {
param int
left float64
right float64
expected bool
}{
{0, 0, 0, false},
{1, 0, 0, false},
{-1, 0, 0, false},
{0, -1, 1, false},
{0, 0, 1, false},
{0, -1, 0, false},
{0, 0, -1, false},
{0, 10, 5, false},
}
for _, test := range testsTypeMix {
actual := InRange(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRange(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)

View File

@@ -33,7 +33,6 @@ const (
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
Hostname string = ``
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([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]))`

View File

@@ -34,6 +34,7 @@ var ParamTagMap = map[string]ParamValidator{
"stringlength": StringLength,
"matches": StringMatches,
"in": isInRaw,
"rsapub": IsRsaPub,
}
// ParamTagRegexMap maps param tags to their respective regexes.
@@ -44,6 +45,7 @@ var ParamTagRegexMap = map[string]*regexp.Regexp{
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
"in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
"rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"),
}
type customTypeTagMap struct {
@@ -72,57 +74,58 @@ var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeV
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
var TagMap = map[string]Validator{
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
}
// ISO3166Entry stores country codes

View File

@@ -108,7 +108,7 @@ func CamelCaseToUnderscore(str string) string {
var output []rune
var segment []rune
for _, r := range str {
if !unicode.IsLower(r) {
if !unicode.IsLower(r) && string(r) != "_" {
output = addSegment(output, segment)
segment = nil
}

View File

@@ -269,6 +269,7 @@ func TestCamelCaseToUnderscore(t *testing.T) {
{"MyFunc", "my_func"},
{"ABC", "a_b_c"},
{"1B", "1_b"},
{"foo_bar", "foo_bar"},
}
for _, test := range tests {
actual := CamelCaseToUnderscore(test.param)

View File

@@ -2,8 +2,14 @@
package govalidator
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"net/url"
"reflect"
@@ -20,10 +26,12 @@ var (
fieldsRequiredByDefault bool
notNumberRegexp = regexp.MustCompile("[^0-9]+")
whiteSpacesAndMinus = regexp.MustCompile("[\\s-]+")
paramsRegexp = regexp.MustCompile("\\(.*\\)$")
)
const maxURLRuneCount = 2083
const minURLRuneCount = 3
const RF3339WithoutZone = "2006-01-02T15:04:05"
// SetFieldsRequiredByDefault causes validation to fail when struct fields
// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`).
@@ -54,7 +62,13 @@ func IsURL(str string) bool {
if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") {
return false
}
u, err := url.Parse(str)
strTemp := str
if strings.Index(str, ":") >= 0 && strings.Index(str, "://") == -1 {
// support no indicated urlscheme but with colon for port number
// http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString
strTemp = "http://" + str
}
u, err := url.Parse(strTemp)
if err != nil {
return false
}
@@ -65,7 +79,6 @@ func IsURL(str string) bool {
return false
}
return rxURL.MatchString(str)
}
// IsRequestURL check if the string rawurl, assuming
@@ -486,6 +499,33 @@ func IsDNSName(str string) bool {
return !IsIP(str) && rxDNSName.MatchString(str)
}
// IsHash checks if a string is a hash of type algorithm.
// Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']
func IsHash(str string, algorithm string) bool {
len := "0"
algo := strings.ToLower(algorithm)
if algo == "crc32" || algo == "crc32b" {
len = "8"
} else if algo == "md5" || algo == "md4" || algo == "ripemd128" || algo == "tiger128" {
len = "32"
} else if algo == "sha1" || algo == "ripemd160" || algo == "tiger160" {
len = "40"
} else if algo == "tiger192" {
len = "48"
} else if algo == "sha256" {
len = "64"
} else if algo == "sha384" {
len = "96"
} else if algo == "sha512" {
len = "128"
} else {
return false
}
return Matches(str, "^[a-f0-9]{" + len + "}$")
}
// IsDialString validates the given string for usage with the various Dial() functions
func IsDialString(str string) bool {
@@ -560,6 +600,40 @@ func IsLongitude(str string) bool {
return rxLongitude.MatchString(str)
}
// IsRsaPublicKey check if a string is valid public key with provided length
func IsRsaPublicKey(str string, keylen int) bool {
bb := bytes.NewBufferString(str)
pemBytes, err := ioutil.ReadAll(bb)
if err != nil {
return false
}
block, _ := pem.Decode(pemBytes)
if block != nil && block.Type != "PUBLIC KEY" {
return false
}
var der []byte
if block != nil {
der = block.Bytes
} else {
der, err = base64.StdEncoding.DecodeString(str)
if err != nil {
return false
}
}
key, err := x509.ParsePKIXPublicKey(der)
if err != nil {
return false
}
pubkey, ok := key.(*rsa.PublicKey)
if !ok {
return false
}
bitlen := len(pubkey.N.Bytes()) * 8
return bitlen == int(keylen)
}
func toJSONName(tag string) string {
if tag == "" {
return ""
@@ -568,7 +642,16 @@ func toJSONName(tag string) string {
// JSON name always comes first. If there's no options then split[0] is
// JSON name, if JSON name is not set, then split[0] is an empty string.
split := strings.SplitN(tag, ",", 2)
return split[0]
name := split[0]
// However it is possible that the field is skipped when
// (de-)serializing from/to JSON, in which case assume that there is no
// tag name to use
if name == "-" {
return ""
}
return name
}
// ValidateStruct use tags for fields.
@@ -595,7 +678,7 @@ func ValidateStruct(s interface{}) (bool, error) {
continue // Private field
}
structResult := true
if valueField.Kind() == reflect.Struct {
if valueField.Kind() == reflect.Struct && typeField.Tag.Get(tagName) != "-" {
var err error
structResult, err = ValidateStruct(valueField.Interface())
if err != nil {
@@ -613,6 +696,14 @@ func ValidateStruct(s interface{}) (bool, error) {
jsonError.Name = jsonTag
err2 = jsonError
case Errors:
for i2, err3 := range jsonError {
switch customErr := err3.(type) {
case Error:
customErr.Name = jsonTag
jsonError[i2] = customErr
}
}
err2 = jsonError
}
}
@@ -630,8 +721,11 @@ func ValidateStruct(s interface{}) (bool, error) {
// parseTagIntoMap parses a struct tag `valid:required~Some error message,length(2|3)` into map[string]string{"required": "Some error message", "length(2|3)": ""}
func parseTagIntoMap(tag string) tagOptionsMap {
optionsMap := make(tagOptionsMap)
options := strings.SplitN(tag, ",", -1)
options := strings.Split(tag, ",")
for _, option := range options {
option = strings.TrimSpace(option)
validationOptions := strings.Split(option, "~")
if !isValidTag(validationOptions[0]) {
continue
@@ -688,6 +782,11 @@ func IsRFC3339(str string) bool {
return IsTime(str, time.RFC3339)
}
// IsRFC3339WithoutZone check if string is valid timestamp value according to RFC3339 which excludes the timezone.
func IsRFC3339WithoutZone(str string) bool {
return IsTime(str, RF3339WithoutZone)
}
// IsISO4217 check if string is valid ISO currency code
func IsISO4217(str string) bool {
for _, currency := range ISO4217List {
@@ -716,6 +815,17 @@ func RuneLength(str string, params ...string) bool {
return StringLength(str, params...)
}
// IsRsaPub check whether string is valid RSA key
// Alias for IsRsaPublicKey
func IsRsaPub(str string, params ...string) bool {
if len(params) == 1 {
len, _ := ToInt(params[0])
return IsRsaPublicKey(str, int(len))
}
return false
}
// StringMatches checks if a string matches a given pattern.
func StringMatches(s string, params ...string) bool {
if len(params) == 1 {
@@ -776,11 +886,11 @@ func IsIn(str string, params ...string) bool {
func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap) (bool, error) {
if requiredOption, isRequired := options["required"]; isRequired {
if len(requiredOption) > 0 {
return false, Error{t.Name, fmt.Errorf(requiredOption), true}
return false, Error{t.Name, fmt.Errorf(requiredOption), true, "required"}
}
return false, Error{t.Name, fmt.Errorf("non zero value required"), false}
return false, Error{t.Name, fmt.Errorf("non zero value required"), false, "required"}
} else if _, isOptional := options["optional"]; fieldsRequiredByDefault && !isOptional {
return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false}
return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false, "required"}
}
// not required and empty is valid
return true, nil
@@ -799,7 +909,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
if !fieldsRequiredByDefault {
return true, nil
}
return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false}
return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false, "required"}
case "-":
return true, nil
}
@@ -822,10 +932,10 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
if result := validatefunc(v.Interface(), o.Interface()); !result {
if len(customErrorMessage) > 0 {
customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf(customErrorMessage), CustomErrorMessageExists: true})
customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf(customErrorMessage), CustomErrorMessageExists: true, Validator: stripParams(validatorName)})
continue
}
customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), validatorName), CustomErrorMessageExists: false})
customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), validatorName), CustomErrorMessageExists: false, Validator: stripParams(validatorName)})
}
}
}
@@ -844,7 +954,7 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
for validator := range options {
isValid = false
resultErr = Error{t.Name, fmt.Errorf(
"The following validator is invalid or can't be applied to the field: %q", validator), false}
"The following validator is invalid or can't be applied to the field: %q", validator), false, stripParams(validator)}
return
}
}
@@ -888,16 +998,16 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
field := fmt.Sprint(v) // make value into string, then validate with regex
if result := validatefunc(field, ps[1:]...); (!result && !negate) || (result && negate) {
if customMsgExists {
return false, Error{t.Name, fmt.Errorf(customErrorMessage), customMsgExists}
return false, Error{t.Name, fmt.Errorf(customErrorMessage), customMsgExists, stripParams(validatorSpec)}
}
if negate {
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists}
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)}
}
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists}
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)}
}
default:
// type not yet supported, fail
return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false}
return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false, stripParams(validatorSpec)}
}
}
@@ -909,17 +1019,17 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
field := fmt.Sprint(v) // make value into string, then validate with regex
if result := validatefunc(field); !result && !negate || result && negate {
if customMsgExists {
return false, Error{t.Name, fmt.Errorf(customErrorMessage), customMsgExists}
return false, Error{t.Name, fmt.Errorf(customErrorMessage), customMsgExists, stripParams(validatorSpec)}
}
if negate {
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists}
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)}
}
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists}
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)}
}
default:
//Not Yet Supported Types (Fail here!)
err := fmt.Errorf("Validator %s doesn't support kind %s for value %v", validator, v.Kind(), v)
return false, Error{t.Name, err, false}
return false, Error{t.Name, err, false, stripParams(validatorSpec)}
}
}
}
@@ -933,9 +1043,18 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
sort.Sort(sv)
result := true
for _, k := range sv {
resultItem, err := ValidateStruct(v.MapIndex(k).Interface())
if err != nil {
return false, err
var resultItem bool
var err error
if v.MapIndex(k).Kind() != reflect.Struct {
resultItem, err = typeCheck(v.MapIndex(k), t, o, options)
if err != nil {
return false, err
}
} else {
resultItem, err = ValidateStruct(v.MapIndex(k).Interface())
if err != nil {
return false, err
}
}
result = result && resultItem
}
@@ -978,6 +1097,10 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
}
}
func stripParams(validatorString string) string {
return paramsRegexp.ReplaceAllString(validatorString, "")
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.String, reflect.Array:

View File

@@ -536,6 +536,37 @@ func TestIsInt(t *testing.T) {
}
}
func TestIsHash(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
algo string
expected bool
}{
{"3ca25ae354e192b26879f651a51d92aa8a34d8d3", "sha1", true},
{"3ca25ae354e192b26879f651a51d34d8d3", "sha1", false},
{"3ca25ae354e192b26879f651a51d92aa8a34d8d3", "Tiger160", true},
{"3ca25ae354e192b26879f651a51d34d8d3", "ripemd160", false},
{"579282cfb65ca1f109b78536effaf621b853c9f7079664a3fbe2b519f435898c", "sha256", true},
{"579282cfb65ca1f109b78536effaf621b853c9f7079664a3fbe2b519f435898casfdsafsadfsdf", "sha256", false},
{"bf547c3fc5841a377eb1519c2890344dbab15c40ae4150b4b34443d2212e5b04aa9d58865bf03d8ae27840fef430b891", "sha384", true},
{"579282cfb65ca1f109b78536effaf621b853c9f7079664a3fbe2b519f435898casfdsafsadfsdf", "sha384", false},
{"45bc5fa8cb45ee408c04b6269e9f1e1c17090c5ce26ffeeda2af097735b29953ce547e40ff3ad0d120e5361cc5f9cee35ea91ecd4077f3f589b4d439168f91b9", "sha512", true},
{"579282cfb65ca1f109b78536effaf621b853c9f7079664a3fbe2b519f435898casfdsafsadfsdf", "sha512", false},
{"46fc0125a148788a3ac1d649566fc04eb84a746f1a6e4fa7", "tiger192", true},
{"46fc0125a148788a3ac1d649566fc04eb84a746f1a6$$%@^", "TIGER192", false},
{"46fc0125a148788a3ac1d649566fc04eb84a746f1a6$$%@^", "SOMEHASH", false},
}
for _, test := range tests {
actual := IsHash(test.param, test.algo)
if actual != test.expected {
t.Errorf("Expected IsHash(%q, %q) to be %v, got %v", test.param, test.algo, test.expected, actual)
}
}
}
func TestIsEmail(t *testing.T) {
t.Parallel()
@@ -633,6 +664,7 @@ func TestIsURL(t *testing.T) {
{"https://pbs.twimg.com/profile_images/560826135676588032/j8fWrmYY_normal.jpeg", true},
// according to #125
{"http://prometheus-alertmanager.service.q:9093", true},
{"aio1_alertmanager_container-63376c45:9093", true},
{"https://www.logn-123-123.url.with.sigle.letter.d:12345/url/path/foo?bar=zzz#user", true},
{"http://me.example.com", true},
{"http://www.me.example.com", true},
@@ -661,6 +693,10 @@ func TestIsURL(t *testing.T) {
{"foo_bar.example.com", true},
{"foo_bar_fizz_buzz.example.com", true},
{"http://hello_world.example.com", true},
// According to #212
{"foo_bar-fizz-buzz:1313", true},
{"foo_bar-fizz-buzz:13:13", false},
{"foo_bar-fizz-buzz://1313", false},
}
for _, test := range tests {
actual := IsURL(test.param)
@@ -980,6 +1016,7 @@ func TestIsMultibyte(t *testing.T) {
{"testexample.com", true},
{"1234abcDE", true},
{"カタカナ", true},
{"", true},
}
for _, test := range tests {
actual := IsMultibyte(test.param)
@@ -1850,6 +1887,13 @@ func TestIsTime(t *testing.T) {
{"2016-12-31T11:00:00.05Z", time.RFC3339, true},
{"2016-12-31T11:00:00.05-01:00", time.RFC3339, true},
{"2016-12-31T11:00:00.05+01:00", time.RFC3339, true},
{"2016-12-31T11:00:00", RF3339WithoutZone, true},
{"2016-12-31T11:00:00Z", RF3339WithoutZone, false},
{"2016-12-31T11:00:00+01:00", RF3339WithoutZone, false},
{"2016-12-31T11:00:00-01:00", RF3339WithoutZone, false},
{"2016-12-31T11:00:00.05Z", RF3339WithoutZone, false},
{"2016-12-31T11:00:00.05-01:00", RF3339WithoutZone, false},
{"2016-12-31T11:00:00.05+01:00", RF3339WithoutZone, false},
}
for _, test := range tests {
actual := IsTime(test.param, test.format)
@@ -2162,7 +2206,7 @@ func TestInvalidValidator(t *testing.T) {
invalidStruct := InvalidStruct{1}
if valid, err := ValidateStruct(&invalidStruct); valid || err == nil ||
err.Error() != `Field: The following validator is invalid or can't be applied to the field: "someInvalidValidator";` {
err.Error() != `Field: The following validator is invalid or can't be applied to the field: "someInvalidValidator"` {
t.Errorf("Got an unexpected result for struct with invalid validator: %t %s", valid, err)
}
}
@@ -2184,12 +2228,12 @@ func TestCustomValidator(t *testing.T) {
t.Errorf("Got an unexpected result for struct with custom always true validator: %t %s", valid, err)
}
if valid, err := ValidateStruct(&InvalidStruct{Field: 1}); valid || err == nil || err.Error() != "Custom validator error;;" {
if valid, err := ValidateStruct(&InvalidStruct{Field: 1}); valid || err == nil || err.Error() != "Custom validator error" {
t.Errorf("Got an unexpected result for struct with custom always false validator: %t %s", valid, err)
}
mixedStruct := StructWithCustomAndBuiltinValidator{}
if valid, err := ValidateStruct(&mixedStruct); valid || err == nil || err.Error() != "Field: non zero value required;" {
if valid, err := ValidateStruct(&mixedStruct); valid || err == nil || err.Error() != "Field: non zero value required" {
t.Errorf("Got an unexpected result for invalid struct with custom and built-in validators: %t %s", valid, err)
}
@@ -2522,6 +2566,8 @@ func TestValidateStruct(t *testing.T) {
type testByteArray [8]byte
type testByteMap map[byte]byte
type testByteSlice []byte
type testStringStringMap map[string]string
type testStringIntMap map[string]int
func TestRequired(t *testing.T) {
@@ -2606,6 +2652,22 @@ func TestRequired(t *testing.T) {
}{},
false,
},
{
struct {
TestStringStringMap testStringStringMap `valid:"required"`
}{
testStringStringMap{"test": "test"},
},
true,
},
{
struct {
TestIntMap testStringIntMap `valid:"required"`
}{
testStringIntMap{"test": 42},
},
true,
},
}
for _, test := range tests {
actual, err := ValidateStruct(test.param)
@@ -2693,7 +2755,7 @@ func TestErrorsByField(t *testing.T) {
{"CustomField", "An error occurred"},
}
err = Error{"CustomField", fmt.Errorf("An error occurred"), false}
err = Error{"CustomField", fmt.Errorf("An error occurred"), false, "hello"}
errs = ErrorsByField(err)
if len(errs) != 1 {
@@ -2838,10 +2900,11 @@ func TestOptionalCustomValidators(t *testing.T) {
func TestJSONValidator(t *testing.T) {
var val struct {
WithJSONName string `json:"with_json_name" valid:"-,required"`
WithoutJSONName string `valid:"-,required"`
WithJSONOmit string `json:"with_other_json_name,omitempty" valid:"-,required"`
WithJSONOption string `json:",omitempty" valid:"-,required"`
WithJSONName string `json:"with_json_name" valid:"-,required"`
WithoutJSONName string `valid:"-,required"`
WithJSONOmit string `json:"with_other_json_name,omitempty" valid:"-,required"`
WithJSONOption string `json:",omitempty" valid:"-,required"`
WithEmptyJSONName string `json:"-" valid:"-,required"`
}
_, err := ValidateStruct(val)
@@ -2861,4 +2924,117 @@ func TestJSONValidator(t *testing.T) {
if Contains(err.Error(), "omitempty") {
t.Errorf("Expected error message to not contain ',omitempty' but actual error is: %s", err.Error())
}
if !Contains(err.Error(), "WithEmptyJSONName") {
t.Errorf("Expected error message to contain WithEmptyJSONName but actual error is: %s", err.Error())
}
}
func TestValidatorIncludedInError(t *testing.T) {
post := Post{
Title: "",
Message: "👍",
AuthorIP: "xyz",
}
validatorMap := map[string]string{
"Title": "required",
"Message": "ascii",
"AuthorIP": "ipv4",
}
ok, errors := ValidateStruct(post)
if ok {
t.Errorf("expected validation to fail %v", ok)
}
for _, e := range errors.(Errors) {
casted := e.(Error)
if validatorMap[casted.Name] != casted.Validator {
t.Errorf("expected validator for %s to be %s, but was %s", casted.Name, validatorMap[casted.Name], casted.Validator)
}
}
// check to make sure that validators with arguments (like length(1|10)) don't include the arguments
// in the validator name
message := MessageWithSeveralFieldsStruct{
Title: "",
Body: "asdfasdfasdfasdfasdf",
}
validatorMap = map[string]string{
"Title": "length",
"Body": "length",
}
ok, errors = ValidateStruct(message)
if ok {
t.Errorf("expected validation to fail, %v", ok)
}
for _, e := range errors.(Errors) {
casted := e.(Error)
if validatorMap[casted.Name] != casted.Validator {
t.Errorf("expected validator for %s to be %s, but was %s", casted.Name, validatorMap[casted.Name], casted.Validator)
}
}
// make sure validators with custom messages don't show up in the validator string
type CustomMessage struct {
Text string `valid:"length(1|10)~Custom message"`
}
cs := CustomMessage{Text: "asdfasdfasdfasdf"}
ok, errors = ValidateStruct(&cs)
if ok {
t.Errorf("expected validation to fail, %v", ok)
}
validator := errors.(Errors)[0].(Error).Validator
if validator != "length" {
t.Errorf("expected validator for Text to be length, but was %s", validator)
}
}
func TestIsRsaPublicKey(t *testing.T) {
var tests = []struct {
rsastr string
keylen int
expected bool
}{
{`fubar`, 2048, false},
{`MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvncDCeibmEkabJLmFec7x9y86RP6dIvkVxxbQoOJo06E+p7tH6vCmiGHKnuu
XwKYLq0DKUE3t/HHsNdowfD9+NH8caLzmXqGBx45/Dzxnwqz0qYq7idK+Qff34qrk/YFoU7498U1Ee7PkKb7/VE9BmMEcI3uoKbeXCbJRI
HoTp8bUXOpNTSUfwUNwJzbm2nsHo2xu6virKtAZLTsJFzTUmRd11MrWCvj59lWzt1/eIMN+ekjH8aXeLOOl54CL+kWp48C+V9BchyKCShZ
B7ucimFvjHTtuxziXZQRO7HlcsBOa0WwvDJnRnskdyoD31s4F4jpKEYBJNWTo63v6lUvbQIDAQAB`, 2048, true},
{`MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvncDCeibmEkabJLmFec7x9y86RP6dIvkVxxbQoOJo06E+p7tH6vCmiGHKnuu
XwKYLq0DKUE3t/HHsNdowfD9+NH8caLzmXqGBx45/Dzxnwqz0qYq7idK+Qff34qrk/YFoU7498U1Ee7PkKb7/VE9BmMEcI3uoKbeXCbJRI
HoTp8bUXOpNTSUfwUNwJzbm2nsHo2xu6virKtAZLTsJFzTUmRd11MrWCvj59lWzt1/eIMN+ekjH8aXeLOOl54CL+kWp48C+V9BchyKCShZ
B7ucimFvjHTtuxziXZQRO7HlcsBOa0WwvDJnRnskdyoD31s4F4jpKEYBJNWTo63v6lUvbQIDAQAB`, 1024, false},
{`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvncDCeibmEkabJLmFec7
x9y86RP6dIvkVxxbQoOJo06E+p7tH6vCmiGHKnuuXwKYLq0DKUE3t/HHsNdowfD9
+NH8caLzmXqGBx45/Dzxnwqz0qYq7idK+Qff34qrk/YFoU7498U1Ee7PkKb7/VE9
BmMEcI3uoKbeXCbJRIHoTp8bUXOpNTSUfwUNwJzbm2nsHo2xu6virKtAZLTsJFzT
UmRd11MrWCvj59lWzt1/eIMN+ekjH8aXeLOOl54CL+kWp48C+V9BchyKCShZB7uc
imFvjHTtuxziXZQRO7HlcsBOa0WwvDJnRnskdyoD31s4F4jpKEYBJNWTo63v6lUv
bQIDAQAB
-----END PUBLIC KEY-----`, 2048, true},
{`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvncDCeibmEkabJLmFec7
x9y86RP6dIvkVxxbQoOJo06E+p7tH6vCmiGHKnuuXwKYLq0DKUE3t/HHsNdowfD9
+NH8caLzmXqGBx45/Dzxnwqz0qYq7idK+Qff34qrk/YFoU7498U1Ee7PkKb7/VE9
BmMEcI3uoKbeXCbJRIHoTp8bUXOpNTSUfwUNwJzbm2nsHo2xu6virKtAZLTsJFzT
UmRd11MrWCvj59lWzt1/eIMN+ekjH8aXeLOOl54CL+kWp48C+V9BchyKCShZB7uc
imFvjHTtuxziXZQRO7HlcsBOa0WwvDJnRnskdyoD31s4F4jpKEYBJNWTo63v6lUv
bQIDAQAB
-----END PUBLIC KEY-----`, 4096, false},
}
for i, test := range tests {
actual := IsRsaPublicKey(test.rsastr, test.keylen)
if actual != test.expected {
t.Errorf("Expected TestIsRsaPublicKey(%d, %d) to be %v, got %v", i, test.keylen, test.expected, actual)
}
}
}