Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

validator.go

CI Coverage Go Reference Go Report Card GitHub Release License

A Go validation library with composable rules, nested struct support, and detailed error reporting.

Installation

go get github.com/raoptimus/validator.go/v2

Usage

Validate a single value

import validator "github.com/raoptimus/validator.go/v2"

ctx := context.Background()

err := validator.ValidateValue(ctx, "user@example.com",
    validator.NewRequired(),
    validator.NewEmail(),
)
// err == nil

Validate a struct

Field names in RuleSet must match struct field names. Error paths use json tags as aliases.

type SignUpRequest struct {
    Email string `json:"email"`
    Name  string `json:"name"`
    Age   int    `json:"age"`
}

req := SignUpRequest{
    Email: "bad-email",
    Name:  "",
    Age:   200,
}

err := validator.Validate(ctx, &req, validator.RuleSet{
    "Email": {validator.NewRequired(), validator.NewEmail()},
    "Name":  {validator.NewRequired(), validator.NewStringLength(1, 100)},
    "Age":   {validator.NewRequired(), validator.NewNumber(1, 150)},
})

// err.Error() == "email: Email is not a valid email. name: Value cannot be blank. age: Value must be no greater than 150."

Validate a map

data := map[string]any{
    "count": 0,
}

err := validator.Validate(ctx, data, validator.RuleSet{
    "count": {
        validator.NewRequired(),
        validator.NewNumber(1, 100),
    },
})

Nested structs

type Address struct {
    City   string `json:"city"`
    Street string `json:"street"`
}

type User struct {
    Name    string  `json:"name"`
    Address Address `json:"address"`
}

err := validator.Validate(ctx, &User{Name: "Alice"}, validator.RuleSet{
    "Name": {validator.NewRequired()},
    "Address": {
        validator.NewNested(validator.RuleSet{
            "City":   {validator.NewRequired()},
            "Street": {validator.NewRequired(), validator.NewStringLength(1, 255)},
        }),
    },
})

// Errors have dotted paths: "address.city", "address.street"

Validate each element in a slice

err := validator.ValidateValue(ctx, []string{"ok", "", "fine"},
    validator.NewEach(validator.NewStringLength(1, 255)),
)

// Error path: "1" (index of the empty string)

Conditional validation

validator.NewEmail().When(func(ctx context.Context, value any) bool {
    return value != nil && value.(string) != ""
})

Skip on empty / skip on error

// Skip email format check if the value is empty
validator.NewEmail().SkipOnEmpty()

// Skip this rule if a previous rule already failed
validator.NewStringLength(1, 100).SkipOnError()

OR rule

At least one of the rules must pass:

validator.NewOR("Must be a valid email or URL",
    validator.NewEmail(),
    validator.NewURL(),
)

Custom callback

validator.NewCallback(func(ctx context.Context, value string) error {
    if strings.Contains(value, "forbidden") {
        return validator.NewValidationError("Value contains forbidden word.")
    }
    return nil
})

Error handling

err := validator.Validate(ctx, &req, rules)
if err != nil {
    var result validator.Result
    if errors.As(err, &result) {
        // Map of field path -> error messages
        errorMap := result.ErrorMessagesIndexedByPath()
        // e.g. {"email": ["Email is not a valid email."], "age": ["Value must be no greater than 150."]}
    }
}

Multilanguage support

Validation messages are translated automatically based on the language in context. Built-in languages: English (en, default) and Russian (ru).

// Set language in context
ctx := validator.WithLanguage(context.Background(), validator.LanguageRU)

err := validator.ValidateValue(ctx, "", validator.NewRequired())
// err.Error() == "Значение не должно быть пустым."

HTTP middleware example

func LangMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        lang := r.Header.Get("Accept-Language") // "ru", "en", etc.
        ctx := validator.WithLanguage(r.Context(), validator.Language(lang))
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Now any validator call inside the handler will use the language from the request:

func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
    var req CreateRequest
    // ... decode request body ...

    // language is already in r.Context() from the middleware
    err := validator.Validate(r.Context(), &req, validator.RuleSet{
        "Name": {validator.NewRequired()},
    })
    // Accept-Language: ru → "Значение не должно быть пустым."
    // Accept-Language: en → "Value cannot be blank."
}

Register a custom language

func init() {
    validator.Translations.Register(validator.Language("es"), map[string]string{
        validator.MessageRequired: "El valor no puede estar vacío.",
        validator.MessageTooShort: "El valor debe contener al menos {min} caracteres.",
        // ...
    })
}

Check for missing translations

missing := validator.Translations.Missing(validator.Language("es"))
// returns a list of message IDs without translation for the given language

Use DummyTranslator (no translation, placeholder replacement only)

validator.SetTranslator(&validator.DummyTranslator{})

Available Rules

Rule Description
Required Value must not be empty
Number Integer within min/max range
StringLength UTF-8 string length within min/max
Email Valid email format
URL Valid URL format
IP Valid IP address
UUID Valid UUID format
MAC Valid MAC address
MSISDN Valid mobile phone number
OGRN Valid Russian OGRN number
JSON Valid JSON structure
SQL Valid SQL statement
MatchRegularExpression Matches a custom regex
In Value is in a predefined set
InRange Numeric value within a range
Compare Compare against another value (==, !=, <, >, <=, >=)
Nested Validate nested struct fields
Each Validate each element in a slice/array
OR At least one rule must pass
Callback Custom validation function
UniqueValues All elements in a slice are unique
NumericString String is a valid numeric representation
HumanText Valid human-readable text
ImageMeta Image metadata validation
Time Time format and range validation

License

BSD 3-Clause

About

Simple validators for golang without using annotation. There are rules : Email, Each, Number, Time, IP, URL, String, Nested etc

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages