v - Data Validation Library
v is a powerful and flexible data validation library, providing a chainable API, rich built-in validation rules, custom error messages, and internationalization support. Supports validation for form data, structs, API parameters, and various other scenarios. Minimum supported Go version: 1.21.0.
Installation
go get -u go-slim.dev/vQuick Start
package mainimport ( "fmt" "go-slim.dev/v")func main() { // Simple validation err := v.Validate( v.Value("", "username", "Username").Required(), v.Value("invalid-email", "email", "Email").IsEmail(), ) if err != nil { fmt.Println(err) // Output: Username is required // Email is not a valid email address }}Core Concepts
Validator
A validator is any type that implements the Validatable interface:
type Validatable interface { Validate() error}Value Validator (Valuer)
Valuer is the core value validator for validating a single value:
// Value(value, field name, label)validator := v.Value("alice@example.com", "email", "Email Address")- Value: The actual data to validate
- Field Name: Field identifier (e.g., “email”)
- Label: User-readable field name (e.g., “Email Address”)
Basic Usage
1. Single Value Validation
import "go-slim.dev/v"// Required validationerr := v.Value("", "username", "Username"). Required(). Validate()// Error: Username is required// Email validationerr := v.Value("alice@example.com", "email", "Email"). Required(). IsEmail(). Validate()// Pass// Chained validationerr := v.Value("pass123", "password", "Password"). Required(). MinLength(8). MaxLength(32). Validate()// Error: Password minimum length is 82. Multiple Value Validation
Validate - Collect All Errors
Executes all validators and collects all errors:
err := v.Validate( v.Value("", "username", "Username").Required(), v.Value("invalid", "email", "Email").IsEmail(), v.Value("123", "age", "Age").Between(18, 100),)if err != nil { // err contains all validation errors errs := err.(*v.Errors) // Get first error first := errs.First() fmt.Println(first.String()) // Get errors for specific field emailErrs := errs.Get("email") // Convert to map errMap := errs.ToMap() // map[string][]*v.Error{ // "username": [...], // "email": [...], // }}Check - Fail Fast
Returns immediately on first error:
err := v.Check( v.Value("", "username", "Username").Required(), v.Value("invalid", "email", "Email").IsEmail(), // Won't execute)// Only returns username error3. Map Data Validation
Validate map data from forms or JSON:
data := map[string]any{ "username": "alice", "email": "alice@example.com", "age": 25,}field := v.Map(data)err := v.Validate( field("username", "Username").Required().MinLength(3), field("email", "Email").Required().IsEmail(), field("age", "Age").Required().Between(18, 100),)4. Struct Validation
Implement the Validatable interface:
type User struct { Username string Email string Age int}func (u *User) Validate() error { return v.Validate( v.Value(u.Username, "username", "Username"). Required(). MinLength(3). MaxLength(20), v.Value(u.Email, "email", "Email"). Required(). IsEmail(), v.Value(u.Age, "age", "Age"). Required(). Between(18, 100), )}// Usageuser := &User{Username: "alice", Email: "invalid", Age: 15}if err := user.Validate(); err != nil { // Handle validation errors}Validation Rules
Required Validation
// Required - Value must be non-emptyv.Value("", "name", "Name").Required()// RequiredIf - Conditional requiredisAdmin := truev.Value("", "role", "Role").RequiredIf(isAdmin)// RequiredWith - Depends on other valuesphone := ""email := "alice@example.com"v.Value(phone, "phone", "Phone").RequiredWith([]any{email})// If email is not empty, phone is requiredString Validation
val := v.Value("hello", "text", "Text")// Length validationval.Length(5) // Length equals 5val.MinLength(3) // Minimum length 3val.MaxLength(10) // Maximum length 10val.LengthBetween(3, 10) // Length between 3-10// Content validationval.Contains("ell") // Contains substringval.ContainsAny("aeiou") // Contains any characterval.ContainsRune('h') // Contains specific characterval.Excludes("xyz") // Doesn't contain substringval.ExcludesAll("123") // Doesn't contain any specified charactersval.ExcludesRune('x') // Doesn't contain specific characterval.StartsWith("he") // Starts with...val.StartsNotWith("x") // Doesn't start with...val.EndsWith("lo") // Ends with...val.EndsNotWith("x") // Doesn't end with...// Character typesval.IsAlpha() // Only contains lettersval.IsAlphanumeric() // Only contains letters and numbersval.IsAlphaUnicode() // Only contains letters and Unicode charactersval.IsAlphanumericUnicode() // Only contains letters, numbers, and Unicodeval.IsNumeric() // Numeric stringval.IsAscii() // ASCII charactersval.IsLower() // Lowercaseval.IsUpper() // Uppercase// Format validationval.IsHexadecimal() // Hexadecimalval.IsBase64() // Base64 encodingval.IsBase64URL() // Base64 URL encodingval.IsURLEncoded() // URL encodingval.IsHTML() // HTML contentval.IsHTMLEncoded() // HTML escapingNumeric Validation
val := v.Value(42, "age", "Age")// Comparisonval.Equal(42) // Equalsval.NotEqual(0) // Not equalsval.GreaterThan(18) // Greater thanval.GreaterEqualThan(18) // Greater or equalval.LessThan(100) // Less thanval.LessEqualThan(100) // Less or equalval.Between(18, 100) // In rangeval.NotBetween(0, 17) // Not in range// Type validationval.IsNumber() // Number typeval.IsNumeric() // Numericval.IsBool() // BooleanFormat Validation
// Network-relatedv.Value("alice@example.com", "email", "Email").IsEmail()v.Value("https://example.com", "url", "URL").IsURL()v.Value("192.168.1.1", "ip", "IP Address").IsIP()v.Value("192.168.1.1", "ip", "IP Address").IsIPv4()v.Value("::1", "ip", "IP Address").IsIPv6()v.Value("00:11:22:33:44:55", "mac", "MAC Address").IsMAC()// Phone numberv.Value("+8613800138000", "phone", "Phone").IsE164()v.Value("13800138000", "phone", "Phone").IsPhoneNumber()// UUIDv.Value("550e8400-e29b-41d4-a716-446655440000", "id", "ID").IsUUID()v.Value("...", "id", "ID").IsUUID3()v.Value("...", "id", "ID").IsUUID4()v.Value("...", "id", "ID").IsUUID5()v.Value("01ARZ3NDEKTSV4RRFFQ69G5FAV", "id", "ID").IsULID()// JWTv.Value("eyJhbGc...", "token", "Token").IsJwt()// Hashv.Value("5d41402abc4b2a76b9719d911017c592", "hash", "Hash").IsMD5()v.Value("...", "hash", "Hash").IsSHA256()v.Value("...", "hash", "Hash").IsSHA384()v.Value("...", "hash", "Hash").IsSHA512()// Colorv.Value("#FF5733", "color", "Color").IsHexColor()v.Value("rgb(255, 87, 51)", "color", "Color").IsRgb()v.Value("rgba(255, 87, 51, 0.5)", "color", "Color").IsRgba()v.Value("hsl(9, 100%, 60%)", "color", "Color").IsHsl()v.Value("hsla(9, 100%, 60%, 0.5)", "color", "Color").IsHsla()v.Value("#FF5733", "color", "Color").IsColor() // Any color format// Coordinatesv.Value(39.9042, "lat", "Latitude").IsLatitude()v.Value(116.4074, "lng", "Longitude").IsLongitude()// Timev.Value("2024-01-15", "date", "Date").IsDatetime("2006-01-02")v.Value("Asia/Shanghai", "tz", "Timezone").IsTimezone()// Otherv.Value("1.2.3", "version", "Version").IsSemver()v.Value(`{"key":"value"}`, "data", "Data").IsJson()v.Value("/path/to/file", "path", "Path").IsFile()v.Value("/path/to/dir", "path", "Path").IsDir()Enum Validation
// OneOf - Value must be in liststatus := "pending"v.Value(status, "status", "Status"). OneOf([]any{"pending", "active", "inactive"})// Practical applicationrole := "admin"v.Value(role, "role", "Role"). OneOf([]any{"admin", "user", "guest"})Collection Validation
Every - All Elements Validation
All elements must pass validation:
numbers := []int{1, 2, 3, 4, 5}err := v.Value(numbers, "numbers", "Number List"). Every(func(item *v.Item) any { // item.Index: element index // item.Value: element value return v.Value(item.Value, "number", "Number"). Between(1, 10) }). Validate()// Map validationdata := map[string]int{ "a": 1, "b": 2, "c": 3,}err := v.Value(data, "data", "Data"). Every(func(item *v.Item) any { // item.Key: map key // item.Value: map value return v.Value(item.Value, "value", "Value"). GreaterThan(0) }). Validate()Some - Any Element Validation
At least one element must pass validation:
emails := []string{"invalid", "alice@example.com", "bad"}err := v.Value(emails, "emails", "Email List"). Some(func(item *v.Item) any { return v.Value(item.Value, "email", "Email"). IsEmail() }). Validate()// Pass (at least one valid email)Conditional Validation
When - Conditional Execution
Only execute validation when condition is met:
isAdmin := trueerr := v.Value("moderator", "role", "Role"). When(isAdmin, func(val *v.Valuer) { val.OneOf([]any{"admin", "superadmin"}) }). Validate()// Only validate role when isAdmin is trueMatch - Pattern Matching
Execute different validations based on value:
userType := "email"identifier := "alice@example.com"err := v.Value(identifier, "identifier", "Identifier"). Match(func(m *v.Matcher) { m.Branch("email", func(val *v.Valuer) error { return val.IsEmail().Validate() }) m.Branch("phone", func(val *v.Valuer) error { return val.IsPhoneNumber().Validate() }) m.Fallback(func(val *v.Valuer) error { return v.NewError("invalid_type") }) }). Validate()// Practical: validate based on login typeloginType := "phone"loginValue := "13800138000"err := v.Value(loginValue, "login", "Login Credential"). Match(func(m *v.Matcher) { // Login with email m.Branch("email", func(val *v.Valuer) error { return val.Required().IsEmail().Validate() }) // Login with phone m.Branch("phone", func(val *v.Valuer) error { return val.Required().IsPhoneNumber().Validate() }) // Login with username m.Branch("username", func(val *v.Valuer) error { return val.Required().MinLength(3).Validate() }) // Default case m.Fallback(func(val *v.Valuer) error { return v.NewError("unsupported_login_type") }) }). Validate()Custom Validation
Custom - Custom Rule
// Return boolv.Value("hello", "text", "Text"). Custom("custom_check", func(val any) any { str := val.(string) return len(str) > 3 // true/false })// Return errorv.Value("hello", "text", "Text"). Custom("custom_check", func(val any) any { str := val.(string) if len(str) < 3 { return errors.New("Too short") } return nil // or true })// Return Validatablev.Value(user, "user", "User"). Custom("check_user", func(val any) any { u := val.(*User) return u // Calls u.Validate() })// Practical: check if username already existsv.Value("alice", "username", "Username"). Custom("username_exists", func(val any) any { exists := db.UserExists(val.(string)) if exists { return errors.New("Username already exists") } return true }, v.ErrorFormat("Username {value} is already taken"))Type Validation
import "reflect"// Typeof - Validate reflection typev.Value("hello", "text", "Text"). Typeof(reflect.String)v.Value(123, "num", "Number"). Typeof(reflect.Int)// IsString - Convenient string type validationv.Value("hello", "text", "Text").IsString()Advanced Features
1. Composite Validators
Every - All Validators Must Pass
validator := v.Every( v.Value("alice", "username", "Username").Required(), v.Value("alice@example.com", "email", "Email").IsEmail(),)err := validator.Validate()// All validations must passSome - Any Validator Passes
// User can provide either email or phonevalidator := v.Some( v.Value(email, "email", "Email").IsEmail(), v.Value(phone, "phone", "Phone").IsPhoneNumber(),)err := validator.Validate()// At least one must pass2. IndexBy - Grouped Validation
For multiple parameter groups, only one complete group is required:
var index int// User can provide: email+password OR phone+codeerr := v.IndexBy(&index, [][]any{ {email, password}, // Group 0 {phone, code}, // Group 1}).Validate()if err == nil { switch index { case 0: // Login with email and password case 1: // Login with phone and code }}// Practical applicationtype LoginRequest struct { Email string Password string Phone string Code string}func (r *LoginRequest) Validate() error { var index int err := v.Validate( v.IndexBy(&index, [][]any{ {r.Email, r.Password}, {r.Phone, r.Code}, }), ) if err != nil { return err } // Execute different validation based on index switch index { case 0: return v.Validate( v.Value(r.Email, "email", "Email").Required().IsEmail(), v.Value(r.Password, "password", "Password").Required().MinLength(6), ) case 1: return v.Validate( v.Value(r.Phone, "phone", "Phone").Required().IsPhoneNumber(), v.Value(r.Code, "code", "Code").Required().Length(6), ) } return nil}3. Functional Validation
Wrap - Wrap Validation Function
// Create reusable validatorcheckUsername := v.Wrap("alice", func(val any) error { username := val.(string) if db.UserExists(username) { return errors.New("Username already exists") } return nil})err := checkUsername.Validate()Checker - Function Type
// Checker implements Validatable interfacevar passwordCheck v.Checker = func() error { if password != confirmPassword { return errors.New("Passwords do not match") } return nil}err := v.Validate( v.Value(password, "password", "Password").Required(), passwordCheck, // Can use directly)Error Handling
Error Structure
type Error struct { // Error code (e.g., "required", "is_email") Code() string // Error formatting template Format() string // Formatting parameters Params() map[string]any // Field name Field() string // Field label (user-readable) Label() string // Validated value Value() any // Internal error Internal() error}Custom Error Messages
// Using ErrorFormatv.Value("", "username", "Username"). Required(v.ErrorFormat("Please enter {label}"))// Using ErrorParam to add parametersv.Value("ab", "username", "Username"). MinLength(3, v.ErrorFormat("{label} requires at least {min} characters, currently only {length}"), v.ErrorParam("length", 2), )// Using ErrorCode to customize error codev.Value("", "username", "Username"). Required(v.ErrorCode("custom_required"))Errors Collection
err := v.Validate( v.Value("", "username", "Username").Required(), v.Value("invalid", "email", "Email").IsEmail(),)if err != nil { errs := err.(*v.Errors) // Check if empty if !errs.IsEmpty() { // Get first error first := errs.First() fmt.Println(first.String()) // Get all errors all := errs.All() // Get errors for specific field usernameErrs := errs.Get("username") // Convert to map[field name][]*Error errMap := errs.ToMap() for field, fieldErrs := range errMap { fmt.Printf("%s: %d errors\n", field, len(fieldErrs)) } // String representation (all errors) fmt.Println(errs.String()) }}Internationalization
Setting Default Translator
import "go-slim.dev/v"// Custom translation functionv.SetDefaultTranslator(func(message string, params map[string]any) string { // Load translation from file or database translated := i18n.Translate(message, params) // Replace parameter placeholders for key, value := range params { translated = strings.ReplaceAll( translated, "{"+key+"}", fmt.Sprintf("%v", value), ) } return translated})Built-in Chinese Messages
The v package includes built-in Chinese error messages:
// These error codes have Chinese translations"required" -> "{label} is required""is_email" -> "{label} is not a valid email address""is_phone_number" -> "{label} is not a valid phone number""min_length" -> "{label} minimum length is {min}""between" -> "{label} must be greater than or equal to {min} and less than or equal to {max}"// ... etc.Use Cases
1. Web Form Validation
import ( "go-slim.dev/slim" "go-slim.dev/v")type RegisterForm struct { Username string `json:"username"` Email string `json:"email"` Password string `json:"password"` ConfirmPassword string `json:"confirm_password"` Age int `json:"age"` Terms bool `json:"terms"`}func (f *RegisterForm) Validate() error { return v.Validate( v.Value(f.Username, "username", "Username"). Required(). MinLength(3). MaxLength(20). IsAlphanumeric(), v.Value(f.Email, "email", "Email"). Required(). IsEmail(), v.Value(f.Password, "password", "Password"). Required(). MinLength(8). MaxLength(32), v.Value(f.ConfirmPassword, "confirm_password", "Confirm Password"). Required(). Equal(f.Password, v.ErrorFormat("Passwords do not match"), ), v.Value(f.Age, "age", "Age"). Required(). Between(18, 100), v.Value(f.Terms, "terms", "Terms of Service"). Custom("must_accept", func(val any) any { return val.(bool) == true }, v.ErrorFormat("Must accept terms of service")), )}func registerHandler(c slim.Context) error { var form RegisterForm if err := c.Bind(&form); err != nil { return c.JSON(400, map[string]any{"error": "Invalid request data"}) } if err := form.Validate(); err != nil { errs := err.(*v.Errors) return c.JSON(422, map[string]any{ "errors": errs.ToMap(), }) } // Create user... return c.JSON(200, map[string]any{"success": true})}2. API Parameter Validation
func updateUserHandler(c slim.Context) error { data := map[string]any{ "name": c.FormValue("name"), "email": c.FormValue("email"), "age": c.FormValue("age"), } field := v.Map(data) err := v.Validate( field("name", "Name").Required().MinLength(2), field("email", "Email").Required().IsEmail(), field("age", "Age").When(data["age"] != "", func(val *v.Valuer) { val.Between(18, 100) }), ) if err != nil { return c.JSON(422, map[string]any{"errors": err}) } // Update user... return c.JSON(200, map[string]any{"success": true})}3. Complex Business Validation
type Order struct { UserID int Items []OrderItem PaymentType string CardNumber string AlipayID string TotalAmount float64}type OrderItem struct { ProductID int Quantity int Price float64}func (o *Order) Validate() error { return v.Validate( // User ID must exist v.Value(o.UserID, "user_id", "User ID"). Required(). Custom("user_exists", func(val any) any { return db.UserExists(val.(int)) }), // Order items validation v.Value(o.Items, "items", "Order Items"). Required(). NotEmpty(). Every(func(item *v.Item) any { orderItem := item.Value.(OrderItem) return v.Validate( v.Value(orderItem.ProductID, "product_id", "Product ID"). Required(). Custom("product_exists", func(val any) any { return db.ProductExists(val.(int)) }), v.Value(orderItem.Quantity, "quantity", "Quantity"). Required(). Between(1, 100), v.Value(orderItem.Price, "price", "Price"). Required(). GreaterThan(0), ) }), // Payment method validation v.Value(o.PaymentType, "payment_type", "Payment Method"). Required(). OneOf([]any{"card", "alipay", "wechat"}). Match(func(m *v.Matcher) { // Credit card payment requires card number m.Branch("card", func(val *v.Valuer) error { return v.Value(o.CardNumber, "card_number", "Card Number"). Required(). Length(16). Validate() }) // Alipay payment requires Alipay account m.Branch("alipay", func(val *v.Valuer) error { return v.Value(o.AlipayID, "alipay_id", "Alipay Account"). Required(). Validate() }) }), // Total amount validation v.Value(o.TotalAmount, "total_amount", "Total Amount"). Required(). Custom("amount_match", func(val any) any { calculated := 0.0 for _, item := range o.Items { calculated += item.Price * float64(item.Quantity) } // Allow 0.01 error margin diff := calculated - o.TotalAmount return diff < 0.01 && diff > -0.01 }, v.ErrorFormat("Total amount doesn't match order items")), )}4. Multi-Scenario Validation
type User struct { ID int Username string Email string Password string Role string}// Validation for creationfunc (u *User) ValidateCreate() error { return v.Validate( v.Value(u.Username, "username", "Username"). Required(). MinLength(3). Custom("username_unique", func(val any) any { return !db.UsernameExists(val.(string)) }), v.Value(u.Email, "email", "Email"). Required(). IsEmail(). Custom("email_unique", func(val any) any { return !db.EmailExists(val.(string)) }), v.Value(u.Password, "password", "Password"). Required(). MinLength(8), v.Value(u.Role, "role", "Role"). Required(). OneOf([]any{"user", "admin"}), )}// Validation for updatefunc (u *User) ValidateUpdate() error { return v.Validate( v.Value(u.ID, "id", "User ID"). Required(). Custom("user_exists", func(val any) any { return db.UserExists(val.(int)) }), v.Value(u.Username, "username", "Username"). When(u.Username != "", func(val *v.Valuer) { val.MinLength(3) }), v.Value(u.Email, "email", "Email"). When(u.Email != "", func(val *v.Valuer) { val.IsEmail() }), // Password not required for update v.Value(u.Password, "password", "Password"). When(u.Password != "", func(val *v.Valuer) { val.MinLength(8) }), )}5. Nested Struct Validation
type Address struct { Street string City string ZipCode string}func (a *Address) Validate() error { return v.Validate( v.Value(a.Street, "street", "Street").Required(), v.Value(a.City, "city", "City").Required(), v.Value(a.ZipCode, "zip_code", "Zip Code"). Required(). Length(6). IsNumeric(), )}type Profile struct { Name string Age int Address Address}func (p *Profile) Validate() error { return v.Validate( v.Value(p.Name, "name", "Name").Required(), v.Value(p.Age, "age", "Age").Between(18, 100), // Nested validation &p.Address, // Implements Validatable interface )}API Reference
Core Functions
// Validation executionfunc Validate(validations ...Validatable) errorfunc Check(validations ...Validatable) error// Value validatorfunc Value(value any, field, label string) *Valuer// Map validatorfunc Map(data map[string]any) func(name, label string) *Valuer// Composite validationfunc Every(validators ...Validatable) Checkerfunc Some(validators ...Validatable) Checkerfunc IndexBy(index *int, values [][]any, options ...ErrorOption) Checker// Wrap functionfunc Wrap(value any, check func(any) error) Checker// Error creationfunc NewError(code string, options ...ErrorOption) *Error// Error optionsfunc ErrorFormat(format string) ErrorOptionfunc ErrorParam(key string, value any) ErrorOptionfunc ErrorCode(code string) ErrorOption// Internationalizationfunc SetDefaultTranslator(translator Translator)func DefaultTranslator() TranslatorValuer Methods
// Requiredfunc (v *Valuer) Required(options ...ErrorOption) *Valuerfunc (v *Valuer) RequiredIf(condition bool, options ...ErrorOption) *Valuerfunc (v *Valuer) RequiredWith(values []any, options ...ErrorOption) *Valuer// Stringfunc (v *Valuer) Length(n int, options ...ErrorOption) *Valuerfunc (v *Valuer) MinLength(min int, options ...ErrorOption) *Valuerfunc (v *Valuer) MaxLength(max int, options ...ErrorOption) *Valuerfunc (v *Valuer) LengthBetween(min, max int, options ...ErrorOption) *Valuerfunc (v *Valuer) Contains(substr string, options ...ErrorOption) *Valuerfunc (v *Valuer) StartsWith(prefix string, options ...ErrorOption) *Valuerfunc (v *Valuer) EndsWith(suffix string, options ...ErrorOption) *Valuer// Numericfunc (v *Valuer) Equal(another any, options ...ErrorOption) *Valuerfunc (v *Valuer) Between(min, max any, options ...ErrorOption) *Valuerfunc (v *Valuer) GreaterThan(min any, options ...ErrorOption) *Valuerfunc (v *Valuer) LessThan(max any, options ...ErrorOption) *Valuer// Formatfunc (v *Valuer) IsEmail(options ...ErrorOption) *Valuerfunc (v *Valuer) IsPhoneNumber(options ...ErrorOption) *Valuerfunc (v *Valuer) IsURL(options ...ErrorOption) *Valuerfunc (v *Valuer) IsIP(options ...ErrorOption) *Valuerfunc (v *Valuer) IsUUID(options ...ErrorOption) *Valuer// Collectionfunc (v *Valuer) Every(handle func(item *Item) any, options ...ErrorOption) *Valuerfunc (v *Valuer) Some(handle func(item *Item) any, options ...ErrorOption) *Valuer// Conditionalfunc (v *Valuer) When(condition bool, then func(*Valuer)) *Valuerfunc (v *Valuer) Match(handle func(m *Matcher)) *Valuer// Customfunc (v *Valuer) Custom(code string, check func(val any) any, options ...ErrorOption) *Valuer// Executefunc (v *Valuer) Validate() errorNotes
-
Empty Value Handling:
- Non-
Requiredfields skip subsequent validation when empty - Use
NotEmptyto validate non-empty but not required cases
- Non-
-
Error Collection:
Validatecollects all errorsCheckreturns immediately on first error- Choose appropriate approach to balance performance and user experience
-
Custom Validation Return Values:
trueornil: Validation passesfalse: Validation fails, use default error messageerror: Validation fails, use custom error messageValidatable: Calls itsValidate()method
-
Performance Considerations:
- Custom validations (like database queries) may be slow
- Consider caching validation results in business layer
- Use
Everycarefully with large amounts of data
-
Error Message Placeholders:
{label}: Field label{value}: Field value{field}: Field name- Custom parameters: Add via
ErrorParam
-
Chained Calls:
- All validation methods return
*Valuer, supporting chaining - Finally call
Validate()to execute validation - Or pass as parameter to
v.Validate()
- All validation methods return
-
Type Safety:
- Custom validations require manual type assertion
- Mind handling panics from type assertions
- Use
Typeofto pre-validate types
Related Links
- GitHub Repository
- go-slim.dev/is - Underlying validation function library
- Validator Library Comparison