Skip to content

Basic Usage

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 creation
nameField := u.Field("John Doe", r.MinS(2), r.MaxS(50))
ageField := u.Field(25, r.MinN(18), r.MaxN(120))
emailField := u.Field("[email protected]", r.NotZero)

Souuup works with any Go type thanks to generics:

// String validation
username := 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 sets
status := u.Field("active",
r.InS([]string{"active", "inactive", "pending"}),
)

Souuup provides comprehensive built-in rules for common validation scenarios:

// Length validation
r.MinS(3) // Minimum 3 characters
r.MaxS(20) // Maximum 20 characters
r.LenS(6) // Exactly 6 characters
// Content validation
r.ContainsS("@") // Must contain "@"
r.InS([]string{"a", "b", "c"}) // Must be one of these values
r.NotInS([]string{"spam", "bad"}) // Must not be one of these
// Comparison rules
r.MinN(18) // Minimum value 18
r.MaxN(65) // Maximum value 65
r.Gt(0) // Greater than 0
r.Gte(1) // Greater than or equal to 1 (alias for MinN)
r.Lt(100) // Less than 100
r.Lte(99) // Less than or equal to 99 (alias for MaxN)
r.NeqN(0) // Not equal to 0
// Length rules
r.MinLen[string](1) // At least 1 element
r.MaxLen[string](10) // At most 10 elements
r.ExactLen[string](5) // Exactly 5 elements
// Element validation
r.Every(r.MinS(3)) // Every element passes the rule
r.Some(r.MinS(10)) // At least one element passes
r.None(r.ContainsS("bad")) // No element passes the rule
// Content rules
r.Contains("item") // Slice contains this value
// Required fields
r.NotZero // Value is not zero/empty
// Comparison
r.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 field
password := 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"]
}
}
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.

// Good
r.MinS(3), r.MaxS(20), r.ContainsS("@")
// Avoid
ComplexEmailRule() // 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)
}

Custom Rules

Create your own validation rules for specific requirements.

Custom Rules →

HTTP Integration

See how to integrate Souuup with web APIs.

Examples →