The root package of the module holds the building blocks the subpackages share: the generic Set[T], the Validator and Validatable interfaces with Validate, TryValidate and DeepValidate, the Normalizable interfaces of the domain types, pointer and iterator helpers, the length-limited LenString, the Finder interface, the per-key KeyMutex, and two reflection helpers. Import it when you validate values or structs of go-types values, or need one of these helpers.
import "github.com/domonda/go-types"
The package name is types.
Set[T cmp.Ordered] is the defined map type map[T]struct{}. Its methods delegate to mapset and mirror the abstract set interface of the Go collections proposal (go.dev/issue/80590).
| Function | Description |
|---|---|
NewSet[T cmp.Ordered](vals ...T) Set[T] |
New set with vals, duplicates removed. Never nil. |
SetToSortedSlice[S ~map[T]struct{}, T cmp.Ordered](set S) []T |
Values in ascending order, nil for an empty or nil set. |
SetToRandomizedSlice[S ~map[T]struct{}, T cmp.Ordered](set S) []T |
Values in map iteration order, which is unspecified but not a shuffle. nil for an empty or nil set. |
ReduceSet[S ~map[T]struct{}, T cmp.Ordered, R any](set S, reduceFunc func(last R, val T) R) (result R) |
Fold the values, starting with the zero R, in undefined order. |
ReduceSlice[S ~[]T, T cmp.Ordered, R any](slice S, reduceFunc func(last R, val T) R) (result R) |
Fold the slice elements in order, starting with the zero R. |
SliceContainsAll[T comparable](outer []T, inner ...T) bool |
Every value of inner is in outer. Duplicates in inner are not counted, so SliceContainsAll([]int{1}, 1, 1) is true. True for an empty inner. |
SliceContainsAny[T comparable](outer []T, inner ...T) bool |
Any value of inner is in outer. False for an empty inner. |
| Method | Description |
|---|---|
Len() int |
Number of values, 0 for nil. |
IsEmpty() bool |
True for an empty or nil set. |
IsNull() bool |
True only for a nil set. Implements nullable.Nullable. |
Contains(val T) bool |
val is in the set. |
ContainsAny(vals ...T) bool |
Any of vals is in the set. False without arguments. |
ContainsAll(seq iter.Seq[T]) bool |
Every value of seq is in the set. True for an empty seq. |
ContainsSet(other Set[T]) bool |
Every value of other is in the set. |
GetOne() T |
Some value of the set, or the zero T for an empty set. |
All() iter.Seq[T] |
Iterate in undefined order. |
Sorted() []T |
SetToSortedSlice(set). |
String() string |
Sorted values formatted with %#v: [1, 2, 3], ["a", "b"], <nil> for nil. |
Equal(other Set[T]) bool |
Same values. A nil set equals an empty one. |
Intersects(other Set[T]) bool |
At least one value in common. |
Clone() Set[T] |
New set with the same values, nil for nil. |
Map(mapFunc func(T) (T, bool)) Set[T] |
New set of the mapped values for which mapFunc returns true. Never nil. |
Insert(val T) bool |
Add val, report whether the set changed. |
InsertAll(seq iter.Seq[T]) bool |
Add the values of seq, report whether the set changed. |
Delete(val T) bool |
Remove val, report whether the set changed. |
DeleteAll(seq iter.Seq[T]) bool |
Remove the values of seq, report whether the set changed. |
DeleteFunc(del func(T) bool) bool |
Remove the values where del is true, report whether the set changed. |
Clear() |
Remove all values. |
Union(other Set[T]) Set[T] |
New set with the values of both. |
Intersection(other Set[T]) Set[T] |
New set with the values in both. |
Difference(other Set[T]) Set[T] |
New set with the values of set that are not in other. |
SymmetricDifference(other Set[T]) Set[T] |
New set with the values in exactly one of the two. |
UnionWith(other Set[T]) |
In-place Union. |
IntersectionWith(other Set[T]) |
In-place Intersection. |
DifferenceWith(other Set[T]) |
In-place Difference. |
SymmetricDifferenceWith(other Set[T]) |
In-place SymmetricDifference. |
Implements: json.Marshaler (value receiver), json.Unmarshaler (pointer receiver), fmt.Stringer, nullable.Nullable. There is no sql.Scanner, driver.Valuer, text marshaling or JSONSchema method.
A nil Set is a valid empty set for every method except Insert, InsertAll, UnionWith and SymmetricDifferenceWith, which panic like an assignment to a nil map when they have to add a value. Union, Intersection, Difference, SymmetricDifference and Map always return a new non-nil set.
Difference is the asymmetric difference of the collections proposal. Before the 2026-09-02 release it returned the symmetric difference, which is now SymmetricDifference. The change does not break compilation, so check old call sites.
Two interfaces let a type validate itself:
| Interface | Method | Use it when |
|---|---|---|
Validator |
Valid() bool |
A yes/no answer is enough. |
Validatable |
Validate() error |
The caller should learn why a value is invalid. Any non-nil error means invalid. |
Composition helpers:
| Identifier | Description |
|---|---|
ValidatorFunc |
func() bool that implements Validator. |
StaticValidator |
bool that implements Validator. |
Validators |
[]Validator. Valid() is false if any element is false. nil elements, nil funcs and nil pointers that Validate does not validate are skipped. |
CombinedValidator(validators ...Validator) Validator |
Returns Validators(validators). |
ValidatableFunc |
func() error that implements Validatable. |
StaticValidatable |
struct{ Err error } whose Validate() returns Err. |
Validatables |
[]Validatable. Validate() calls Validate for every element and returns all errors joined with errors.Join, or nil. |
CombinedValidatable(validatables ...Validatable) Validatable |
Returns Validatables(validatables). |
ValidatorAsValidatable |
struct{ Validator } whose Validate() returns an error naming the type and value of the wrapped Validator if it is invalid. |
Entry points:
| Function | Description |
|---|---|
Validate(v any) error |
Validate one value, see the rules below. nil if v implements neither interface. |
TryValidate(v any) (implemented bool, err error) |
Like Validate, plus whether v implements one of the interfaces. |
DeepValidate(v any) []error |
Validate v and everything reachable from it. Returns nil if nothing is invalid. |
ValidOr[T Validator](val, def T) T |
val if val.Valid() is true, else def, which is not validated. |
ValidatedOr[T Validatable](val, def T) T |
val if val.Validate() returns nil, else def, which is not validated. The error is discarded. |
ValidOr and ValidatedOr call the method directly, so unlike Validate, which returns nil for a nil v and skips the values of rule 3 below, they panic for a nil func like ValidatorFunc(nil), a nil interface value, or a nil pointer to a type with a value receiver method.
Validate and TryValidate follow these rules:
- If
vimplementsValidatable, onlyValidate()is called and its error is returned unchanged, even ifvalso implementsValidator. - Otherwise, if
vimplementsValidatorandValid()is false, the error isfmt.Errorf("invalid %T value: %#v", v, v), likeinvalid main.Status value: "bad". The whole value is part of the message, so implementValidatablefor a value that must not be logged, like a password, or that can be large. - A nil pointer whose element type implements the method with a value receiver is not validated:
Validatereturns nil andTryValidatereturnstrue, nil. A method with a pointer receiver is called with the nil pointer, so it can report nil itself. The same goes for a nil func likeValidatorFunc(nil), whose method would call the nil func. A struct whose method is promoted from an embedded nil pointer or interface, likestruct{ *T }with a nil*Tand a value receiver onT, orstruct{ types.Validatable }with a nilValidatable, is not validated either. A panic in a method that the struct declares itself is passed on.
DeepValidate walks v with reflection:
- It calls
Validateonvand then on every reachable value. If only the pointer type implements an interface, like*LenString, it validates through a pointer to the value, or to a copy if the value is not addressable.vitself is not copied: passed by value it is validated with the methods of its own type like byValidate, so a pointer receiverValidatecallingDeepValidate(*receiver)doesn't call itself again. The nested call has its own cycle detection, and an outerDeepValidatereports the field errors twice. - It follows pointers and interfaces to any depth. nil pointers, interfaces and funcs stop the descent.
- It descends into exported struct fields, the exported fields of unexported embedded structs, slice and array elements, and map values. Unexported fields and map keys are not validated. Map keys are sorted with
CompareReflectValue, so the error order is deterministic. - A value that implements one of the interfaces is still descended into, so a struct can report its own error and errors of its fields.
- A pointer, map or slice that is already on the current path is not entered again, so cyclic data is validated once.
- Every error except one of
vitself is prefixed with its path and wraps the original error, soerrors.Isanderrors.Aswork:
| Path element | Example error |
|---|---|
struct field <Name> |
struct field Status: invalid main.Status value: "bad" |
element [<index>] |
struct field Tags -> element [1]: invalid main.Status value: "nope" |
map value [<key %#v>] |
map value ["a"]: invalid main.Status value: "y" |
Use errors.Join(types.DeepValidate(v)...) to get one error, which is nil if the slice is empty.
| Interface | Methods |
|---|---|
Normalizable[T any] |
Normalized() (T, error): the value in its canonical form, or an error. Should be idempotent. |
NormalizableValidator[T any] |
Validator, Normalizable[T], and ValidAndNormalized() bool, true if the value is valid and already canonical. |
These types assert NormalizableValidator at compile time: bank.IBAN, bank.NullableIBAN, bank.BIC, bank.NullableBIC, country.Code, country.NullableCode, email.Address, email.NullableAddress, email.AddressList, email.AddressSet, language.Code, money.Currency, money.NullableCurrency, vat.ID, vat.NullableID, date.Date, date.NullableDate, date.YearMonth, date.NullableYearMonth, date.YearQuarter and date.NullableYearQuarter. The dates take a language hint with NormalizedLang instead of Normalized.
| Function | Description |
|---|---|
FromPtr[T any](ptr *T) T |
*ptr, or the zero T for nil. |
FromPtrOr[T any](ptr *T, defaultValue T) T |
*ptr, or defaultValue for nil. |
| Function | Description |
|---|---|
Yield[V any](value V) iter.Seq[V] |
Sequence of the single value. |
Yield2[K, V any](key K, value V) iter.Seq2[K, V] |
Sequence of the single pair key, value. |
YieldErr[K any](err error) iter.Seq2[K, error] |
Sequence of the single pair zero K, err. |
Seq2NilError[K any](seq iter.Seq[K]) iter.Seq2[K, error] |
Pairs every value of seq with a nil error. |
LenString holds a string with a minimum and maximum length, counted in bytes.
| Identifier | Description |
|---|---|
NewLenString(str string, min, max int) *LenString |
New pointer without validation. |
MustLenString(str string, min, max int) LenString |
New value, panics if Validate fails. |
Validate() error |
Error for a nil pointer, a negative min or max, min > max, or a length outside [min, max]. |
SetString(str string) error |
Set str if its length is within the limits, else return an error and keep the old string. |
ScanString(source string, validate bool) error |
Like SetString if validate is true. With false it sets source without any check. |
String() string |
The string, "<nil>" for a nil pointer. |
MinLen() int / MaxLen() int |
The limits. |
MarshalText() ([]byte, error) / UnmarshalText(text []byte) error |
Text form. UnmarshalText validates like SetString. |
MarshalJSON() ([]byte, error) / UnmarshalJSON(text []byte) error |
JSON string. UnmarshalJSON validates like SetString. |
Implements, all with pointer receivers: Validatable, fmt.Stringer, encoding.TextMarshaler, encoding.TextUnmarshaler, json.Marshaler, json.Unmarshaler, strfmt.Scannable. There is no SQL support.
Finder is implemented by types that locate values in text:
type Finder interface {
FindAllIndex(str []byte, n int) [][]int
}FindAllIndex returns [start, end) byte offsets, so str[start:end] is a match. n < 0 returns all matches, n == 0 none, n > 0 at most n. Implementations: bank.IBANFinder, bank.BICFinder, *date.Finder (from date.NewFinder), vat.IDFinder and money.AmountFinder.
KeyMutex[T comparable] holds one mutex per key. A key's mutex is created by the first Lock and deleted when no goroutine holds or waits for it. The zero value is usable.
| Identifier | Description |
|---|---|
NewKeyMutex[T comparable]() *KeyMutex[T] |
New mutex. Optional, the zero value works too. |
Lock(key T) |
Block until key is free, then lock it. |
Unlock(key T) |
Unlock key. Panics with a message like KeyMutex[string].Unlock("k") called for non locked key if key is not locked. |
IsLocked(key T) bool |
key is locked or a Lock call for it is in progress. Only a snapshot. |
strutil.StrMutex and uu.IDMutex implement the same algorithm for string and uu.ID keys. KeyMutex is the generic version. uu.IDMutex has IsLocked too and adds lock and unlock callbacks, strutil.StrMutex has no IsLocked.
| Function | Description |
|---|---|
CanMarshalJSON(t reflect.Type) bool |
Static check whether encoding/json can encode a value of type t. True for interfaces, types implementing json.Marshaler or encoding.TextMarshaler (also through the pointer type), bool, integer, float and string kinds, arrays, slices, structs, maps with string, integer or text-marshaling keys, and pointers to such types. False for nil, complex, chan, func and unsafe.Pointer. |
CompareReflectValue(a, b reflect.Value) int |
Three-way comparison for slices.SortFunc: numbers by value, strings lexicographically, false < true, complex by real then imaginary part, pointers and channels by address, arrays, slices and structs element by element, interfaces nil first, then by dynamic type name, then by value. The invalid zero reflect.Value sorts first. 0 for different types and kinds like map or func. |
Set[T] has a JSON codec that keeps nil, empty and populated sets apart:
| Go value | json.Marshal |
String() |
|---|---|---|
Set[int](nil) |
null |
<nil> |
Set[int]{} |
[] |
[] |
NewSet(3, 1, 2) |
[1,2,3] |
[1, 2, 3] |
NewSet("b", "a") |
["a","b"] |
["a", "b"] |
MarshalJSON writes elements of a normalizing string type, one that implements types.Normalizable, as stored, not with their own MarshalJSON, so elements whose MarshalJSON changes the spelling, like money.Currency uppercasing, are not merged: NewSet[money.Currency]("eur", "EUR") marshals as ["EUR","eur"] and reads back with both elements. Other elements, also of other string types, are written with their own MarshalJSON.
UnmarshalJSON turns null into a nil set, [] into an empty non-nil set, and an array into a new set without duplicates, elements of a normalizing string type as given and other elements with their own UnmarshalJSON, so its validation still applies. It always assigns a new map, so a map shared with another variable is never changed. A JSON object is an error.
*LenString marshals as its string:
| Go value | json.Marshal |
MarshalText |
|---|---|---|
(*LenString)(nil) |
null |
nil |
NewLenString("abc", 1, 3) |
"abc" |
abc |
LenString field of a struct passed by value |
{} |
not called |
UnmarshalJSON accepts a JSON string within the limits and leaves the value unchanged for null or an error. UnmarshalText accepts any text within the limits.
var s types.Set[int]; s.Insert(1)panics, and JSONnullunmarshals to such a nil set. Create sets withNewSetormake, or checkIsNullafter unmarshalling.TofSet[T]must becmp.Ordered, soSetcan't holduu.IDor structs. Useuu.IDSetor amap[K]struct{}with themapsetfunctions.Setis not safe for concurrent use, like any map.ReduceSetvisits values in undefined order. Only an order independent function like a sum gives a stable result.Set.String()gives[1, 2], whilemapset.Stringgives{1 2}.- A
Validatablemethod wins over aValidatormethod on the same type, soValidatecan accept a value whoseValid()is false. - The error for a failed
Validatorcontains the value formatted with%#v: a pointer shows up as an address, and a secret like a password ends up in the message, for exampleinvalid main.secret value: "pw". ImplementValidatablewith your own message for such types. - A nil func like
types.ValidatorFunc(nil)counts as valid:Validatereturns nil for it, andValidators,ValidatablesandDeepValidateskip it, also if its method could handle nil. So do nil elements ofValidatorsandValidatables. DeepValidatecallsValidatemethods with pointer receivers through a pointer to addressable data, so aValidatethat changes its receiver changes the caller's data when you pass a pointer. Map values and the fields and elements of values passed by value are validated through a copy.LenStringcounts bytes:NewLenString("äö", 0, 3).Validate()fails because"äö"has 4 bytes.- All
LenStringmethods have pointer receivers. ALenStringfield of a struct marshalled by value encodes as{}, andtypes.MustLenString("abc", 1, 3).String()does not compile. Use*LenStringfields or marshal a pointer to the struct. - The zero
LenString{}hasminandmax0 and accepts only"". KeyMutexis not reentrant, andUnlockof a key that is not locked panics with the key in the message.- Sorting values of different types with
CompareReflectValuedoes not panic, but their order is unspecified, because the result is not a strict weak ordering. CanMarshalJSONcan return true for types that fail at runtime: struct fields, slice elements and map values are not inspected, sostruct{ C chan int }passes, and pointer-receiver marshalers are not called for values passed by value.
Sets and their JSON form:
package main
import (
"encoding/json"
"fmt"
types "github.com/domonda/go-types"
)
func main() {
a := types.NewSet(3, 1, 2)
b := types.NewSet(2, 4)
fmt.Println(a.Insert(5), a.Insert(5))
fmt.Println(a.Difference(b), a.SymmetricDifference(b), a.Intersects(b))
for _, s := range []types.Set[int]{nil, {}, a} {
j, _ := json.Marshal(s)
fmt.Println(string(j))
}
var decoded types.Set[string]
_ = json.Unmarshal([]byte(`["b","a","b"]`), &decoded)
fmt.Println(decoded, decoded.Len())
_ = json.Unmarshal([]byte(`null`), &decoded)
fmt.Println(decoded.IsNull())
// Output:
// true false
// [1, 3, 5] [1, 3, 4, 5] true
// null
// []
// [1,2,3,5]
// ["a", "b"] 2
// true
}Validating a struct with DeepValidate:
package main
import (
"errors"
"fmt"
types "github.com/domonda/go-types"
)
type Status string
func (s Status) Valid() bool { return s == "active" || s == "blocked" }
type User struct {
Name *types.LenString
Status Status
Tags []Status
}
func main() {
fmt.Println(types.Validate(Status("unknown")))
ok, err := types.TryValidate(42)
fmt.Println(ok, err)
user := User{
Name: types.NewLenString("Alexander", 1, 5),
Status: "unknown",
Tags: []Status{"active", "nope"},
}
errs := types.DeepValidate(user)
for _, err := range errs {
fmt.Println(err)
}
fmt.Println(errors.Join(errs...) != nil)
// Output:
// invalid main.Status value: "unknown"
// false <nil>
// struct field Name: length 9 of LenString "Alexander" is longer than maximum of 5
// struct field Status: invalid main.Status value: "unknown"
// struct field Tags -> element [1]: invalid main.Status value: "nope"
// true
}Pointers, iterators, LenString and the zero KeyMutex:
package main
import (
"encoding/json"
"fmt"
types "github.com/domonda/go-types"
)
type Profile struct {
Nick types.LenString
}
func main() {
fmt.Println(*new(42), types.FromPtr[int](nil), types.FromPtrOr(nil, "default"))
for v, err := range types.Seq2NilError(types.Yield("only")) {
fmt.Println(v, err)
}
nick := types.NewLenString("", 2, 8)
fmt.Println(json.Unmarshal([]byte(`"gopher"`), nick), nick)
fmt.Println(json.Unmarshal([]byte(`"a"`), nick), nick)
p := Profile{Nick: types.MustLenString("gopher", 2, 8)}
byValue, _ := json.Marshal(p)
byPointer, _ := json.Marshal(&p)
fmt.Println(string(byValue), string(byPointer))
var locks types.KeyMutex[string] // usable without NewKeyMutex
locks.Lock("invoice-1")
fmt.Println(locks.IsLocked("invoice-1"))
locks.Unlock("invoice-1")
fmt.Println(locks.IsLocked("invoice-1"))
// Output:
// 42 0 default
// only <nil>
// <nil> gopher
// length 1 of LenString "a" is shorter than minimum of 2 gopher
// {"Nick":{}} {"Nick":"gopher"}
// true
// false
}- Validation and normalization explained
- How to validate structs
- Sets: nil, empty and the collections proposal
mapset: the set functions behindSet[T],uu.IDSet,strutil.StringSetandemail.AddressSet.set: older helper functions formap[T]struct{}.uu:uu.IDSetfor UUIDs anduu.IDMutex.strutil:StringSet,StrMutexandSplitAndTrimIndex, which several finders use.nullable: thenullable.Nullableinterface thatSet[T]implements.strfmt: thestrfmt.Scannableinterface that*LenStringimplements.- Packages with
Finderimplementations:bank,date,vat,money.