improve validate testing with examples

also simplified structure of test cases and failure logging
This commit is contained in:
Aneurin Barker Snook
2024-07-18 06:53:20 +01:00
parent 4c99b5a654
commit a9c1184d6b
14 changed files with 417 additions and 428 deletions
+1 -25
View File
@@ -4,31 +4,7 @@ A suite of straightforward validation functions. You put something in, you get b
## Error handling ## Error handling
You can use `errors.Is()` to ascertain the type of errors thrown by validation functions. This may be helpful to control side effects, particularly if using multiple validators and returning early (similar to a strongly-typed try-catch). For example: You can use `errors.Is()` to ascertain the type of errors returned by validation functions. This may be helpful to control side effects, particularly if you are using multiple validators or want to change the error message.
## Example
```go
package main
import (
"errors"
"fmt"
"github.com/annybs/go/validate"
)
func main() {
v := validate.Equal("a")
if err := v("b"); err != nil {
if errors.Is(err, validate.ErrNotEqual) {
fmt.Println("failed successfully")
} else {
fmt.Println("failed unsuccessfully")
}
}
}
```
## License ## License
+25 -22
View File
@@ -2,40 +2,43 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func TestAll(t *testing.T) { func ExampleAll() {
type TestCase[T any] struct { testAll := All(MinLength(4), Chars("0123456789abcdef"))
Input T fmt.Println(testAll("invalid input"))
F func(T) error // Output: contains disallowed characters
Err error }
}
f := All( func TestAll(t *testing.T) {
testAll := All(
MinLength(4), MinLength(4),
MaxLength(8), MaxLength(8),
Chars("0123456789abcdef"), Chars("0123456789abcdef"),
In("abcd", "abcdef", "12345678"), In("abcd", "abcdef", "12345678"),
) )
testCases := []TestCase[string]{ testCases := map[string]error{
{Input: "abcd", F: f}, "abcd": nil,
{Input: "abcdef", F: f}, "abcdef": nil,
{Input: "12345678", F: f}, "12345678": nil,
{Input: "abc", F: f, Err: ErrMustBeLonger},
{Input: "abcdef012", F: f, Err: ErrMustBeShorter}, "abc": ErrMustBeLonger.With(4),
{Input: "abcdefgh", F: f, Err: ErrDisallowedChars}, "abcdef012": ErrMustBeShorter.With(8),
{Input: "01abcd", F: f, Err: ErrValueNotAllowed}, "abcdefgh": ErrDisallowedChars,
"01abcd": ErrValueNotAllowed,
} }
for n, tc := range testCases { for input, want := range testCases {
t.Logf("(%d) Testing %q", n, tc.Input) t.Run(input, func(t *testing.T) {
got := testAll(input)
err := tc.F(tc.Input) if !errors.Is(got, want) {
t.Error("got", got)
if !errors.Is(err, tc.Err) { t.Error("want", want)
t.Errorf("Expected error %v, got %v", tc.Err, err) }
} })
} }
} }
+37 -36
View File
@@ -2,59 +2,60 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func ExampleChars() {
testChars := Chars("0123456789abcdef")
fmt.Println(testChars("invalid input"))
// Output: contains disallowed characters
}
func ExampleExceptChars() {
testExceptChars := Chars("0123456789abcdef")
fmt.Println(testExceptChars("invalid input"))
// Output: contains disallowed characters
}
func TestChars(t *testing.T) { func TestChars(t *testing.T) {
type TestCase struct { testCases := map[string]map[string]error{
Input string "0123456789abcdef": {"abcd1234": nil, "abcd 1234": ErrDisallowedChars, "ghijklmno": ErrDisallowedChars},
C string
Err error
} }
hexRange := "0123456789abcdef" for setup, values := range testCases {
testChars := Chars(setup)
testCases := []TestCase{ for input, want := range values {
{Input: "abcd1234", C: hexRange}, t.Run(input, func(t *testing.T) {
{Input: "abcd 1234", C: hexRange, Err: ErrDisallowedChars}, got := testChars(input)
{Input: "ghijklmno", C: hexRange, Err: ErrDisallowedChars},
}
for n, tc := range testCases { if !errors.Is(got, want) {
t.Logf("(%d) Testing %q against %q", n, tc.Input, tc.C) t.Error("got", got)
t.Error("want", want)
f := Chars(tc.C) }
err := f(tc.Input) })
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
} }
} }
} }
func TestExceptChars(t *testing.T) { func TestExceptChars(t *testing.T) {
type TestCase struct { testCases := map[string]map[string]error{
Input string "0123456789abcdef": {"abcd1234": ErrDisallowedChars, "abcd 1234": ErrDisallowedChars, "ghijklmno": nil},
C string
Err error
} }
hexRange := "0123456789abcdef" for setup, values := range testCases {
testExceptChars := ExceptChars(setup)
testCases := []TestCase{ for input, want := range values {
{Input: "abcd1234", C: hexRange, Err: ErrDisallowedChars}, t.Run(input, func(t *testing.T) {
{Input: "abcd 1234", C: hexRange, Err: ErrDisallowedChars}, got := testExceptChars(input)
{Input: "ghijklmno", C: hexRange},
}
for n, tc := range testCases { if !errors.Is(got, want) {
t.Logf("(%d) Testing %q against %q", n, tc.Input, tc.C) t.Error("got", got)
t.Error("want", want)
f := ExceptChars(tc.C) }
err := f(tc.Input) })
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
} }
} }
} }
+33 -15
View File
@@ -2,27 +2,45 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func TestEmail(t *testing.T) { func ExampleEmail() {
type TestCase struct { fmt.Println(Email("not an email"))
Input string // Output: invalid email address
Err error }
}
testCases := []TestCase{ func FuzzEmail(f *testing.F) {
{Input: "test@example.com"}, want := ErrInvalidEmail
{Input: "testexample.com", Err: ErrInvalidEmail},
}
for n, tc := range testCases { f.Fuzz(func(t *testing.T, input string) {
t.Logf("(%d) Testing %q", n, tc.Input) got := Email(input)
err := Email(tc.Input) if !errors.Is(got, want) {
t.Error("got", got)
if !errors.Is(err, tc.Err) { t.Error("want", want)
t.Errorf("Expected error %v, got %v", tc.Err, err)
} }
})
}
func TestEmail(t *testing.T) {
testCases := map[string]error{
"test@example.com": nil,
"firstname.lastname@some-website.co.uk": nil,
"not an email": ErrInvalidEmail,
"testexample.com": ErrInvalidEmail,
}
for input, want := range testCases {
t.Run(input, func(t *testing.T) {
got := Email(input)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
} }
} }
+16 -42
View File
@@ -2,55 +2,29 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func TestEqualInt(t *testing.T) { func TestEqual(t *testing.T) {
type TestCase struct { testCases := map[string]map[string]error{
I int "abc": {"abc": nil, "def": ErrNotEqual.With("abc"), "xyz": ErrNotEqual.With("abc")},
C int "def": {"abc": ErrNotEqual.With("def"), "def": nil, "xyz": ErrNotEqual.With("def")},
Err error "xyz": {"abc": ErrNotEqual.With("xyz"), "def": ErrNotEqual.With("xyz"), "xyz": nil},
} }
testCases := []TestCase{ for setup, values := range testCases {
{I: 1, C: 1}, testEqual := Equal(setup)
{I: 5 ^ 3, C: 5 ^ 3},
{I: 10, C: 15, Err: ErrNotEqual},
}
for i, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %d against %d", i, tc.I, tc.C) t.Run(fmt.Sprintf("%s/%s", setup, input), func(t *testing.T) {
got := testEqual(input)
f := Equal(tc.C) if !errors.Is(got, want) {
err := f(tc.I) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
}
}
}
func TestEqualStr(t *testing.T) {
type TestCase struct {
I string
C string
Err error
}
testCases := []TestCase{
{I: "abc", C: "abc"},
{I: "def ghi 123", C: "def ghi 123"},
{I: "jkl", C: "mno", Err: ErrNotEqual},
}
for i, tc := range testCases {
t.Logf("(%d) Testing %s against %s", i, tc.I, tc.C)
f := Equal(tc.C)
err := f(tc.I)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
} }
} }
} }
+28 -28
View File
@@ -2,48 +2,48 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func TestErrorIs(t *testing.T) { func TestErrorIs(t *testing.T) {
type TestCase struct { type TestCase struct {
Err error A error
Target error B error
Is bool Want bool
} }
testCases := []TestCase{ testCases := []TestCase{
// Is any validation error // Want any validation error
{Err: Err, Target: Err, Is: true}, {A: Err, B: Err, Want: true},
{Err: ErrDisallowedChars, Target: Err, Is: true}, {A: ErrDisallowedChars, B: Err, Want: true},
{Err: ErrMustBeGreater, Target: Err, Is: true}, {A: ErrMustBeGreater, B: Err, Want: true},
// Is specific validation error // Want specific validation error
{Err: ErrDisallowedChars, Target: ErrDisallowedChars, Is: true}, {A: ErrDisallowedChars, B: ErrDisallowedChars, Want: true},
{Err: ErrMustBeGreater, Target: ErrMustBeGreater, Is: true}, {A: ErrMustBeGreater, B: ErrMustBeGreater, Want: true},
// Is not specific validation error // Want not specific validation error
{Err: Err, Target: ErrDisallowedChars}, {A: Err, B: ErrDisallowedChars},
{Err: Err, Target: ErrMustBeGreater}, {A: Err, B: ErrMustBeGreater},
{Err: ErrMustBeGreater, Target: ErrDisallowedChars}, {A: ErrMustBeGreater, B: ErrDisallowedChars},
{Err: ErrDisallowedChars, Target: ErrMustBeGreater}, {A: ErrDisallowedChars, B: ErrMustBeGreater},
// Is not any other error // Want not any other error
{Err: ErrDisallowedChars, Target: errors.New("contains disallowed characters")}, {A: ErrDisallowedChars, B: errors.New("contains disallowed characters")},
{Err: ErrMustBeGreater, Target: errors.New("must be greater than %v")}, {A: ErrMustBeGreater, B: errors.New("must be greater than %v")},
} }
for i, tc := range testCases { for _, testCase := range testCases {
t.Logf("(%d) Testing %v against %v", i, tc.Err, tc.Target) a, b, want := testCase.A, testCase.B, testCase.Want
if errors.Is(tc.Err, tc.Target) { t.Run(fmt.Sprintf("%v/%v", a, b), func(t *testing.T) {
if !tc.Is { got := errors.Is(a, b)
t.Errorf("%v should not equal %v", tc.Err, tc.Target)
if got != want {
t.Error("got", got)
t.Error("want", want)
} }
} else { })
if tc.Is {
t.Errorf("%v should equal %v", tc.Err, tc.Target)
}
}
} }
} }
+24 -46
View File
@@ -2,59 +2,37 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func TestInInt(t *testing.T) { func ExampleIn() {
type TestCase struct { testIn := In("abc", "def", "xyz")
Input int fmt.Println(testIn("123"))
A []int // Output: not allowed
Err error
}
allow := []int{1, 23, 456}
testCases := []TestCase{
{Input: 1, A: allow},
{Input: 23, A: allow},
{Input: 456, A: allow},
{Input: 789, A: allow, Err: ErrValueNotAllowed},
}
for n, tc := range testCases {
t.Logf("(%d) Testing %d against %v", n, tc.Input, tc.A)
f := In(tc.A...)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
} }
func TestInString(t *testing.T) { func TestIn(t *testing.T) {
type TestCase struct { testIn := In("abc", "def", "xyz")
Input string
A []string testCases := map[string]error{
Err error "abc": nil,
"def": nil,
"xyz": nil,
"abcd": ErrValueNotAllowed,
"123": ErrValueNotAllowed,
"": ErrValueNotAllowed,
} }
allow := []string{"abcd", "ef", "1234"} for input, want := range testCases {
testCases := []TestCase{ t.Run(input, func(t *testing.T) {
{Input: "abcd", A: allow}, got := testIn(input)
{Input: "ef", A: allow},
{Input: "1234", A: allow},
{Input: "5678", A: allow, Err: ErrValueNotAllowed},
}
for n, tc := range testCases { if !errors.Is(got, want) {
t.Logf("(%d) Testing %q against %v", n, tc.Input, tc.A) t.Error("got", got)
t.Error("want", want)
f := In(tc.A...) }
err := f(tc.Input) })
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
} }
} }
+2 -2
View File
@@ -2,8 +2,8 @@ package validate
// Validation error. // Validation error.
var ( var (
ErrMustBeLonger = NewError("must be at least %d characters") ErrMustBeLonger = NewError("must contain at least %d characters")
ErrMustBeShorter = NewError("must be no more than %d characters") ErrMustBeShorter = NewError("must contain no more than %d characters")
) )
// MaxLength validates the length of a string as being less than or equal to a given maximum. // MaxLength validates the length of a string as being less than or equal to a given maximum.
+37 -34
View File
@@ -2,57 +2,60 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func ExampleMaxLength() {
testMaxLength := MaxLength(8)
fmt.Println(testMaxLength("this string is too long"))
// Output: must contain no more than 8 characters
}
func ExampleMinLength() {
testMinLength := MinLength(8)
fmt.Println(testMinLength("2short"))
// Output: must contain at least 8 characters
}
func TestMaxLength(t *testing.T) { func TestMaxLength(t *testing.T) {
type TestCase struct { testCases := map[int]map[string]error{
Input string 8: {"abcd": nil, "abcdefgh": nil, "abcd efg": nil, "abcdefghi": ErrMustBeShorter.With(8)},
L int
Err error
} }
testCases := []TestCase{ for setup, values := range testCases {
{Input: "abcd", L: 8}, testMaxLength := MaxLength(setup)
{Input: "abcdefgh", L: 8},
{Input: "abcd efg", L: 8},
{Input: "abcdefghi", L: 8, Err: ErrMustBeShorter},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %q against maximum length of %d", n, tc.Input, tc.L) t.Run(fmt.Sprintf("%d/%s", setup, input), func(t *testing.T) {
got := testMaxLength(input)
f := MaxLength(tc.L) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
} }
} }
} }
func TestMinLength(t *testing.T) { func TestMinLength(t *testing.T) {
type TestCase struct { testCases := map[int]map[string]error{
Input string 8: {"abcd": ErrMustBeLonger.With(8), "abcdefgh": nil, "abcd efg": nil, "abcdefghi": nil},
L int
Err error
} }
testCases := []TestCase{ for setup, values := range testCases {
{Input: "abcd", L: 8, Err: ErrMustBeLonger}, testMinLength := MinLength(setup)
{Input: "abcdefgh", L: 8},
{Input: "abcd efg", L: 8},
{Input: "abcdefghi", L: 8},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %q against minimum length of %d", n, tc.Input, tc.L) t.Run(fmt.Sprintf("%d/%s", setup, input), func(t *testing.T) {
got := testMinLength(input)
f := MinLength(tc.L) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
} }
} }
} }
+115 -108
View File
@@ -2,167 +2,174 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func ExampleMax() {
testMax := Max(10, true)
fmt.Println(testMax(10))
// Output: must be less than 10
}
func ExampleMin() {
testMin := Min(10, false)
fmt.Println(testMin(5))
// Output: must be greater than or equal to 10
}
func TestMax(t *testing.T) { func TestMax(t *testing.T) {
type TestCase struct { testCases := map[int]map[bool]map[int]error{
Input int 10: {
N int true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)},
Excl bool false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)},
Err error },
} }
testCases := []TestCase{ for setup, subSetup := range testCases {
{Input: 10, N: 0, Err: ErrMustBeLessOrEqual}, for excl, values := range subSetup {
{Input: 10, N: 10}, testMax := Max(setup, excl)
{Input: 10, N: 15},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeLess},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %d against maximum of %d", n, tc.Input, tc.N) t.Run(fmt.Sprintf("%d/%v/%d", setup, excl, input), func(t *testing.T) {
got := testMax(input)
f := Max(tc.N, tc.Excl) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
}
} }
} }
} }
func TestMaxFloat32(t *testing.T) { func TestMaxFloat32(t *testing.T) {
type TestCase struct { testCases := map[float32]map[bool]map[float32]error{
Input float32 10: {
N float32 true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)},
Excl bool false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)},
Err error },
} }
testCases := []TestCase{ for setup, subSetup := range testCases {
{Input: 10, N: 0, Err: ErrMustBeLessOrEqual}, for excl, values := range subSetup {
{Input: 10, N: 10}, testMax := MaxFloat32(setup, excl)
{Input: 10, N: 15},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeLess},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N) t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMax(input)
f := MaxFloat32(tc.N, tc.Excl) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
}
} }
} }
} }
func TestMaxFloat64(t *testing.T) { func TestMaxFloat64(t *testing.T) {
type TestCase struct { testCases := map[float64]map[bool]map[float64]error{
Input float64 10: {
N float64 true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)},
Excl bool false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)},
Err error },
} }
testCases := []TestCase{ for setup, subSetup := range testCases {
{Input: 10, N: 0, Err: ErrMustBeLessOrEqual}, for excl, values := range subSetup {
{Input: 10, N: 10}, testMax := MaxFloat64(setup, excl)
{Input: 10, N: 15},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeLess},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N) t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMax(input)
f := MaxFloat64(tc.N, tc.Excl) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
}
} }
} }
} }
func TestMin(t *testing.T) { func TestMin(t *testing.T) {
type TestCase struct { testCases := map[int]map[bool]map[int]error{
Input int 10: {
N int true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil},
Excl bool false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil},
Err error },
} }
testCases := []TestCase{ for setup, subSetup := range testCases {
{Input: 10, N: 0}, for excl, values := range subSetup {
{Input: 10, N: 10}, testMin := Min(setup, excl)
{Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %d against minimum of %d", n, tc.Input, tc.N) t.Run(fmt.Sprintf("%d/%v/%d", setup, excl, input), func(t *testing.T) {
got := testMin(input)
f := Min(tc.N, tc.Excl) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
}
} }
} }
} }
func TestMinFloat32(t *testing.T) { func TestMinFloat32(t *testing.T) {
type TestCase struct { testCases := map[float32]map[bool]map[float32]error{
Input float32 10: {
N float32 true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil},
Excl bool false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil},
Err error },
} }
testCases := []TestCase{ for setup, subSetup := range testCases {
{Input: 10, N: 0}, for excl, values := range subSetup {
{Input: 10, N: 10}, testMin := MinFloat32(setup, excl)
{Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N) t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMin(input)
f := MinFloat32(tc.N, tc.Excl) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
}
} }
} }
} }
func TestMinFloat64(t *testing.T) { func TestMinFloat64(t *testing.T) {
type TestCase struct { testCases := map[float64]map[bool]map[float64]error{
Input float64 10: {
N float64 true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil},
Excl bool false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil},
Err error },
} }
testCases := []TestCase{ for setup, subSetup := range testCases {
{Input: 10, N: 0}, for excl, values := range subSetup {
{Input: 10, N: 10}, testMin := MinFloat64(setup, excl)
{Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater},
}
for n, tc := range testCases { for input, want := range values {
t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N) t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMin(input)
f := MinFloat64(tc.N, tc.Excl) if !errors.Is(got, want) {
err := f(tc.Input) t.Error("got", got)
t.Error("want", want)
if !errors.Is(err, tc.Err) { }
t.Errorf("Expected error %v, got %v", tc.Err, err) })
}
} }
} }
} }
+2 -2
View File
@@ -10,7 +10,7 @@ var (
func MaxSize[T any](l int) func([]T) error { func MaxSize[T any](l int) func([]T) error {
return func(value []T) error { return func(value []T) error {
if len(value) > l { if len(value) > l {
return ErrMustHaveFewerItems return ErrMustHaveFewerItems.With(l)
} }
return nil return nil
} }
@@ -20,7 +20,7 @@ func MaxSize[T any](l int) func([]T) error {
func MinSize[T any](l int) func([]T) error { func MinSize[T any](l int) func([]T) error {
return func(value []T) error { return func(value []T) error {
if len(value) < l { if len(value) < l {
return ErrMustHaveMoreItems return ErrMustHaveMoreItems.With(l)
} }
return nil return nil
} }
+53 -30
View File
@@ -2,55 +2,78 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func ExampleMaxSize() {
testMaxSize := MaxSize[string](3)
fmt.Println(testMaxSize([]string{"abc", "def", "ghi", "jkl"}))
// Output: must have no more than 3 items
}
func ExampleMinSize() {
testMaxSize := MinSize[string](3)
fmt.Println(testMaxSize([]string{"abc", "def"}))
// Output: must have at least 3 items
}
func TestMaxSize(t *testing.T) { func TestMaxSize(t *testing.T) {
type TestCase struct { testCases := map[int]map[int]error{
Input []int 0: {0: nil, 1: ErrMustHaveFewerItems.With(0), 2: ErrMustHaveFewerItems.With(0), 10: ErrMustHaveFewerItems.With(0)},
L int 1: {0: nil, 1: nil, 2: ErrMustHaveFewerItems.With(1), 10: ErrMustHaveFewerItems.With(1)},
Err error 2: {0: nil, 1: nil, 2: nil, 10: ErrMustHaveFewerItems.With(2)},
10: {0: nil, 1: nil, 2: nil, 10: nil},
100: {0: nil, 1: nil, 2: nil, 10: nil},
} }
testCases := []TestCase{ for max, values := range testCases {
{Input: []int{1, 2, 3, 4}, L: 8}, testMaxSize := MaxSize[int](max)
{Input: []int{1, 2, 3, 4, 5, 6, 7, 8}, L: 8},
{Input: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, L: 8, Err: ErrMustHaveFewerItems},
}
for n, tc := range testCases { for l, want := range values {
t.Logf("(%d) Testing %q against maximum length of %d", n, tc.Input, tc.L) t.Run(fmt.Sprintf("%d/%d", max, l), func(t *testing.T) {
input := []int{}
for i := 0; i < l; i++ {
input = append(input, i)
}
f := MaxSize[int](tc.L) got := testMaxSize(input)
err := f(tc.Input)
if !errors.Is(err, tc.Err) { if !errors.Is(got, want) {
t.Errorf("Expected error %v, got %v", tc.Err, err) t.Error("got", got)
t.Error("want", want)
}
})
} }
} }
} }
func TestMinSize(t *testing.T) { func TestMinSize(t *testing.T) {
type TestCase struct { testCases := map[int]map[int]error{
Input []int 0: {0: nil, 1: nil, 2: nil, 10: nil},
L int 1: {0: ErrMustHaveMoreItems.With(1), 1: nil, 2: nil, 10: nil},
Err error 2: {0: ErrMustHaveMoreItems.With(2), 1: ErrMustHaveMoreItems.With(2), 2: nil, 10: nil},
10: {0: ErrMustHaveMoreItems.With(10), 1: ErrMustHaveMoreItems.With(10), 2: ErrMustHaveMoreItems.With(10), 10: nil},
100: {0: ErrMustHaveMoreItems.With(100), 1: ErrMustHaveMoreItems.With(100), 2: ErrMustHaveMoreItems.With(100), 10: ErrMustHaveMoreItems.With(100)},
} }
testCases := []TestCase{ for min, values := range testCases {
{Input: []int{1, 2, 3, 4}, L: 8, Err: ErrMustHaveMoreItems}, testMinSize := MinSize[int](min)
{Input: []int{1, 2, 3, 4, 5, 6, 7, 8}, L: 8},
{Input: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, L: 8},
}
for n, tc := range testCases { for l, want := range values {
t.Logf("(%d) Testing %q against minimum length of %d", n, tc.Input, tc.L) t.Run(fmt.Sprintf("%d/%d", min, l), func(t *testing.T) {
input := []int{}
for i := 0; i < l; i++ {
input = append(input, i)
}
f := MinSize[int](tc.L) got := testMinSize(input)
err := f(tc.Input)
if !errors.Is(err, tc.Err) { if !errors.Is(got, want) {
t.Errorf("Expected error %v, got %v", tc.Err, err) t.Error("got", got)
t.Error("want", want)
}
})
} }
} }
} }
+21 -18
View File
@@ -2,30 +2,33 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func ExampleURL() {
fmt.Println(URL("not a url"))
// Output: invalid URL
}
func TestURL(t *testing.T) { func TestURL(t *testing.T) {
type TestCase struct { testCases := map[string]error{
Input string "http://example.com": nil,
Err error "http://subdomain.example.com": nil,
"http://www.example.com/some-page.html": nil,
"not a url": ErrInvalidURL,
"subdomain.com": ErrInvalidURL,
} }
testCases := []TestCase{ for input, want := range testCases {
{Input: "http://example.com"}, t.Run(input, func(t *testing.T) {
{Input: "http://subdomain.example.com"}, got := URL(input)
{Input: "http://www.example.com/some-page.html"},
{Input: "subdomain.com", Err: ErrInvalidURL},
{Input: "not a url", Err: ErrInvalidURL},
}
for n, tc := range testCases { if !errors.Is(got, want) {
t.Logf("(%d) Testing %q", n, tc.Input) t.Error("got", got)
t.Error("want", want)
err := URL(tc.Input) }
})
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
} }
} }
+23 -20
View File
@@ -2,32 +2,35 @@ package validate
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
) )
func ExampleUUID() {
fmt.Println(UUID("not a uuid"))
// Output: invalid UUID
}
func TestUUID(t *testing.T) { func TestUUID(t *testing.T) {
type TestCase struct { testCases := map[string]error{
Input string "00000000-0000-0000-0000-000000000000": nil,
Err error "01234567-89ab-cdef-0123-456789abcdef": nil,
"abcdef01-2345-6789-abcd-ef0123456789": nil,
"not a uuid": ErrInvalidUUID,
"00000000-00-0000-0000-00000000000000": ErrInvalidUUID,
"00000000000000000000000000000000": ErrInvalidUUID,
"01234567-89ab-cdef-ghij-klmnopqrstuv": ErrInvalidUUID,
} }
testCases := []TestCase{ for input, want := range testCases {
{Input: "00000000-0000-0000-0000-000000000000"}, t.Run(input, func(t *testing.T) {
{Input: "01234567-89ab-cdef-0123-456789abcdef"}, got := UUID(input)
{Input: "abcdef01-2345-6789-abcd-ef0123456789"},
{Input: "Not a UUID", Err: ErrInvalidUUID},
{Input: "00000000-00-0000-0000-00000000000000", Err: ErrInvalidUUID},
{Input: "00000000000000000000000000000000", Err: ErrInvalidUUID},
{Input: "01234567-89ab-cdef-ghij-klmnopqrstuv", Err: ErrInvalidUUID},
}
for n, tc := range testCases { if !errors.Is(got, want) {
t.Logf("(%d) Testing %q", n, tc.Input) t.Error("got", got)
t.Error("want", want)
err := UUID(tc.Input) }
})
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
} }
} }