This document provides guidance for AI coding agents working with this Go validation library.
This is a comprehensive Go validation library that provides:
- Declarative validation using struct tags
- Chainable validation constraints via
itpackage - Conditional checks via
ispackage - Custom error messages and translations
- Validation groups for different contexts
- Support for nested structures and collections
**validation.go**- Main validation entry points andValidatableinterface**validator.go**- Core validation logic and execution**constraints.go**- Constraint interface and execution**it/**- Constraint builders for assertions (e.g.,it.IsEmail(),it.MinLength())**is/**- Boolean check functions (e.g.,is.Email(),is.URL())**validate/**- Standalone validation functions**violations.go**- Validation error handling**message/**- Message templating and translation system
- User calls
validation.Validate(value)or implementsValidatableinterface - Validator discovers constraints from struct tags or
Validate()methods - Constraints execute and collect violations
- Violations are formatted using message templates
When adding new validation constraints:
- Add boolean check to
is/package (if needed)
- Pure functions returning
bool - No error handling, just true/false
- Add constraint builder to
it/package
- Returns a
validation.Constraint - Uses corresponding
is/function - Defines violation message template
- Add tests in
test/constraints_*_cases_test.go
- Use table-driven tests
- Test both valid and invalid cases
- Include edge cases
- Add examples in relevant
example_*_test.gofiles
This project uses table-driven tests extensively:
func TestConstraintName(t *testing.T) {
cases := []struct {
name string
value any
constraint validation.Constraint
wantErr bool
}{
// test cases here
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
// test implementation
})
}
}Black-box tests next to package code (is/, it/, validate/, root validation package, etc.):
- Prefer black-box testing: put tests in
*_test.gofiles that declare**package foo_test** (the package name must end with the_testsuffix), and import**github.com/muonsoft/validation/foo**to exercise only the exported API. - Use
**package foo** in the same directory only when the test must call unexported identifiers (unusual); keep those cases minimal and justified.
Integration-style constraint suites stay in **test/** as today (test/constraints_*_cases_test.go).
When defining new constraints, use message templates:
validation.NewError(
"your constraint unique code",
"{{ label }} must satisfy your constraint",
)Add translations in:
message/translations/english/messages.gomessage/translations/russian/messages.go
- Naming Conventions
- Use clear, descriptive names
- Constraint builders:
it.IsXxx(),it.HasXxx(),it.Xxx() - Check functions:
is.Xxx()
- Documentation
- All exported functions must have godoc comments
- Include examples in
example_*_test.gofiles - Use
// Output:comments for testable examples
- Error Handling
- Use
violations.gotypes for validation errors - Preserve constraint paths for nested validations
- Provide clear, actionable error messages
- Testing
- Aim for high test coverage
- Use table-driven tests
- Test edge cases and error conditions
- Include benchmarks for performance-critical code
- For unit tests in
is/,it/,validate/, etc., use**package foo_test** black-box tests (see Writing Tests)
When adding new functionality:
- Constraints →
it/package - Check functions →
is/package - Standalone validators →
validate/package - Tests → shared constraint suites in
test/; package unit tests in*_test.gowithpackage foo_testwhere applicable - Examples →
example_*_test.goin root - Messages →
message/translations/
type User struct {
Email string
Age int
}
func (u User) Validate() error {
return validation.ValidateValue(
validation.String(u.Email, it.IsEmail()),
validation.Number(u.Age, it.IsGreaterThanOrEqual(18)),
)
}var ErrNotNumeric = errors.New("not numeric")
type NumericConstraint struct {
matcher *regexp.Regexp
}
// it is recommended to use semantic constructors for constraints.
func IsNumeric() NumericConstraint {
return NumericConstraint{matcher: regexp.MustCompile("^[0-9]+$")}
}
func (c NumericConstraint) ValidateString(ctx context.Context, validator *validation.Validator, value *string) error {
// usually, you should ignore empty values
// to check for an empty value you should use it.NotBlankConstraint
if value == nil || *value == "" {
return nil
}
if c.matcher.MatchString(*value) {
return nil
}
// use the validator to build violation with translations
return validator.CreateViolation(ctx, ErrNotNumeric, "This value should be numeric.")
}- Run tests:
go test ./... - Run linter:
golangci-lint run - Check test coverage
- Update documentation if adding public APIs
- Add examples for new features
- Ensure all tests pass in CI
- Update CHANGELOG.md when the change is user-visible (see Changelog below)
The project uses Keep a Changelog in **CHANGELOG.md**.
- Always edit
CHANGELOG.mdin the same branch/PR when your work would matter to library users: new or changed public API, new constraints, message or translation changes, behavior changes, deprecations, removals, security fixes, or notable bug fixes. - Use the
[Unreleased]section at the top. Maintainers move entries under a version heading and date when they cut a release. - Pick the right subsection:
Added,Changed,Deprecated,Removed,Fixed,Security. UseBreakingonly for incompatible changes (or describe breaking impact underChangedif you prefer a single list). - Write for consumers: short, imperative bullets; mention package or symbol names (
it.IsXxx,validate.Xxx,validation.ErrXxx) when helpful. Linking to PRs/issues is optional. - Do not rewrite published versions: never change the bullet list under a released version tag except to fix obvious typos or incorrect facts.
- Skip the changelog only for internal-only changes (refactors, tests, CI, comments) with no user-visible effect.
- Release links: when adding a new version section, update the comparison links at the bottom of the file (
[Unreleased]: ...compare/vX.Y.Z...HEADand[X.Y.Z]: ...releases/tag/vX.Y.Z).
- CHANGELOG.md - Release history for users and upgraders
- README.md - User-facing documentation
- CONTRIBUTING.md - Contribution guidelines
- CODE_OF_CONDUCT.md - Community standards
- pkg.go.dev - Auto-generated API documentation
This is a pure Go library with no external services or infrastructure dependencies. The entire dev workflow is:
- Install deps:
go mod download - Lint:
golangci-lint run(requiresgolangci-lintv2 onPATH; installed to$(go env GOPATH)/bin) - Test:
go test -race ./... - Build:
go build ./...
golangci-lintis installed to$(go env GOPATH)/bin. Ensure this is onPATH(the VM's~/.bashrcexports it).- The CI workflow (
.github/workflows/tests.yml) pinsgolangci-lintat v2.11.4 and Go at ^1.24. Match these versions locally. - The
.golangci.ymluses config version: "2" (golangci-lint v2 format). Do not use golangci-lint v1. - No Makefile, Docker, or docker-compose is used. No services need to be started.