docs: explain what distinguishes validate (+ comment audit) #11

Merged
aneurin merged 4 commits from docs-positioning into main 2026-09-07 16:35:55 +00:00
9 changed files with 38 additions and 28 deletions
Showing only changes of commit da971f4df5 - Show all commits
+3 -2
View File
@@ -1,7 +1,8 @@
package validate package validate
// All validates a value using a sequence of validation functions. // All validates a value against a sequence of validation functions,
// If any validation function returns an error, the sequence stops and the error is returned. // stopping and returning the first error. See Collect to run every
// function and report all failures at once.
func All[T any](fs ...func(T) error) func(T) error { func All[T any](fs ...func(T) error) func(T) error {
return func(value T) error { return func(value T) error {
for _, f := range fs { for _, f := range fs {
+1 -1
View File
@@ -20,7 +20,7 @@ func Chars(allow string) func(string) error {
} }
} }
// ExceptChars validates whether a string does not contain disallowed characters. // ExceptChars validates that a string contains none of the given characters.
func ExceptChars(disallow string) func(string) error { func ExceptChars(disallow string) func(string) error {
return func(value string) error { return func(value string) error {
for _, r := range disallow { for _, r := range disallow {
+1 -1
View File
@@ -13,7 +13,7 @@ func ExampleChars() {
} }
func ExampleExceptChars() { func ExampleExceptChars() {
testExceptChars := Chars("0123456789abcdef") testExceptChars := ExceptChars("0123456789abcdef")
fmt.Println(testExceptChars("invalid input")) fmt.Println(testExceptChars("invalid input"))
// Output: contains disallowed characters // Output: contains disallowed characters
} }
+10 -6
View File
@@ -2,10 +2,9 @@ package validate
import "fmt" import "fmt"
// Validation error. // Err is the zero Error. It carries no message, so errors.Is(x, Err) is
var ( // true for any error produced by this package.
Err = Error{} var Err = Error{}
)
// Error represents a validation error. // Error represents a validation error.
type Error struct { type Error struct {
@@ -13,8 +12,9 @@ type Error struct {
Data []any Data []any
} }
// Error retrieves the message of a validation Error. // Error returns the error message. If Data is non-empty, Message is used
// If it has Data, the message will be formatted. // as an fmt.Sprintf format string and Data as its arguments (this is how
// the sentinels with %d/%q verbs are filled in, e.g. via With).
func (e Error) Error() string { func (e Error) Error() string {
if len(e.Data) > 0 { if len(e.Data) > 0 {
return fmt.Sprintf(e.Message, e.Data...) return fmt.Sprintf(e.Message, e.Data...)
@@ -34,6 +34,10 @@ func (e Error) Is(target error) bool {
return false return false
} }
// With returns a copy of the Error with value appended to Data, so it
// lands in the message when Message contains a formatting verb:
//
// ErrMustBeLonger.With(4) // "must contain at least 4 characters"
func (e Error) With(value any) Error { func (e Error) With(value any) Error {
if e.Data == nil { if e.Data == nil {
e.Data = []any{} e.Data = []any{}
+4 -4
View File
@@ -4,7 +4,7 @@ var (
ErrValueNotAllowed = NewError("not allowed") ErrValueNotAllowed = NewError("not allowed")
) )
// In validates whether a value is found in a slice of allowed values. // In validates that a value equals one of the 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 {
@@ -16,10 +16,10 @@ func In[T comparable](allow ...T) func(T) error {
} }
} }
// NotIn validates whether a value is not found in a slice of disallowed values. // NotIn validates that a value equals none of the disallowed values.
func NotIn[T comparable](allow ...T) func(T) error { func NotIn[T comparable](disallow ...T) func(T) error {
return func(value T) error { return func(value T) error {
for _, cmp := range allow { for _, cmp := range disallow {
if cmp == value { if cmp == value {
return ErrValueNotAllowed return ErrValueNotAllowed
} }
+4 -2
View File
@@ -5,7 +5,8 @@ var (
ErrMustBeShorter = NewError("must contain no more than %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. // MaxLength validates that a string is no longer than a given maximum.
// Length is measured in bytes (len), not runes.
Outdated
Review

The original intention was to measure text length. Does this make it more correct to measure runes rather than bytes?

The original intention was to measure text length. Does this make it more correct to measure runes rather than bytes?
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 {
@@ -15,7 +16,8 @@ func MaxLength(l int) func(string) error {
} }
} }
// MinLength validates the length of a string as being greater than or equal to a given minimum. // MinLength validates that a string is at least a given minimum length.
// Length is measured in bytes (len), not runes.
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 {
+9 -9
View File
@@ -8,7 +8,7 @@ var (
) )
// Max validates whether an integer is less than or equal to a given maximum. // 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. // If exclusive is true, an equal value also produces an error.
func Max(n int, exclusive bool) func(int) error { func Max(n int, exclusive bool) func(int) error {
return func(value int) error { return func(value int) error {
if exclusive { if exclusive {
@@ -24,7 +24,7 @@ func Max(n int, exclusive bool) func(int) error {
} }
// MaxFloat32 validates whether a float32 is less than or equal to a given maximum. // 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. // If exclusive is true, an equal value also produces an error.
func MaxFloat32(n float32, exclusive bool) func(float32) error { func MaxFloat32(n float32, exclusive bool) func(float32) error {
return func(value float32) error { return func(value float32) error {
if exclusive { if exclusive {
@@ -40,7 +40,7 @@ func MaxFloat32(n float32, exclusive bool) func(float32) error {
} }
// MaxFloat64 validates whether a float64 is less than or equal to a given maximum. // 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. // If exclusive is true, an equal value also produces an error.
func MaxFloat64(n float64, exclusive bool) func(float64) error { func MaxFloat64(n float64, exclusive bool) func(float64) error {
return func(value float64) error { return func(value float64) error {
if exclusive { if exclusive {
@@ -55,8 +55,8 @@ func MaxFloat64(n float64, exclusive bool) func(float64) error {
} }
} }
// Min validates whether an integer is less than or equal to a given maximum. // Min validates whether an integer is greater than or equal to a given minimum.
// If exclusive is true, an equal value will also produce an error. // If exclusive is true, an equal value also produces an error.
func Min(n int, exclusive bool) func(int) error { func Min(n int, exclusive bool) func(int) error {
return func(value int) error { return func(value int) error {
if exclusive { if exclusive {
@@ -71,8 +71,8 @@ func Min(n int, exclusive bool) func(int) error {
} }
} }
// MinFloat32 validates whether a float32 is less than or equal to a given maximum. // MinFloat32 validates whether a float32 is greater than or equal to a given minimum.
// If exclusive is true, an equal value will also produce an error. // If exclusive is true, an equal value also produces an error.
func MinFloat32(n float32, exclusive bool) func(float32) error { func MinFloat32(n float32, exclusive bool) func(float32) error {
return func(value float32) error { return func(value float32) error {
if exclusive { if exclusive {
@@ -87,8 +87,8 @@ func MinFloat32(n float32, exclusive bool) func(float32) error {
} }
} }
// MinFloat64 validates whether a float64 is less than or equal to a given maximum. // MinFloat64 validates whether a float64 is greater than or equal to a given minimum.
// If exclusive is true, an equal value will also produce an error. // If exclusive is true, an equal value also produces an error.
func MinFloat64(n float64, exclusive bool) func(float64) error { func MinFloat64(n float64, exclusive bool) func(float64) error {
return func(value float64) error { return func(value float64) error {
if exclusive { if exclusive {
+3 -1
View File
@@ -6,7 +6,9 @@ var (
ErrInvalidURL Error = NewError("invalid URL") ErrInvalidURL Error = NewError("invalid URL")
) )
// URL validates a URL. // URL validates that a string is an absolute URL (with a scheme) or an
// absolute path, per net/url.ParseRequestURI. "example.com" with no
// scheme is rejected.
func URL(value string) error { func URL(value string) error {
if _, err := url.ParseRequestURI(value); err != nil { if _, err := url.ParseRequestURI(value); err != nil {
return ErrInvalidURL return ErrInvalidURL
+3 -2
View File
@@ -10,8 +10,9 @@ var (
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 in the canonical 8-4-4-4-12 hyphenated
// The UUID must be formatted with separators. // form. Only lowercase hexadecimal is accepted; the version and variant
// bits are not checked.
func UUID(value string) error { func UUID(value string) error {
if !uuidRegexp.MatchString(value) { if !uuidRegexp.MatchString(value) {
return ErrInvalidUUID return ErrInvalidUUID