Frequently Asked Questions
Find answers to common questions about Souuup. If you don’t see your question here, feel free to open an issue on GitHub.
Getting Started
Section titled “Getting Started”What is Souuup?
Section titled “What is Souuup?”Souuup is a type-safe validation library for Go that uses generics to provide compile-time type checking for validation rules. It’s designed to be flexible, composable, and easy to use for validating complex data structures.
Why choose Souuup over other validation libraries?
Section titled “Why choose Souuup over other validation libraries?”Souuup offers several advantages:
- Type Safety: Uses Go generics for compile-time type checking
- Composable Design: Build complex validation logic from simple, reusable rules
- Clear Error Messages: Structured error reporting that matches your data shape
- Zero Dependencies: Only depends on
golang.org/x/exp
for constraints - Familiar API: Similar to popular validation libraries like Ozzo and Valibot
What Go version does Souuup require?
Section titled “What Go version does Souuup require?”Souuup requires Go 1.21 or later due to its use of generics and type constraints.
How do I install Souuup?
Section titled “How do I install Souuup?”go get github.com/cachesdev/souuup
Then import the packages you need:
import ( "github.com/cachesdev/souuup/u" // Core validation framework "github.com/cachesdev/souuup/r" // Built-in rules)
Usage Questions
Section titled “Usage Questions”How do I validate a simple struct?
Section titled “How do I validate a simple struct?”Create a schema that maps field names to u.Field
definitions:
type User struct { Name string Email string Age int}
func ValidateUser(user User) error { schema := u.Schema{ "name": u.Field(user.Name, r.NotZero, r.MinS(2)), "email": u.Field(user.Email, r.NotZero, r.ContainsS("@")), "age": u.Field(user.Age, r.MinN(18)), }
return u.NewSouuup(schema).Validate()}
How do I make a field optional?
Section titled “How do I make a field optional?”Simply don’t include r.NotZero
in the field’s rules. Other rules will only be checked if the field has a non-zero value:
schema := u.Schema{ "name": u.Field(user.Name, r.NotZero), // Required "phone": u.Field(user.Phone, r.MinS(10)), // Optional, but if provided must be 10+ chars}
How do I validate nested structs?
Section titled “How do I validate nested structs?”Use nested schemas that mirror your data structure:
schema := u.Schema{ "user": u.Schema{ "name": u.Field(data.User.Name, r.NotZero), "address": u.Schema{ "street": u.Field(data.User.Address.Street, r.NotZero), "city": u.Field(data.User.Address.City, r.NotZero), }, },}
How do I validate arrays/slices?
Section titled “How do I validate arrays/slices?”Use slice-specific rules like r.MinLen
, r.Every
, and r.Some
:
schema := u.Schema{ "interests": u.Field(user.Interests, r.MinLen[string](1), // At least one interest r.Every(r.MinS(3)), // Each interest >= 3 characters ), "scores": u.Field(user.Scores, r.Every(r.MinN(0)), // All scores >= 0 r.Every(r.MaxN(100)), // All scores <= 100 ),}
How do I create custom validation rules?
Section titled “How do I create custom validation rules?”Create a function that takes a FieldState[T]
and returns an error:
func ValidEmail(fs u.FieldState[string]) error { email := fs.Value if !strings.Contains(email, "@") { return fmt.Errorf("must be a valid email address") } return nil}
// Use it in a field
For parameterized rules, return a rule function:
func MinWords(count int) u.Rule[string] { return func(fs u.FieldState[string]) error { words := len(strings.Fields(fs.Value)) if words < count { return fmt.Errorf("must contain at least %d words", count) } return nil }}
// UsagedescField := u.Field(description, r.NotZero, MinWords(5))
Error Handling
Section titled “Error Handling”How do I access validation errors?
Section titled “How do I access validation errors?”Validation errors are returned as a structured ValidationError
that can be converted to JSON or a map:
err := validator.Validate()if err != nil { // As JSON string fmt.Println(err.Error())
// As structured data if validationErr, ok := err.(*u.ValidationError); ok { errorMap := validationErr.ToMap() // Process errorMap as needed }}
What does the error structure look like?
Section titled “What does the error structure look like?”Errors are structured to match your data shape:
{ "username": { "errors": ["length is 2, but needs to be at least 3"] }, "address": { "street": { "errors": ["value is required but has zero value"] } }}
How do I check for errors on specific fields?
Section titled “How do I check for errors on specific fields?”Use the ToMap()
method to access errors programmatically:
if err := validator.Validate(); err != nil { if validationErr, ok := err.(*u.ValidationError); ok { errorMap := validationErr.ToMap()
if usernameErrors, exists := errorMap["username"]; exists { if errors, exists := usernameErrors["errors"]; exists { fmt.Printf("Username errors: %v", errors) } } }}
Advanced Usage
Section titled “Advanced Usage”How do I validate fields conditionally?
Section titled “How do I validate fields conditionally?”Create rules that check conditions:
func RequiredIf(condition bool) u.Rule[string] { return func(fs u.FieldState[string]) error { if condition && fs.Value == "" { return fmt.Errorf("field is required") } return nil }}
// Usageschema := u.Schema{ "country": u.Field(user.Country, r.NotZero), "state": u.Field(user.State, RequiredIf(user.Country == "US")),}
How do I validate one field against another?
Section titled “How do I validate one field against another?”Pass both values to your custom rule:
func PasswordsMatch(password, confirm string) u.Rule[string] { return func(fs u.FieldState[string]) error { if password != confirm { return fmt.Errorf("passwords do not match") } return nil }}
schema := u.Schema{ "password": u.Field(form.Password, r.NotZero, r.MinS(8)), "confirm": u.Field(form.Confirm, PasswordsMatch(form.Password, form.Confirm)),}
How do I validate database constraints (uniqueness, etc.)?
Section titled “How do I validate database constraints (uniqueness, etc.)?”Create rules that perform database lookups:
type UserService struct { db *sql.DB}
func (us *UserService) UniqueEmail(fs u.FieldState[string]) error { var count int err := us.db.QueryRow("SELECT COUNT(*) FROM users WHERE email = ?", fs.Value).Scan(&count) if err != nil { return fmt.Errorf("failed to check email uniqueness") }
if count > 0 { return fmt.Errorf("email address is already in use") }
return nil}
// UsageuserService := &UserService{db: db}emailField := u.Field(email, r.NotZero, ValidEmail, userService.UniqueEmail)
How do I reuse validation schemas?
Section titled “How do I reuse validation schemas?”Create functions that return schemas:
func AddressSchema(addr Address) u.Schema { return u.Schema{ "street": u.Field(addr.Street, r.NotZero), "city": u.Field(addr.City, r.NotZero), "country": u.Field(addr.Country, r.NotZero, r.LenS(2)), }}
// Use in multiple placesschema := u.Schema{ "billing_address": AddressSchema(user.BillingAddress), "shipping_address": AddressSchema(user.ShippingAddress),}
Performance and Best Practices
Section titled “Performance and Best Practices”Is Souuup performant?
Section titled “Is Souuup performant?”Yes! Souuup is designed for performance:
- Zero allocations for most validation operations
- Compile-time optimizations through generics
- Early termination on validation failures
- Minimal overhead compared to reflection-based libraries
Should I create validators once or per request?
Section titled “Should I create validators once or per request?”For best performance, create validation logic per request but reuse rule functions:
// Reusable rulevar emailRule = func(fs u.FieldState[string]) error { // validation logic}
// Create schema per validation (cheap)func ValidateUser(user User) error { schema := u.Schema{ "email": u.Field(user.Email, emailRule), // Reuse rule } return u.NewSouuup(schema).Validate()}
How do I organize validation code?
Section titled “How do I organize validation code?”Consider these patterns:
// 1. Methods on structsfunc (u User) Validate() error { schema := u.Schema{ "name": u.Field(u.Name, r.NotZero), } return u.NewSouuup(schema).Validate()}
// 2. Separate validation packagepackage validation
func ValidateUser(user User) error { // validation logic}
// 3. Validator structtype UserValidator struct{}
func (uv UserValidator) Validate(user User) error { // validation logic}
What are the best practices for error messages?
Section titled “What are the best practices for error messages?”- Be specific: “length is 2, but needs to be at least 3”
- Be actionable: Tell users what they need to fix
- Be consistent: Use the same language patterns
- Be helpful: Provide context when possible
Troubleshooting
Section titled “Troubleshooting”Why am I getting compile-time errors about type mismatches?
Section titled “Why am I getting compile-time errors about type mismatches?”This is by design! Souuup uses generics to catch type mismatches at compile time:
// This won't compile - string rule on int fieldu.Field(42, r.MinS(3)) // ❌
// This is correctu.Field(42, r.MinN(3)) // ✅
Make sure your rules match your field types.
Why isn’t my validation rule being called?
Section titled “Why isn’t my validation rule being called?”Check these common issues:
-
Field is zero and you’re not using
r.NotZero
:// This won't validate an empty stringu.Field("", CustomRule)// This willu.Field("", r.NotZero, CustomRule) -
Rule has incorrect signature:
// Wrongfunc BadRule(value string) error { ... }// Correctfunc GoodRule(fs u.FieldState[string]) error { ... }
How do I debug validation issues?
Section titled “How do I debug validation issues?”-
Check each rule individually:
// Test one rule at a timefield := u.Field(value, r.MinS(3))err := u.NewSouuup(u.Schema{"test": field}).Validate() -
Print intermediate values:
func DebugRule(fs u.FieldState[string]) error {fmt.Printf("Validating value: %q\n", fs.Value)// your validation logicreturn nil} -
Use structured error output:
if err := validator.Validate(); err != nil {fmt.Printf("Validation errors: %s\n", err.Error())}
Integration
Section titled “Integration”How do I use Souuup with HTTP APIs?
Section titled “How do I use Souuup with HTTP APIs?”func CreateUserHandler(w http.ResponseWriter, r *http.Request) { var user User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return }
if err := ValidateUser(user); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]any{ "success": false, "errors": err.(*u.ValidationError).ToMap(), }) return }
// Process valid user...}
How do I use Souuup with popular Go web frameworks?
Section titled “How do I use Souuup with popular Go web frameworks?”func createUser(c *gin.Context) { var user User if err := c.ShouldBindJSON(&user); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return }
if err := ValidateUser(user); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "success": false, "errors": err.(*u.ValidationError).ToMap(), }) return }
// Process user... c.JSON(http.StatusCreated, gin.H{"success": true})}
func createUser(c echo.Context) error { var user User if err := c.Bind(&user); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) }
if err := ValidateUser(user); err != nil { return c.JSON(http.StatusBadRequest, map[string]any{ "success": false, "errors": err.(*u.ValidationError).ToMap(), }) }
// Process user... return c.JSON(http.StatusCreated, map[string]any{"success": true})}
func createUser(c *fiber.Ctx) error { var user User if err := c.BodyParser(&user); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) }
if err := ValidateUser(user); err != nil { return c.Status(400).JSON(fiber.Map{ "success": false, "errors": err.(*u.ValidationError).ToMap(), }) }
// Process user... return c.JSON(fiber.Map{"success": true})}
Can I use Souuup with gRPC?
Section titled “Can I use Souuup with gRPC?”Yes! Validate your protobuf messages after unmarshaling:
func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) { // Convert protobuf to internal struct user := User{ Name: req.Name, Email: req.Email, Age: int(req.Age), }
// Validate if err := ValidateUser(user); err != nil { return nil, status.Errorf(codes.InvalidArgument, "validation failed: %v", err) }
// Process user...}
Contributing
Section titled “Contributing”How can I contribute to Souuup?
Section titled “How can I contribute to Souuup?”We welcome contributions! Here are ways to help:
- Report bugs by opening issues on GitHub
- Suggest features through GitHub discussions
- Submit pull requests for bug fixes or new features
- Improve documentation by submitting corrections or additions
- Share examples of how you’re using Souuup
What should I include in a bug report?
Section titled “What should I include in a bug report?”Please include:
- Go version and Souuup version
- Minimal code example that reproduces the issue
- Expected vs actual behavior
- Error messages (if any)
How do I suggest a new built-in rule?
Section titled “How do I suggest a new built-in rule?”Open an issue with:
- Description of the validation need
- Example use cases
- Proposed API design
- Whether it’s domain-specific or generally useful
Still have questions? Open an issue on GitHub or check out our examples for more practical guidance!