validator

validator wraps go-playground/validator with typed errors, template-based message translation, and custom conditional validation tags.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/validator"

Struct-tag validation on go-playground/validator, with the error handling and message rendering that library leaves to the caller.

v, err := validator.New()

err = v.ValidateStruct(req)

Errors come back joined and typed

Upstream returns vt.ValidationErrors, a slice you type-assert and loop over, formatting each entry yourself. Every service that uses it writes that loop, and they all format differently.

Here the result is an errors.Join aggregate of typed Error values, each carrying the failed tag, the parameter, the namespace, the field name, the kind, and a translated human-readable message.

Two things follow. errors.Is and errors.As work on the result, so a handler can inspect it without a type assertion against the upstream type. And because it is a standard joined error, it flows through ordinary error handling instead of needing a special path.

Tag groups joined by | are iterated individually, so each branch of an OR produces its own error rather than one opaque failure for the whole group.

Message templates

WithErrorTemplates maps a validation tag to a text/template string that receives the Error value, with access to the namespace, field name, tag, parameter, kind and actual value.

ErrorTemplates returns a map covering the built-in go-playground tags plus the custom ones, so overriding one message does not mean writing all of them.

This is what turns Key: 'Req.Email' Error:Field validation for 'Email' failed on the 'email' tag into something a client can display. It also puts the wording in one place rather than in whichever handler produced the error, so messages stay consistent across endpoints.

Field-name tag aliases matter here too: registering a JSON tag alias means messages name email_address as the client knows it, rather than the Go field name.

Custom tags

CustomValidationTags registers the additional validators, including falseif, a conditional negation combinator.

Conditional rules are where struct-tag validation usually runs out. A field required only when another has a particular value, or forbidden in one mode and mandatory in another, cannot be expressed with independent per-field tags. Combinators keep those rules in the tag rather than splitting validation across the tags and a hand-written check that runs somewhere else.

WithCustomValidationFunc and the custom type functions register your own.

Context-aware validation

ValidateStructCtx is the twin of ValidateStruct that carries a context.

That matters for any custom validator doing I/O, such as a uniqueness check against a database. It gives the validator cancellation and deadline propagation, so a validation that queries does not outlive the request that triggered it.

Be cautious about putting I/O in a validator at all: a per-field database round trip on a struct with many fields is easy to add and hard to see.

Where it is used

devlake, sleuth and jirasrv all validate their request payloads through this before sending, so a malformed request fails at the call site rather than as a remote rejection.

Dependencies

Reaches github.com/go-playground/validator.