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
|
package validate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Validation error.
|
||||||
|
var (
|
||||||
|
ErrDisallowedChars = errors.New("contains disallowed characters")
|
||||||
|
)
|
||||||
|
|
||||||
// Chars validates whether a string contains only allowed characters.
|
// Chars validates whether a string contains only allowed characters.
|
||||||
func Chars(allow string) func(string) error {
|
func Chars(allow string) func(string) error {
|
||||||
return func(value string) error {
|
return func(value string) error {
|
||||||
rs := []rune(value)
|
rs := []rune(value)
|
||||||
for _, r := range rs {
|
for _, r := range rs {
|
||||||
if !strings.ContainsRune(allow, r) {
|
if !strings.ContainsRune(allow, r) {
|
||||||
return fmt.Errorf("Contains disallowed characters")
|
return ErrDisallowedChars
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Validation error.
|
||||||
|
var (
|
||||||
|
ErrInvalidEmail = errors.New("invalid email address")
|
||||||
|
)
|
||||||
|
|
||||||
// Based on https://stackoverflow.com/a/201378
|
// 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])+)\\])$")
|
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.
|
// Email validates an email address.
|
||||||
func Email(value string) error {
|
func Email(value string) error {
|
||||||
if !emailRegexp.MatchString(value) {
|
if !emailRegexp.MatchString(value) {
|
||||||
return errors.New("Invalid email address")
|
return ErrInvalidEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,24 +2,29 @@ package validate
|
|||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
|
// Validation error.
|
||||||
|
var (
|
||||||
|
ErrValueNotAllowed = errors.New("not allowed")
|
||||||
|
)
|
||||||
|
|
||||||
// In validates whether a value is found in a slice of allowed values.
|
// 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 {
|
return func(value T) error {
|
||||||
for _, cmp := range allow {
|
for _, cmp := range allow {
|
||||||
if cmp == value {
|
if cmp == value {
|
||||||
return nil
|
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.
|
// 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 {
|
return func(value T) error {
|
||||||
for _, cmp := range allow {
|
for _, cmp := range allow {
|
||||||
if cmp == value {
|
if cmp == value {
|
||||||
return errors.New("Not an allowed value")
|
return ErrValueNotAllowed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ func TestIn(t *testing.T) {
|
|||||||
for _, tc := range strTestCases {
|
for _, tc := range strTestCases {
|
||||||
t.Logf("%q in %s", tc.Input, strings.Join(tc.S, ", "))
|
t.Logf("%q in %s", tc.Input, strings.Join(tc.S, ", "))
|
||||||
|
|
||||||
f := In(tc.S)
|
f := In(tc.S...)
|
||||||
err := f(tc.Input)
|
err := f(tc.Input)
|
||||||
|
|
||||||
if tc.Err {
|
if tc.Err {
|
||||||
@@ -53,7 +53,7 @@ func TestIn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Logf("%d in %s", tc.Input, strings.Join(intf, ", "))
|
t.Logf("%d in %s", tc.Input, strings.Join(intf, ", "))
|
||||||
|
|
||||||
f := In(tc.S)
|
f := In(tc.S...)
|
||||||
err := f(tc.Input)
|
err := f(tc.Input)
|
||||||
|
|
||||||
if tc.Err {
|
if tc.Err {
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ package validate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"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.
|
// MaxLength validates the length of a string as being less than or equal to a given maximum.
|
||||||
func MaxLength(l int) func(string) error {
|
func MaxLength(l int) func(string) error {
|
||||||
return func(value string) error {
|
return func(value string) error {
|
||||||
if len(value) > l {
|
if len(value) > l {
|
||||||
if l != 1 {
|
return ErrTooManyChars
|
||||||
return fmt.Errorf("Must not be longer than %d characters", l)
|
|
||||||
}
|
|
||||||
return errors.New("Must not be longer than 1 character")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -22,10 +24,7 @@ func MaxLength(l int) func(string) error {
|
|||||||
func MinLength(l int) func(string) error {
|
func MinLength(l int) func(string) error {
|
||||||
return func(value string) error {
|
return func(value string) error {
|
||||||
if len(value) < l {
|
if len(value) < l {
|
||||||
if l != 1 {
|
return ErrTooFewChars
|
||||||
return fmt.Errorf("Must not be shorter than %d characters", l)
|
|
||||||
}
|
|
||||||
return errors.New("Must not be shorter than 1 character")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ import (
|
|||||||
"regexp"
|
"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}$")
|
var uuidRegexp = regexp.MustCompile("^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$")
|
||||||
|
|
||||||
// UUID validates a UUID string.
|
// UUID validates a UUID string.
|
||||||
// The UUID must be formatted with separators.
|
// The UUID must be formatted with separators.
|
||||||
func UUID(value string) error {
|
func UUID(value string) error {
|
||||||
if !uuidRegexp.MatchString(value) {
|
if !uuidRegexp.MatchString(value) {
|
||||||
return errors.New("Invalid UUID")
|
return ErrInvalidUUID
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user