Validation

Kashvi's validation engine lives in pkg/validate. It has zero external dependencies and supports 28 rules via struct tags.

Struct Tags

Add a validate tag to any field:

go
type RegisterInput struct {
    Name            string  `json:"name"             validate:"required,min=2,max=100"`
    Email           string  `json:"email"            validate:"required,email"`
    Age             int     `json:"age"              validate:"required,min=18,max=120"`
    Role            string  `json:"role"             validate:"in=admin,user,editor"`
    Password        string  `json:"password"         validate:"required,min=8"`
    PasswordConfirm string  `json:"password_confirm" validate:"confirmed=password"`
    Website         *string `json:"website"          validate:"nullable,url"`
}

All Validation Rules

RuleExample TagDescription
requiredvalidate:"required"Field must be non-zero
emailvalidate:"email"Valid email address
minvalidate:"min=3"String min length / numeric min value
maxvalidate:"max=100"String max length / numeric max value
betweenvalidate:"between=1,10"Numeric between two values (inclusive)
invalidate:"in=a,b,c"Value must be one of the listed options
not_invalidate:"not_in=bad,worse"Value must NOT be in the list
confirmedvalidate:"confirmed=password"Must match another field's value
urlvalidate:"url"Valid HTTP/HTTPS URL
alphavalidate:"alpha"Letters only
alpha_numvalidate:"alpha_num"Letters and numbers only
alpha_dashvalidate:"alpha_dash"Letters, numbers, -, _
numericvalidate:"numeric"Any number (int or float)
integervalidate:"integer"Must be an integer
booleanvalidate:"boolean"true or false
ipvalidate:"ip"Valid IPv4 or IPv6 address
uuidvalidate:"uuid"Valid UUID
datevalidate:"date"Valid date in YYYY-MM-DD format
date_formatvalidate:"date_format=2006-01-02"Custom Go time layout
starts_withvalidate:"starts_with=https"String prefix check
ends_withvalidate:"ends_with=.go"String suffix check
containsvalidate:"contains=@"Substring check
regexvalidate:"regex=^[A-Z]+"Custom regex pattern
jsonvalidate:"json"Valid JSON string
lenvalidate:"len=6"Exact string length
samevalidate:"same=other_field"Alias for confirmed
differentvalidate:"different=old_password"Must differ from field
nullablevalidate:"nullable,email"Skip other rules if field is empty/nil

Using Validation Directly

In a handler with BindJSON

go
func (ctrl *UserController) Register(c *appctx.Context) {
    var input RegisterInput
    if !c.BindJSON(&input) {
        return // 422 already sent
    }
    // input is valid here
}

Manual validation

go
import "github.com/shashiranjanraj/kashvi/pkg/validate"

errs := validate.Struct(&input)
if validate.HasErrors(errs) {
    // errs = map[string]string{"email": "The email field must be a valid email address."}
}

Error Messages

Errors are returned as map[string]string where the key is the JSON field name:

go
{
  "status": 422,
  "message": "Validation failed",
  "errors": {
    "email": "The email field must be a valid email address.",
    "password": "The password field must be at least 8 characters.",
    "password_confirm": "The password_confirm field must match password."
  }
}

Nullable Fields

Use nullable to skip all other rules when the field is empty/nil:

go
type UpdateInput struct {
    // These are all optional — only validated if provided
    Bio     *string `json:"bio"     validate:"nullable,max=500"`
    Website *string `json:"website" validate:"nullable,url"`
    Age     *int    `json:"age"     validate:"nullable,min=18"`
}

Combining Rules

Rules are comma-separated and evaluated in order. All failures are collected (not short-circuit):

go
validate:"required,min=8,max=64,alpha_num"