Warning: Valgopt is still being tested and is not ready for production use.
Valgopt is the bridge between Valgo and Opt. It provides Valgo-style fluent validators for fields represented by:
null.Val[T]: present, but possiblynullomit.Val[T]: a value or unsetomitnull.Val[T]: a value, explicitlynull, or unset
Valgopt preserves the distinction between missing, null, zero, and concrete values while keeping validation in the same valgo.Validation chain as ordinary Go values. It complements Valgo; it does not replace Valgo's validation sessions, error handling, localization, or logical operators.
go get github.com/cohesivestack/valgoptImport Valgo and Valgopt together:
import (
"github.com/cohesivestack/valgo"
"github.com/cohesivestack/valgopt"
)package main
import (
"github.com/aarondl/opt/null"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/cohesivestack/valgo"
"github.com/cohesivestack/valgopt"
)
type UpdateUser struct {
Age null.Val[int] // must contain a value
DisplayName omit.Val[string] // may be omitted
Level omitnull.Val[int] // may be omitted or null
}
func validate(input UpdateUser) *valgo.Validation {
return valgo.New().
Is(
valgopt.NullInt(input.Age, "age").
GreaterThan(0).
LessThan(150),
valgopt.OmitString(input.DisplayName, "display_name").
Not().Set().
OrElse().
LengthBetween(3, 80),
valgopt.OmitNullInt(input.Level, "level").
NilOrUnset().
OrElse().
Between(1, 10),
)
}The three chains intentionally express different state policies:
Agemust be non-null because ordinary value rules only pass when a concrete value exists.DisplayNamemay be unset; when set, it must contain 3–80 characters.Levelmay be null or unset; when it contains a value, it must be between 1 and 10.
Every public constructor follows the same pattern:
valgopt.OmitString(value) // generated field name
valgopt.OmitString(value, "display_name") // explicit name; title is humanized
valgopt.OmitString(value, "display_name", "Name") // explicit name and titleChoose the prefix from the Opt wrapper and the suffix from the Go value:
| Opt wrapper | Constructor prefix | State-specific rules |
|---|---|---|
null.Val[T] |
Null... |
Nil, plus type-specific ...OrNil rules |
omit.Val[T] |
Omit... |
Set, plus type-specific ...OrUnset rules |
omitnull.Val[T] |
OmitNull... |
Set, Nil, NilOrUnset, plus type-specific combined rules |
| Value kind | Constructor suffixes |
|---|---|
| Any value | Any, Typed |
| Comparable value | Comparable |
| Boolean | Boolean |
| String | String |
| Any numeric type | Number |
| Signed integer | Int, Int8, Int16, Int32, Int64 |
| Unsigned integer | Uint, Uint8, Uint16, Uint32, Uint64, Byte |
| Floating point | Float32, Float64 |
| Time | Time |
For example, omitnull.Val[time.Time] uses OmitNullTime, while null.Val[uint16] uses NullUint16.
See the complete interface reference for every constructor and validator method.
Normal value rules such as EqualTo, Between, or LengthBetween require a concrete value. A null or unset wrapper therefore fails those rules unless the chain explicitly accepts that state.
Use these patterns to make the intended policy visible:
| Policy | Pattern |
|---|---|
| Nullable value must be non-null | NullString(v).MinLength(1) |
| Nullable value may be null | NullString(v).Nil().OrElse().MinLength(1) |
| Omittable value must be set | OmitString(v).MinLength(1) |
| Omittable value may be unset | OmitString(v).Not().Set().OrElse().MinLength(1) |
| Omit-null value must contain a value | OmitNullString(v).MinLength(1) |
| Omit-null value is required but may be null | OmitNullString(v).Nil().OrElse().MinLength(1) |
| Omit-null value may be null or unset | OmitNullString(v).NilOrUnset().OrElse().MinLength(1) |
Set() on an omitnull.Val[T] means “not unset,” so an explicitly null value counts as set. There is no Unset() method; express unset as Not().Set().
Or() builds an OR group. OrElse() also short-circuits the rest of the chain when the left side succeeds, which makes it the right operator for “accept this optional state, otherwise validate the value.”
All validator families provide:
Context()for extending a validatorNot()to invert the next ruleOr()andOrElse()for logical compositionPassing()for a custom predicate that receives the original Opt wrapperEqualTo()and additional type-specific rules where applicable
Type-specific interfaces include:
- strings: ordering, empty/blank checks, regular expressions, byte length, and rune length
- numbers: ordering, inclusive ranges, zero checks, sign checks, and slice membership
- floats: numeric rules plus
NaN,Infinite, andFinite - booleans:
True,False, and optional-state variants - time values:
After,AfterOrEqualTo,Before,BeforeOrEqualTo, ranges, and zero checks - comparable values: equality and slice membership
- typed or arbitrary values: reflection-aware equality, nil checks where meaningful, and custom predicates
String byte and character rules are deliberately separate:
var text omit.Val[string]
text.Set("虎視眈々") // 12 bytes, 4 runes
validation := valgo.Is(
valgopt.OmitString(text).
ByteLength(12).
Length(4),
)Use ByteLength, ByteLengthBetween, MinBytes, and MaxBytes for bytes. Use Length, LengthBetween, MinLength, and MaxLength for runes. The older OfByteLength, OfByteLengthBetween, OfLength, and OfLengthBetween names are deprecated.
Passing receives the wrapper rather than only its contained value, so a custom rule can inspect value, null, and unset states:
var code omitnull.Val[string]
code.Set("AB-123")
validation := valgo.Is(
valgopt.OmitNullString(code, "code").
Passing(func(value omitnull.Val[string]) bool {
code, ok := value.Get()
return ok && len(code) == 6
}),
)Prefer built-in rules when one exists because they preserve Valgo's standard error keys and templates.
This repository includes an Agent Skill for implementing and reviewing Valgopt validation. The skill follows the SKILL.md format discovered by the skills CLI:
npx skills add cohesivestack/valgopt --skill valgoptThe skill is self-contained under skills/valgopt. Its detailed interface reference is also the canonical method index linked by this README, avoiding a second duplicated API list.
Valgopt tracks the Valgo API version declared in go.mod. This checkout targets Valgo v0.8.1 and adapts that validator surface to Opt wrappers.
In practice:
- use Valgopt constructors for Opt-backed fields
- use Valgo's
Validation,Is,Check, localization, logical composition, and error APIs - keep Valgopt and Valgo versions aligned when upgrading
- check
go doc github.com/cohesivestack/valgoptfor the exact API in the version selected by your module
Copyright © 2025 Carlos Forero
Valgopt is released under the MIT License.