add validation error type, fix tests, add number tests

This commit is contained in:
Aneurin Barker Snook
2023-11-19 20:27:33 +00:00
parent 196e1faeeb
commit db902e3c1c
13 changed files with 340 additions and 40 deletions
+5 -2
View File
@@ -1,6 +1,9 @@
package validate
import "testing"
import (
"errors"
"testing"
)
func TestAll(t *testing.T) {
type TestCase[T any] struct {
@@ -31,7 +34,7 @@ func TestAll(t *testing.T) {
err := tc.F(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
+2 -4
View File
@@ -1,20 +1,18 @@
package validate
import (
"errors"
"strings"
)
// Validation error.
var (
ErrDisallowedChars = errors.New("contains disallowed characters")
ErrDisallowedChars = NewError("contains disallowed characters")
)
// Chars validates whether a string contains only allowed characters.
func Chars(allow string) func(string) error {
return func(value string) error {
rs := []rune(value)
for _, r := range rs {
for _, r := range value {
if !strings.ContainsRune(allow, r) {
return ErrDisallowedChars
}
+5 -2
View File
@@ -1,6 +1,9 @@
package validate
import "testing"
import (
"errors"
"testing"
)
func TestChars(t *testing.T) {
type TestCase struct {
@@ -22,7 +25,7 @@ func TestChars(t *testing.T) {
f := Chars(tc.C)
err := f(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
+5 -2
View File
@@ -1,6 +1,9 @@
package validate
import "testing"
import (
"errors"
"testing"
)
func TestEmail(t *testing.T) {
type TestCase struct {
@@ -18,7 +21,7 @@ func TestEmail(t *testing.T) {
err := Email(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
+6 -3
View File
@@ -1,6 +1,9 @@
package validate
import "testing"
import (
"errors"
"testing"
)
func TestEqualInt(t *testing.T) {
type TestCase struct {
@@ -21,7 +24,7 @@ func TestEqualInt(t *testing.T) {
f := Equal(tc.C)
err := f(tc.I)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
@@ -46,7 +49,7 @@ func TestEqualStr(t *testing.T) {
f := Equal(tc.C)
err := f(tc.I)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
+50
View File
@@ -0,0 +1,50 @@
package validate
import "fmt"
// Validation error.
var (
Err = Error{}
)
// Error represents a validation error.
type Error struct {
Message string
Data []any
}
// Error retrieves the message of a validation Error.
// If it has Data, the message will be formatted.
func (e Error) Error() string {
if len(e.Data) > 0 {
return fmt.Sprintf(e.Message, e.Data...)
}
return e.Message
}
// Is determines whether the Error is an instance of the target.
// https://pkg.go.dev/errors#Is
//
// If the target is a validation error and specifies a message, this function returns true if the messages match.
// If the target is an empty validation error, this function always returns true.
func (e Error) Is(target error) bool {
if t, ok := target.(Error); ok {
return t.Message == e.Message || t.Message == ""
}
return false
}
func (e Error) With(value any) Error {
if e.Data == nil {
e.Data = []any{}
}
e.Data = append(e.Data, value)
return e
}
// NewError creates a new validation error.
func NewError(message string) Error {
return Error{
Message: message,
}
}
+49
View File
@@ -0,0 +1,49 @@
package validate
import (
"errors"
"testing"
)
func TestErrorIs(t *testing.T) {
type TestCase struct {
Err error
Target error
Is 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},
// Is specific validation error
{Err: ErrDisallowedChars, Target: ErrDisallowedChars, Is: true},
{Err: ErrMustBeGreater, Target: ErrMustBeGreater, Is: true},
// Is not specific validation error
{Err: Err, Target: ErrDisallowedChars},
{Err: Err, Target: ErrMustBeGreater},
{Err: ErrMustBeGreater, Target: ErrDisallowedChars},
{Err: ErrDisallowedChars, Target: ErrMustBeGreater},
// Is not any other error
{Err: ErrDisallowedChars, Target: errors.New("contains disallowed characters")},
{Err: ErrMustBeGreater, Target: errors.New("must be greater than %v")},
}
for i, tc := range testCases {
t.Logf("(%d) Testing %v against %v", i, tc.Err, tc.Target)
if errors.Is(tc.Err, tc.Target) {
if !tc.Is {
t.Errorf("%v should not equal %v", tc.Err, tc.Target)
}
} else {
if tc.Is {
t.Errorf("%v should equal %v", tc.Err, tc.Target)
}
}
}
}
+3 -2
View File
@@ -1,6 +1,7 @@
package validate
import (
"errors"
"testing"
)
@@ -25,7 +26,7 @@ func TestInInt(t *testing.T) {
f := In(tc.A...)
err := f(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
@@ -52,7 +53,7 @@ func TestInString(t *testing.T) {
f := In(tc.A...)
err := f(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
+7 -4
View File
@@ -1,6 +1,9 @@
package validate
import "testing"
import (
"errors"
"testing"
)
func TestMaxLength(t *testing.T) {
type TestCase struct {
@@ -22,7 +25,7 @@ func TestMaxLength(t *testing.T) {
f := MaxLength(tc.L)
err := f(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
@@ -30,8 +33,8 @@ func TestMaxLength(t *testing.T) {
func TestMinLength(t *testing.T) {
type TestCase struct {
L int
Input string
L int
Err error
}
@@ -48,7 +51,7 @@ func TestMinLength(t *testing.T) {
f := MinLength(tc.L)
err := f(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
+29 -16
View File
@@ -1,91 +1,104 @@
package validate
import "errors"
// Validation error.
var (
ErrTooHigh = errors.New("too high")
ErrTooLow = errors.New("too low")
ErrMustBeGreater = NewError("must be greater than %v")
ErrMustBeGreaterOrEqual = NewError("must be greater than or equal to %v")
ErrMustBeLess = NewError("must be less than %v")
ErrMustBeLessOrEqual = NewError("must be less than or equal to %v")
)
// Max validates whether an integer is less than or equal to a given maximum.
// If exclusive is true, an equal value will also produce an error.
func Max(n int, exclusive bool) func(int) error {
return func(value int) error {
if exclusive {
if value >= n {
return ErrTooHigh
return ErrMustBeLess.With(n)
}
}
if value > n {
return ErrTooHigh
return ErrMustBeLessOrEqual.With(n)
}
return nil
}
}
// MaxFloat32 validates whether a float32 is less than or equal to a given maximum.
// If exclusive is true, an equal value will also produce an error.
func MaxFloat32(n float32, exclusive bool) func(float32) error {
return func(value float32) error {
if exclusive {
if value >= n {
return ErrTooHigh
return ErrMustBeLess.With(n)
}
}
if value > n {
return ErrTooHigh
return ErrMustBeLessOrEqual.With(n)
}
return nil
}
}
// MaxFloat64 validates whether a float64 is less than or equal to a given maximum.
// If exclusive is true, an equal value will also produce an error.
func MaxFloat64(n float64, exclusive bool) func(float64) error {
return func(value float64) error {
if exclusive {
if value >= n {
return ErrTooHigh
return ErrMustBeLess.With(n)
}
}
if value > n {
return ErrTooHigh
return ErrMustBeLessOrEqual.With(n)
}
return nil
}
}
// Min validates whether an integer is less than or equal to a given maximum.
// If exclusive is true, an equal value will also produce an error.
func Min(n int, exclusive bool) func(int) error {
return func(value int) error {
if exclusive {
if value <= n {
return ErrTooHigh
return ErrMustBeGreater.With(n)
}
}
if value < n {
return ErrTooHigh
return ErrMustBeGreaterOrEqual.With(n)
}
return nil
}
}
// MinFloat32 validates whether a float32 is less than or equal to a given maximum.
// If exclusive is true, an equal value will also produce an error.
func MinFloat32(n float32, exclusive bool) func(float32) error {
return func(value float32) error {
if exclusive {
if value <= n {
return ErrTooHigh
return ErrMustBeGreater.With(n)
}
}
if value < n {
return ErrTooHigh
return ErrMustBeGreaterOrEqual.With(n)
}
return nil
}
}
// MinFloat64 validates whether a float64 is less than or equal to a given maximum.
// If exclusive is true, an equal value will also produce an error.
func MinFloat64(n float64, exclusive bool) func(float64) error {
return func(value float64) error {
if exclusive {
if value <= n {
return ErrTooHigh
return ErrMustBeGreater.With(n)
}
}
if value < n {
return ErrTooHigh
return ErrMustBeGreaterOrEqual.With(n)
}
return nil
}
+168
View File
@@ -0,0 +1,168 @@
package validate
import (
"errors"
"testing"
)
func TestMax(t *testing.T) {
type TestCase struct {
Input int
N int
Excl bool
Err error
}
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 n, tc := range testCases {
t.Logf("(%d) Testing %d against maximum of %d", n, tc.Input, tc.N)
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)
}
}
}
func TestMaxFloat32(t *testing.T) {
type TestCase struct {
Input float32
N float32
Excl bool
Err error
}
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 n, tc := range testCases {
t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N)
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)
}
}
}
func TestMaxFloat64(t *testing.T) {
type TestCase struct {
Input float64
N float64
Excl bool
Err error
}
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 n, tc := range testCases {
t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N)
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)
}
}
}
func TestMin(t *testing.T) {
type TestCase struct {
Input int
N int
Excl bool
Err error
}
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 n, tc := range testCases {
t.Logf("(%d) Testing %d against minimum of %d", n, tc.Input, tc.N)
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)
}
}
}
func TestMinFloat32(t *testing.T) {
type TestCase struct {
Input float32
N float32
Excl bool
Err error
}
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 n, tc := range testCases {
t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N)
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)
}
}
}
func TestMinFloat64(t *testing.T) {
type TestCase struct {
Input float64
N float64
Excl bool
Err error
}
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 n, tc := range testCases {
t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N)
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)
}
}
}
+6 -3
View File
@@ -1,6 +1,9 @@
package validate
import "testing"
import (
"errors"
"testing"
)
func TestMaxSize(t *testing.T) {
type TestCase struct {
@@ -21,7 +24,7 @@ func TestMaxSize(t *testing.T) {
f := MaxSize[int](tc.L)
err := f(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
@@ -46,7 +49,7 @@ func TestMinSize(t *testing.T) {
f := MinSize[int](tc.L)
err := f(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}
+5 -2
View File
@@ -1,6 +1,9 @@
package validate
import "testing"
import (
"errors"
"testing"
)
func TestUUID(t *testing.T) {
type TestCase struct {
@@ -23,7 +26,7 @@ func TestUUID(t *testing.T) {
err := UUID(tc.Input)
if err != tc.Err {
if !errors.Is(err, tc.Err) {
t.Errorf("Expected error %v, got %v", tc.Err, err)
}
}