Keep Rules Simple
Write focused rules that do one thing well. Combine multiple simple rules rather than creating complex ones.
// Goodr.MinS(3), r.MaxS(20), r.ContainsS("@")
// AvoidComplexEmailRule() // Does everything
This guide covers the essential patterns and techniques for using Souuup effectively. You’ll learn how to create fields, apply rules, and handle validation results.
Fields are the foundation of Souuup validation. They combine a value with validation rules:
// Basic field creationnameField := u.Field("John Doe", r.MinS(2), r.MaxS(50))ageField := u.Field(25, r.MinN(18), r.MaxN(120))
Souuup works with any Go type thanks to generics:
// String validationusername := u.Field("johndoe", r.NotZero, // Required r.MinS(3), // Min 3 characters r.MaxS(20), // Max 20 characters r.ContainsS("john"), // Must contain "john")
// String setsstatus := u.Field("active", r.InS([]string{"active", "inactive", "pending"}),)
// Integer validationage := u.Field(25, r.MinN(0), // Non-negative r.MaxN(150), // Reasonable max age r.NeqN(0), // Cannot be zero)
// Float validationprice := u.Field(19.99, r.Gt(0.0), // Must be positive r.Lte(9999.99), // Max price)
// Slice validationinterests := u.Field([]string{"coding", "music", "sports"}, r.MinLen[string](1), // At least one interest r.MaxLen[string](5), // At most 5 interests r.Every(r.MinS(3)), // Each interest >= 3 chars)
// Numeric slicesscores := u.Field([]int{85, 92, 78}, r.MinLen[int](1), // At least one score r.Every(r.MinN(0)), // All scores >= 0 r.Every(r.MaxN(100)), // All scores <= 100)
// Boolean validation WIP
Souuup provides comprehensive built-in rules for common validation scenarios:
// Length validationr.MinS(3) // Minimum 3 charactersr.MaxS(20) // Maximum 20 charactersr.LenS(6) // Exactly 6 characters
// Content validationr.ContainsS("@") // Must contain "@"r.InS([]string{"a", "b", "c"}) // Must be one of these valuesr.NotInS([]string{"spam", "bad"}) // Must not be one of these
// Comparison rulesr.MinN(18) // Minimum value 18r.MaxN(65) // Maximum value 65r.Gt(0) // Greater than 0r.Gte(1) // Greater than or equal to 1 (alias for MinN)r.Lt(100) // Less than 100r.Lte(99) // Less than or equal to 99 (alias for MaxN)r.NeqN(0) // Not equal to 0
// Length rulesr.MinLen[string](1) // At least 1 elementr.MaxLen[string](10) // At most 10 elementsr.ExactLen[string](5) // Exactly 5 elements
// Element validationr.Every(r.MinS(3)) // Every element passes the ruler.Some(r.MinS(10)) // At least one element passesr.None(r.ContainsS("bad")) // No element passes the rule
// Content rulesr.Contains("item") // Slice contains this value
// Required fieldsr.NotZero // Value is not zero/empty
// Comparisonr.SameAs(otherValue) // Value equals another value
You can apply multiple rules to a single field. All rules must pass for validation to succeed:
// Multiple rules on one fieldpassword := u.Field(userPassword, r.NotZero, // Required r.MinS(8), // At least 8 characters r.MaxS(128), // At most 128 characters func(fs u.FieldState[string]) error { // Custom rule if !strings.ContainsAny(fs.Value, "0123456789") { return fmt.Errorf("password must contain at least one number") } return nil },)
Schemas organize multiple fields into a cohesive validation structure:
type User struct { Username string Email string Age int Active bool}
func ValidateUser(user User) error { schema := u.Schema{ "username": u.Field(user.Username, r.MinS(3), r.MaxS(20)), "email": u.Field(user.Email, r.NotZero, r.ContainsS("@")), "age": u.Field(user.Age, r.MinN(13), r.MaxN(120)), "active": u.Field(user.Active, r.NotZero), // Must be true }
s := u.NewSouuup(schema) return s.Validate()}
Understanding Souuup’s error structure helps you provide better user feedback:
err := s.Validate()if err != nil { // Cast to ValidationError for detailed access if validationErr, ok := err.(*u.ValidationError); ok { // Access structured error data errorMap := validationErr.ToMap()
// Print JSON representation fmt.Println(err.Error()) // Outputs JSON }}
{ "username": { "errors": ["length is 2, but needs to be at least 3"] }}
{ "username": { "errors": ["length is 2, but needs to be at least 3"] }, "email": { "errors": ["value is required but has zero value"] }}
{ "password": { "errors": [ "length is 4, but needs to be at least 8", "password must contain at least one number" ] }}
type ContactForm struct { Name string Email string Subject string Message string}
func ValidateContactForm(form ContactForm) error { schema := u.Schema{ "name": u.Field(form.Name, r.NotZero, r.MinS(2), r.MaxS(100), ), "email": u.Field(form.Email, r.NotZero, r.ContainsS("@"), r.ContainsS("."), ), "subject": u.Field(form.Subject, r.NotZero, r.MinS(5), r.MaxS(200), ), "message": u.Field(form.Message, r.NotZero, r.MinS(10), r.MaxS(5000), ), }
return u.NewSouuup(schema).Validate()}
type CreateUserRequest struct { Username string `json:"username"` Email string `json:"email"` Password string `json:"password"` Age int `json:"age"` Interests []string `json:"interests"`}
func ValidateCreateUserRequest(req CreateUserRequest) error { schema := u.Schema{ "username": u.Field(req.Username, r.NotZero, r.MinS(3), r.MaxS(20), // Custom rule: only alphanumeric func(fs u.FieldState[string]) error { for _, char := range fs.Value { if !unicode.IsLetter(char) && !unicode.IsDigit(char) { return fmt.Errorf("username must contain only letters and numbers") } } return nil }, ), "email": u.Field(req.Email, r.NotZero, EmailRule, // Custom email validation ), "password": u.Field(req.Password, r.NotZero, r.MinS(8), StrongPasswordRule, // Custom strong password validation ), "age": u.Field(req.Age, r.MinN(13), r.MaxN(120), ), "interests": u.Field(req.Interests, r.MinLen[string](1), r.MaxLen[string](10), r.Every(r.MinS(2)), r.Every(r.MaxS(50)), ), }
return u.NewSouuup(schema).Validate()}
Keep Rules Simple
Write focused rules that do one thing well. Combine multiple simple rules rather than creating complex ones.
// Goodr.MinS(3), r.MaxS(20), r.ContainsS("@")
// AvoidComplexEmailRule() // Does everything
Use Descriptive Field Names
Use clear field names in your schemas that match your domain language. Try and keep your schema in the same shape as your data model.
// Good"email_address": u.Field(email, r.NotZero)
// Avoid"field1": u.Field(email, r.NotZero)
Group Related Validation
Create reusable validation functions for common patterns.
func ValidateEmail(email string) *u.FieldDef[string] { return u.Field(email, r.NotZero, EmailRule)}
Handle Errors Gracefully
Don’t ignore validation errors. Always check and handle them appropriately.
if err := s.Validate(); err != nil { // maybe for a CLI: return fmt.Errorf("validation failed: %w", err) // or for an API: return connect.NewError(connect.CodeInvalidArgument, err)}
Nested Validation
Learn how to validate complex, nested data structures.
Custom Rules
Create your own validation rules for specific requirements.
HTTP Integration
See how to integrate Souuup with web APIs.