Compare commits

..

10 Commits

Author SHA1 Message Date
aneurin 8fab67ff77 update package name 2026-07-05 23:38:45 +01:00
Aneurin Barker Snook dff0caf40b fix link to license 2024-07-19 14:29:46 +01:00
Aneurin Barker Snook b8c82f3529 remove unnecessary doc comments 2024-07-19 14:11:53 +01:00
Aneurin Barker Snook 45298c462a ensure go.sum not committed 2024-07-19 14:11:34 +01:00
Aneurin Barker Snook 38b1cf53d8 remove unnecessary staticcheck.conf 2024-07-18 08:36:08 +01:00
Aneurin Barker Snook c1d7c423a7 simplify readme 2024-07-18 08:24:36 +01:00
Aneurin Barker Snook 5b0e0ca1ec restore staticcheck, test ci 2024-07-18 08:24:36 +01:00
Aneurin Barker Snook 15b4c91cf0 rename package 2024-07-18 08:24:36 +01:00
Aneurin Barker Snook a9c1184d6b improve validate testing with examples
also simplified structure of test cases and failure logging
2024-07-18 06:53:20 +01:00
Aneurin Barker Snook 4c99b5a654 duplicate license to each package 2024-07-11 17:33:18 +01:00
24 changed files with 477 additions and 441 deletions
+47
View File
@@ -0,0 +1,47 @@
name: Test
on:
pull_request:
branches:
- develop
push:
branches:
- develop
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ^1.21.0
- name: Display Go version
run: go version
- name: Install dependencies
run: go get
- name: Run tests
run: go test -v
notify:
name: Send Discord workflow notification
runs-on: ubuntu-latest
needs: test
steps:
- name: Send notification
uses: annybs/action-notify-discord@v1
if: ${{ always() }}
with:
repository: ${{ github.repository }}
result: ${{ needs.test.result }}
run-id: ${{ github.run_id }}
run-number: ${{ github.run_number }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK }}
workflow: ${{ github.workflow }}
+1
View File
@@ -0,0 +1 @@
go.sum
+11
View File
@@ -0,0 +1,11 @@
# MIT License
Copyright © 2024 Aneurin Barker Snook a@aneur.in
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.
https://mit-license.org/
+1 -29
View File
@@ -2,34 +2,6 @@
A suite of straightforward validation functions. You put something in, you get back `nil` or an error.
## 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:
## 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
See [LICENSE.md](../LICENSE.md)
See [LICENSE.md](./LICENSE.md)
+25 -22
View File
@@ -2,40 +2,43 @@ package validate
import (
"errors"
"fmt"
"testing"
)
func TestAll(t *testing.T) {
type TestCase[T any] struct {
Input T
F func(T) error
Err error
}
func ExampleAll() {
testAll := All(MinLength(4), Chars("0123456789abcdef"))
fmt.Println(testAll("invalid input"))
// Output: contains disallowed characters
}
f := All(
func TestAll(t *testing.T) {
testAll := All(
MinLength(4),
MaxLength(8),
Chars("0123456789abcdef"),
In("abcd", "abcdef", "12345678"),
)
testCases := []TestCase[string]{
{Input: "abcd", F: f},
{Input: "abcdef", F: f},
{Input: "12345678", F: f},
{Input: "abc", F: f, Err: ErrMustBeLonger},
{Input: "abcdef012", F: f, Err: ErrMustBeShorter},
{Input: "abcdefgh", F: f, Err: ErrDisallowedChars},
{Input: "01abcd", F: f, Err: ErrValueNotAllowed},
testCases := map[string]error{
"abcd": nil,
"abcdef": nil,
"12345678": nil,
"abc": ErrMustBeLonger.With(4),
"abcdef012": ErrMustBeShorter.With(8),
"abcdefgh": ErrDisallowedChars,
"01abcd": ErrValueNotAllowed,
}
for n, tc := range testCases {
t.Logf("(%d) Testing %q", n, tc.Input)
for input, want := range testCases {
t.Run(input, func(t *testing.T) {
got := testAll(input)
err := tc.F(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
-1
View File
@@ -4,7 +4,6 @@ import (
"strings"
)
// Validation error.
var (
ErrDisallowedChars = NewError("contains disallowed characters")
)
+37 -36
View File
@@ -2,59 +2,60 @@ package validate
import (
"errors"
"fmt"
"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) {
type TestCase struct {
Input string
C string
Err error
testCases := map[string]map[string]error{
"0123456789abcdef": {"abcd1234": nil, "abcd 1234": ErrDisallowedChars, "ghijklmno": ErrDisallowedChars},
}
hexRange := "0123456789abcdef"
for setup, values := range testCases {
testChars := Chars(setup)
testCases := []TestCase{
{Input: "abcd1234", C: hexRange},
{Input: "abcd 1234", C: hexRange, Err: ErrDisallowedChars},
{Input: "ghijklmno", C: hexRange, Err: ErrDisallowedChars},
}
for input, want := range values {
t.Run(input, func(t *testing.T) {
got := testChars(input)
for n, tc := range testCases {
t.Logf("(%d) Testing %q against %q", n, tc.Input, tc.C)
f := Chars(tc.C)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
func TestExceptChars(t *testing.T) {
type TestCase struct {
Input string
C string
Err error
testCases := map[string]map[string]error{
"0123456789abcdef": {"abcd1234": ErrDisallowedChars, "abcd 1234": ErrDisallowedChars, "ghijklmno": nil},
}
hexRange := "0123456789abcdef"
for setup, values := range testCases {
testExceptChars := ExceptChars(setup)
testCases := []TestCase{
{Input: "abcd1234", C: hexRange, Err: ErrDisallowedChars},
{Input: "abcd 1234", C: hexRange, Err: ErrDisallowedChars},
{Input: "ghijklmno", C: hexRange},
}
for input, want := range values {
t.Run(input, func(t *testing.T) {
got := testExceptChars(input)
for n, tc := range testCases {
t.Logf("(%d) Testing %q against %q", n, tc.Input, tc.C)
f := ExceptChars(tc.C)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
-1
View File
@@ -4,7 +4,6 @@ import (
"regexp"
)
// Validation error.
var (
ErrInvalidEmail = NewError("invalid email address")
)
+33 -15
View File
@@ -2,27 +2,45 @@ package validate
import (
"errors"
"fmt"
"testing"
)
func TestEmail(t *testing.T) {
type TestCase struct {
Input string
Err error
}
func ExampleEmail() {
fmt.Println(Email("not an email"))
// Output: invalid email address
}
testCases := []TestCase{
{Input: "test@example.com"},
{Input: "testexample.com", Err: ErrInvalidEmail},
}
func FuzzEmail(f *testing.F) {
want := ErrInvalidEmail
for n, tc := range testCases {
t.Logf("(%d) Testing %q", n, tc.Input)
f.Fuzz(func(t *testing.T, input string) {
got := Email(input)
err := Email(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
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 (
"errors"
"fmt"
"testing"
)
func TestEqualInt(t *testing.T) {
type TestCase struct {
I int
C int
Err error
func TestEqual(t *testing.T) {
testCases := map[string]map[string]error{
"abc": {"abc": nil, "def": ErrNotEqual.With("abc"), "xyz": ErrNotEqual.With("abc")},
"def": {"abc": ErrNotEqual.With("def"), "def": nil, "xyz": ErrNotEqual.With("def")},
"xyz": {"abc": ErrNotEqual.With("xyz"), "def": ErrNotEqual.With("xyz"), "xyz": nil},
}
testCases := []TestCase{
{I: 1, C: 1},
{I: 5 ^ 3, C: 5 ^ 3},
{I: 10, C: 15, Err: ErrNotEqual},
}
for setup, values := range testCases {
testEqual := Equal(setup)
for i, tc := range testCases {
t.Logf("(%d) Testing %d against %d", i, tc.I, tc.C)
for input, want := range values {
t.Run(fmt.Sprintf("%s/%s", setup, input), func(t *testing.T) {
got := testEqual(input)
f := Equal(tc.C)
err := f(tc.I)
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)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
+28 -28
View File
@@ -2,48 +2,48 @@ package validate
import (
"errors"
"fmt"
"testing"
)
func TestErrorIs(t *testing.T) {
type TestCase struct {
Err error
Target error
Is bool
A error
B error
Want bool
}
testCases := []TestCase{
// Is any validation error
{Err: Err, Target: Err, Is: true},
{Err: ErrDisallowedChars, Target: Err, Is: true},
{Err: ErrMustBeGreater, Target: Err, Is: true},
// Want any validation error
{A: Err, B: Err, Want: true},
{A: ErrDisallowedChars, B: Err, Want: true},
{A: ErrMustBeGreater, B: Err, Want: true},
// Is specific validation error
{Err: ErrDisallowedChars, Target: ErrDisallowedChars, Is: true},
{Err: ErrMustBeGreater, Target: ErrMustBeGreater, Is: true},
// Want specific validation error
{A: ErrDisallowedChars, B: ErrDisallowedChars, Want: true},
{A: ErrMustBeGreater, B: ErrMustBeGreater, Want: true},
// Is not specific validation error
{Err: Err, Target: ErrDisallowedChars},
{Err: Err, Target: ErrMustBeGreater},
{Err: ErrMustBeGreater, Target: ErrDisallowedChars},
{Err: ErrDisallowedChars, Target: ErrMustBeGreater},
// Want not specific validation error
{A: Err, B: ErrDisallowedChars},
{A: Err, B: ErrMustBeGreater},
{A: ErrMustBeGreater, B: ErrDisallowedChars},
{A: ErrDisallowedChars, B: ErrMustBeGreater},
// Is not any other error
{Err: ErrDisallowedChars, Target: errors.New("contains disallowed characters")},
{Err: ErrMustBeGreater, Target: errors.New("must be greater than %v")},
// Want not any other error
{A: ErrDisallowedChars, B: errors.New("contains disallowed characters")},
{A: ErrMustBeGreater, B: errors.New("must be greater than %v")},
}
for i, tc := range testCases {
t.Logf("(%d) Testing %v against %v", i, tc.Err, tc.Target)
for _, testCase := range testCases {
a, b, want := testCase.A, testCase.B, testCase.Want
if errors.Is(tc.Err, tc.Target) {
if !tc.Is {
t.Errorf("%v should not equal %v", tc.Err, tc.Target)
t.Run(fmt.Sprintf("%v/%v", a, b), func(t *testing.T) {
got := errors.Is(a, b)
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)
}
}
})
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/annybs/go/validate
module code.aneur.in/go/validate
go 1.21
-1
View File
@@ -1,6 +1,5 @@
package validate
// Validation error.
var (
ErrValueNotAllowed = NewError("not allowed")
)
+24 -46
View File
@@ -2,59 +2,37 @@ package validate
import (
"errors"
"fmt"
"testing"
)
func TestInInt(t *testing.T) {
type TestCase struct {
Input int
A []int
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 ExampleIn() {
testIn := In("abc", "def", "xyz")
fmt.Println(testIn("123"))
// Output: not allowed
}
func TestInString(t *testing.T) {
type TestCase struct {
Input string
A []string
Err error
func TestIn(t *testing.T) {
testIn := In("abc", "def", "xyz")
testCases := map[string]error{
"abc": nil,
"def": nil,
"xyz": nil,
"abcd": ErrValueNotAllowed,
"123": ErrValueNotAllowed,
"": ErrValueNotAllowed,
}
allow := []string{"abcd", "ef", "1234"}
testCases := []TestCase{
{Input: "abcd", A: allow},
{Input: "ef", A: allow},
{Input: "1234", A: allow},
{Input: "5678", A: allow, Err: ErrValueNotAllowed},
}
for input, want := range testCases {
t.Run(input, func(t *testing.T) {
got := testIn(input)
for n, tc := range testCases {
t.Logf("(%d) Testing %q 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)
}
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
+2 -3
View File
@@ -1,9 +1,8 @@
package validate
// Validation error.
var (
ErrMustBeLonger = NewError("must be at least %d characters")
ErrMustBeShorter = NewError("must be no more than %d characters")
ErrMustBeLonger = NewError("must contain at least %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.
+37 -34
View File
@@ -2,57 +2,60 @@ package validate
import (
"errors"
"fmt"
"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) {
type TestCase struct {
Input string
L int
Err error
testCases := map[int]map[string]error{
8: {"abcd": nil, "abcdefgh": nil, "abcd efg": nil, "abcdefghi": ErrMustBeShorter.With(8)},
}
testCases := []TestCase{
{Input: "abcd", L: 8},
{Input: "abcdefgh", L: 8},
{Input: "abcd efg", L: 8},
{Input: "abcdefghi", L: 8, Err: ErrMustBeShorter},
}
for setup, values := range testCases {
testMaxLength := MaxLength(setup)
for n, tc := range testCases {
t.Logf("(%d) Testing %q against maximum length of %d", n, tc.Input, tc.L)
for input, want := range values {
t.Run(fmt.Sprintf("%d/%s", setup, input), func(t *testing.T) {
got := testMaxLength(input)
f := MaxLength(tc.L)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
func TestMinLength(t *testing.T) {
type TestCase struct {
Input string
L int
Err error
testCases := map[int]map[string]error{
8: {"abcd": ErrMustBeLonger.With(8), "abcdefgh": nil, "abcd efg": nil, "abcdefghi": nil},
}
testCases := []TestCase{
{Input: "abcd", L: 8, Err: ErrMustBeLonger},
{Input: "abcdefgh", L: 8},
{Input: "abcd efg", L: 8},
{Input: "abcdefghi", L: 8},
}
for setup, values := range testCases {
testMinLength := MinLength(setup)
for n, tc := range testCases {
t.Logf("(%d) Testing %q against minimum length of %d", n, tc.Input, tc.L)
for input, want := range values {
t.Run(fmt.Sprintf("%d/%s", setup, input), func(t *testing.T) {
got := testMinLength(input)
f := MinLength(tc.L)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
-1
View File
@@ -1,6 +1,5 @@
package validate
// Validation error.
var (
ErrMustBeGreater = NewError("must be greater than %v")
ErrMustBeGreaterOrEqual = NewError("must be greater than or equal to %v")
+115 -108
View File
@@ -2,167 +2,174 @@ package validate
import (
"errors"
"fmt"
"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) {
type TestCase struct {
Input int
N int
Excl bool
Err error
testCases := map[int]map[bool]map[int]error{
10: {
true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)},
false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)},
},
}
testCases := []TestCase{
{Input: 10, N: 0, Err: ErrMustBeLessOrEqual},
{Input: 10, N: 10},
{Input: 10, N: 15},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeLess},
}
for setup, subSetup := range testCases {
for excl, values := range subSetup {
testMax := Max(setup, excl)
for n, tc := range testCases {
t.Logf("(%d) Testing %d against maximum of %d", n, tc.Input, tc.N)
for input, want := range values {
t.Run(fmt.Sprintf("%d/%v/%d", setup, excl, input), func(t *testing.T) {
got := testMax(input)
f := Max(tc.N, tc.Excl)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
}
func TestMaxFloat32(t *testing.T) {
type TestCase struct {
Input float32
N float32
Excl bool
Err error
testCases := map[float32]map[bool]map[float32]error{
10: {
true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)},
false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)},
},
}
testCases := []TestCase{
{Input: 10, N: 0, Err: ErrMustBeLessOrEqual},
{Input: 10, N: 10},
{Input: 10, N: 15},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeLess},
}
for setup, subSetup := range testCases {
for excl, values := range subSetup {
testMax := MaxFloat32(setup, excl)
for n, tc := range testCases {
t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N)
for input, want := range values {
t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMax(input)
f := MaxFloat32(tc.N, tc.Excl)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
}
func TestMaxFloat64(t *testing.T) {
type TestCase struct {
Input float64
N float64
Excl bool
Err error
testCases := map[float64]map[bool]map[float64]error{
10: {
true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)},
false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)},
},
}
testCases := []TestCase{
{Input: 10, N: 0, Err: ErrMustBeLessOrEqual},
{Input: 10, N: 10},
{Input: 10, N: 15},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeLess},
}
for setup, subSetup := range testCases {
for excl, values := range subSetup {
testMax := MaxFloat64(setup, excl)
for n, tc := range testCases {
t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N)
for input, want := range values {
t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMax(input)
f := MaxFloat64(tc.N, tc.Excl)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
}
func TestMin(t *testing.T) {
type TestCase struct {
Input int
N int
Excl bool
Err error
testCases := map[int]map[bool]map[int]error{
10: {
true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil},
false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil},
},
}
testCases := []TestCase{
{Input: 10, N: 0},
{Input: 10, N: 10},
{Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater},
}
for setup, subSetup := range testCases {
for excl, values := range subSetup {
testMin := Min(setup, excl)
for n, tc := range testCases {
t.Logf("(%d) Testing %d against minimum of %d", n, tc.Input, tc.N)
for input, want := range values {
t.Run(fmt.Sprintf("%d/%v/%d", setup, excl, input), func(t *testing.T) {
got := testMin(input)
f := Min(tc.N, tc.Excl)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
}
func TestMinFloat32(t *testing.T) {
type TestCase struct {
Input float32
N float32
Excl bool
Err error
testCases := map[float32]map[bool]map[float32]error{
10: {
true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil},
false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil},
},
}
testCases := []TestCase{
{Input: 10, N: 0},
{Input: 10, N: 10},
{Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater},
}
for setup, subSetup := range testCases {
for excl, values := range subSetup {
testMin := MinFloat32(setup, excl)
for n, tc := range testCases {
t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N)
for input, want := range values {
t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMin(input)
f := MinFloat32(tc.N, tc.Excl)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
}
func TestMinFloat64(t *testing.T) {
type TestCase struct {
Input float64
N float64
Excl bool
Err error
testCases := map[float64]map[bool]map[float64]error{
10: {
true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil},
false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil},
},
}
testCases := []TestCase{
{Input: 10, N: 0},
{Input: 10, N: 10},
{Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual},
{Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater},
}
for setup, subSetup := range testCases {
for excl, values := range subSetup {
testMin := MinFloat64(setup, excl)
for n, tc := range testCases {
t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N)
for input, want := range values {
t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) {
got := testMin(input)
f := MinFloat64(tc.N, tc.Excl)
err := f(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
}
+2 -3
View File
@@ -1,6 +1,5 @@
package validate
// Validation error.
var (
ErrMustHaveMoreItems = NewError("must have at least %d items")
ErrMustHaveFewerItems = NewError("must have no more than %d items")
@@ -10,7 +9,7 @@ var (
func MaxSize[T any](l int) func([]T) error {
return func(value []T) error {
if len(value) > l {
return ErrMustHaveFewerItems
return ErrMustHaveFewerItems.With(l)
}
return nil
}
@@ -20,7 +19,7 @@ func MaxSize[T any](l int) func([]T) error {
func MinSize[T any](l int) func([]T) error {
return func(value []T) error {
if len(value) < l {
return ErrMustHaveMoreItems
return ErrMustHaveMoreItems.With(l)
}
return nil
}
+53 -30
View File
@@ -2,55 +2,78 @@ package validate
import (
"errors"
"fmt"
"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) {
type TestCase struct {
Input []int
L int
Err error
testCases := map[int]map[int]error{
0: {0: nil, 1: ErrMustHaveFewerItems.With(0), 2: ErrMustHaveFewerItems.With(0), 10: ErrMustHaveFewerItems.With(0)},
1: {0: nil, 1: nil, 2: ErrMustHaveFewerItems.With(1), 10: ErrMustHaveFewerItems.With(1)},
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{
{Input: []int{1, 2, 3, 4}, L: 8},
{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 max, values := range testCases {
testMaxSize := MaxSize[int](max)
for n, tc := range testCases {
t.Logf("(%d) Testing %q against maximum length of %d", n, tc.Input, tc.L)
for l, want := range values {
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)
err := f(tc.Input)
got := testMaxSize(input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
func TestMinSize(t *testing.T) {
type TestCase struct {
Input []int
L int
Err error
testCases := map[int]map[int]error{
0: {0: nil, 1: nil, 2: nil, 10: nil},
1: {0: ErrMustHaveMoreItems.With(1), 1: nil, 2: nil, 10: nil},
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{
{Input: []int{1, 2, 3, 4}, L: 8, Err: ErrMustHaveMoreItems},
{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 min, values := range testCases {
testMinSize := MinSize[int](min)
for n, tc := range testCases {
t.Logf("(%d) Testing %q against minimum length of %d", n, tc.Input, tc.L)
for l, want := range values {
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)
err := f(tc.Input)
got := testMinSize(input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
}
-1
View File
@@ -2,7 +2,6 @@ package validate
import "net/url"
// Validation error.
var (
ErrInvalidURL Error = NewError("invalid URL")
)
+21 -18
View File
@@ -2,30 +2,33 @@ package validate
import (
"errors"
"fmt"
"testing"
)
func ExampleURL() {
fmt.Println(URL("not a url"))
// Output: invalid URL
}
func TestURL(t *testing.T) {
type TestCase struct {
Input string
Err error
testCases := map[string]error{
"http://example.com": nil,
"http://subdomain.example.com": nil,
"http://www.example.com/some-page.html": nil,
"not a url": ErrInvalidURL,
"subdomain.com": ErrInvalidURL,
}
testCases := []TestCase{
{Input: "http://example.com"},
{Input: "http://subdomain.example.com"},
{Input: "http://www.example.com/some-page.html"},
{Input: "subdomain.com", Err: ErrInvalidURL},
{Input: "not a url", Err: ErrInvalidURL},
}
for input, want := range testCases {
t.Run(input, func(t *testing.T) {
got := URL(input)
for n, tc := range testCases {
t.Logf("(%d) Testing %q", n, tc.Input)
err := URL(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}
-1
View File
@@ -4,7 +4,6 @@ import (
"regexp"
)
// Validation error.
var (
ErrInvalidUUID = NewError("invalid UUID")
)
+23 -20
View File
@@ -2,32 +2,35 @@ package validate
import (
"errors"
"fmt"
"testing"
)
func ExampleUUID() {
fmt.Println(UUID("not a uuid"))
// Output: invalid UUID
}
func TestUUID(t *testing.T) {
type TestCase struct {
Input string
Err error
testCases := map[string]error{
"00000000-0000-0000-0000-000000000000": nil,
"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{
{Input: "00000000-0000-0000-0000-000000000000"},
{Input: "01234567-89ab-cdef-0123-456789abcdef"},
{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 input, want := range testCases {
t.Run(input, func(t *testing.T) {
got := UUID(input)
for n, tc := range testCases {
t.Logf("(%d) Testing %q", n, tc.Input)
err := UUID(tc.Input)
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
if !errors.Is(got, want) {
t.Error("got", got)
t.Error("want", want)
}
})
}
}