Forráskód Böngészése

add validation error type, fix tests, add number tests

Aneurin Barker Snook 1 éve
szülő
commit
db902e3c1c
13 módosított fájl, 340 hozzáadás és 40 törlés
  1. 5 2
      all_test.go
  2. 2 4
      chars.go
  3. 5 2
      chars_test.go
  4. 5 2
      email_test.go
  5. 6 3
      equal_test.go
  6. 50 0
      error.go
  7. 49 0
      error_test.go
  8. 3 2
      in_test.go
  9. 7 4
      length_test.go
  10. 29 16
      number.go
  11. 168 0
      number_test.go
  12. 6 3
      size_test.go
  13. 5 2
      uuid_test.go

+ 5 - 2
all_test.go

@@ -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
chars.go

@@ -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
chars_test.go

@@ -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
email_test.go

@@ -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
equal_test.go

@@ -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 - 0
error.go

@@ -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 - 0
error_test.go

@@ -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
in_test.go

@@ -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
length_test.go

@@ -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
number.go

@@ -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 - 0
number_test.go

@@ -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
size_test.go

@@ -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
uuid_test.go

@@ -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)
 		}
 	}