add Required, Prefix, Suffix, Contains, Match validators

Broadens the built-in set with the string/presence checks that came up
most often when reaching for this package:

  - Required[T comparable]  non-zero value
  - Prefix / Suffix / Contains  strings.HasPrefix/HasSuffix/Contains
  - Match(*regexp.Regexp)  arbitrary pattern

All follow the existing shape: a constructor returning func(T) error, a
sentinel error matched with errors.Is, table-driven tests and an Example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 13:21:56 +01:00
co-authored by Claude Sonnet 5
parent 8fab67ff77
commit fb47461d5d
6 changed files with 232 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
package validate
import (
"errors"
"fmt"
"testing"
)
func ExampleRequired() {
notBlank := Required[string]()
fmt.Println(notBlank(""))
// Output: is required
}
func TestRequired(t *testing.T) {
t.Run("string", func(t *testing.T) {
v := Required[string]()
if err := v(""); !errors.Is(err, ErrRequired) {
t.Error("empty string should be required, got", err)
}
if err := v("x"); err != nil {
t.Error("non-empty string should pass, got", err)
}
})
t.Run("int", func(t *testing.T) {
v := Required[int]()
if err := v(0); !errors.Is(err, ErrRequired) {
t.Error("zero should be required, got", err)
}
if err := v(1); err != nil {
t.Error("non-zero should pass, got", err)
}
})
t.Run("pointer", func(t *testing.T) {
v := Required[*int]()
if err := v(nil); !errors.Is(err, ErrRequired) {
t.Error("nil pointer should be required, got", err)
}
n := 0
if err := v(&n); err != nil {
t.Error("non-nil pointer should pass, got", err)
}
})
}