Skip to main content

Overview

model — defaults & validation for Go structs

model is a small Go library for applying defaults and validation to structs using field tags.

It offers two entry points:

  • model.New(...) creates a Model[T] bound to a single struct instance.
  • model.NewBinding[T]() creates a reusable Binding[T] that can be applied to many values of the same type.

The library can:

  • Set defaults from struct tags like default:"…" and defaultElem:"…".
  • Validate fields using named rules from validate:"…" and validateElem:"…".
  • Accumulate all issues into a single *validation.Error instead of failing fast.
  • Recurse through nested structs, pointers, slices/arrays, and map values.

It is designed to be small, explicit, and type-safe. You can register custom rules with validation.NewRule(...), while built-ins such as min, max, email, uuid, nonzero, and oneof(...) are available automatically.

Internally, model uses the companion errorc and keys libraries to attach structured metadata to validation and rule errors.

Quick start

package main

import (
"context"
"encoding/json"
"errors"
"fmt"
"time"

"github.com/ygrebnov/model"
modelvalidation "github.com/ygrebnov/model/validation"
)

type User struct {
Name string `default:"Anonymous" validate:"min(3),max(50)"`
Age int `default:"18" validate:"min(1),nonzero"`
Timeout time.Duration `default:"1s"`
}

func main() {
u := User{}

m, err := model.New(&u,
model.WithDefaults[User](),
model.WithValidation[User](context.Background()),
)
if err != nil {
var ve *modelvalidation.Error
if errors.As(err, &ve) {
b, _ := json.MarshalIndent(ve, "", " ")
fmt.Println(string(b))
} else {
fmt.Println("error:", err)
}
return
}

_ = m
fmt.Printf("User after defaults: %+v\n", u)
}

Why use it?

  • Explicit and predictable: defaults only fill zero values; validation collects all failures.
  • Reusable: Binding[T] lets you build one validation/defaulting engine and apply it to many objects.
  • Extensible: add custom rules for concrete types or interfaces.
  • Structured diagnostics: underlying rule errors carry metadata built with errorc and keys.

Common workflows

Single object: Model[T]

Use model.New(...) when you want to bind one struct instance and optionally apply defaults and validation immediately.

Reusable engine: Binding[T]

Use model.NewBinding[T]() when many requests or records share the same struct type.

b, err := model.NewBinding[User]()
if err != nil {
panic(err)
}

u := User{}
if err := b.ValidateWithDefaults(context.Background(), &u); err != nil {
fmt.Println(err)
}

Structured errors and companion projects

Validation failures are returned as *validation.Error, which groups FieldError values by field path. Internally, many of the underlying rule errors use structured metadata built with:

  • errorc for wrapped error context
  • keys for stable structured keys

That makes model a good fit when you want both human-readable messages and machine-friendly error metadata.

See Installation and Examples for more.