add all validator, simplify error messages
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package validate
|
||||
|
||||
// All validates a value using a sequence of validation functions.
|
||||
// If any validation function returns an error, the sequence stops and the error is returned.
|
||||
func All[T any](fs ...func(T) error) func(T) error {
|
||||
return func(value T) error {
|
||||
for _, f := range fs {
|
||||
if err := f(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package validate
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
type TestCase[T any] struct {
|
||||
Input T
|
||||
F func(T) error
|
||||
Err error
|
||||
}
|
||||
|
||||
f := 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: ErrTooFewChars},
|
||||
{Input: "abcdef012", F: f, Err: ErrTooManyChars},
|
||||
{Input: "abcdefgh", F: f, Err: ErrDisallowedChars},
|
||||
{Input: "01abcd", F: f, Err: ErrValueNotAllowed},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Logf("%q", tc.Input)
|
||||
|
||||
err := tc.F(tc.Input)
|
||||
if tc.Err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s; got nil", tc.Err)
|
||||
}
|
||||
if err != tc.Err {
|
||||
t.Errorf("Expected %s; got %s", tc.Err, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil; got %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Validation error.
|
||||
var (
|
||||
ErrDisallowedChars = errors.New("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 {
|
||||
if !strings.ContainsRune(allow, r) {
|
||||
return fmt.Errorf("Contains disallowed characters")
|
||||
return ErrDisallowedChars
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -5,13 +5,18 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Validation error.
|
||||
var (
|
||||
ErrInvalidEmail = errors.New("invalid email address")
|
||||
)
|
||||
|
||||
// Based on https://stackoverflow.com/a/201378
|
||||
var emailRegexp = regexp.MustCompile("^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\\])$")
|
||||
|
||||
// Email validates an email address.
|
||||
func Email(value string) error {
|
||||
if !emailRegexp.MatchString(value) {
|
||||
return errors.New("Invalid email address")
|
||||
return ErrInvalidEmail
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,24 +2,29 @@ package validate
|
||||
|
||||
import "errors"
|
||||
|
||||
// Validation error.
|
||||
var (
|
||||
ErrValueNotAllowed = errors.New("not allowed")
|
||||
)
|
||||
|
||||
// In validates whether a value is found in a slice of allowed values.
|
||||
func In[T comparable](allow []T) func(T) error {
|
||||
func In[T comparable](allow ...T) func(T) error {
|
||||
return func(value T) error {
|
||||
for _, cmp := range allow {
|
||||
if cmp == value {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("Not an allowed value")
|
||||
return ErrValueNotAllowed
|
||||
}
|
||||
}
|
||||
|
||||
// NotIn validates whether a value is not found in a slice of disallowed values.
|
||||
func NotIn[T comparable](allow []T) func(T) error {
|
||||
func NotIn[T comparable](allow ...T) func(T) error {
|
||||
return func(value T) error {
|
||||
for _, cmp := range allow {
|
||||
if cmp == value {
|
||||
return errors.New("Not an allowed value")
|
||||
return ErrValueNotAllowed
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ func TestIn(t *testing.T) {
|
||||
for _, tc := range strTestCases {
|
||||
t.Logf("%q in %s", tc.Input, strings.Join(tc.S, ", "))
|
||||
|
||||
f := In(tc.S)
|
||||
f := In(tc.S...)
|
||||
err := f(tc.Input)
|
||||
|
||||
if tc.Err {
|
||||
@@ -53,7 +53,7 @@ func TestIn(t *testing.T) {
|
||||
}
|
||||
t.Logf("%d in %s", tc.Input, strings.Join(intf, ", "))
|
||||
|
||||
f := In(tc.S)
|
||||
f := In(tc.S...)
|
||||
err := f(tc.Input)
|
||||
|
||||
if tc.Err {
|
||||
|
||||
@@ -2,17 +2,19 @@ package validate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Validation error.
|
||||
var (
|
||||
ErrTooFewChars = errors.New("too few characters")
|
||||
ErrTooManyChars = errors.New("too many characters")
|
||||
)
|
||||
|
||||
// MaxLength validates the length of a string as being less than or equal to a given maximum.
|
||||
func MaxLength(l int) func(string) error {
|
||||
return func(value string) error {
|
||||
if len(value) > l {
|
||||
if l != 1 {
|
||||
return fmt.Errorf("Must not be longer than %d characters", l)
|
||||
}
|
||||
return errors.New("Must not be longer than 1 character")
|
||||
return ErrTooManyChars
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -22,10 +24,7 @@ func MaxLength(l int) func(string) error {
|
||||
func MinLength(l int) func(string) error {
|
||||
return func(value string) error {
|
||||
if len(value) < l {
|
||||
if l != 1 {
|
||||
return fmt.Errorf("Must not be shorter than %d characters", l)
|
||||
}
|
||||
return errors.New("Must not be shorter than 1 character")
|
||||
return ErrTooFewChars
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,13 +5,18 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Validation error.
|
||||
var (
|
||||
ErrInvalidUUID = errors.New("invalid UUID")
|
||||
)
|
||||
|
||||
var uuidRegexp = regexp.MustCompile("^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$")
|
||||
|
||||
// UUID validates a UUID string.
|
||||
// The UUID must be formatted with separators.
|
||||
func UUID(value string) error {
|
||||
if !uuidRegexp.MatchString(value) {
|
||||
return errors.New("Invalid UUID")
|
||||
return ErrInvalidUUID
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user