Type Safety First
Let the type system catch validation rule mismatches at compile time.
// Correct - types matchu.Field("string", r.MinS(3))u.Field(42, r.MinN(0))
// Won't compile - type mismatchu.Field("string", r.MinN(3)) // ❌
This reference documents the core types and functions that make up the Souuup validation framework. These are the building blocks you’ll use to create validation schemas and handle errors.
Rule[T any]
The fundamental type for validation rules. A rule is a function that takes a field state and returns an error if validation fails.
type Rule[T any] func(FieldState[T]) error
Usage:
// Custom rule examplefunc CustomRule(fs u.FieldState[string]) error { if fs.Value == "forbidden" { return fmt.Errorf("value is not allowed") } return nil}
// Using the rulefield := u.Field("hello", CustomRule)
FieldState[T any]
Contains the value being validated and provides context to validation rules.
type FieldState[T any] struct { Value T // ... internal fields}
Properties:
Value
- The actual value being validatedUsage:
func MyRule(fs u.FieldState[string]) error { value := fs.Value // Access the value // Perform validation logic return nil}
FieldDef[T any]
Represents a field with its value and associated validation rules. Created using the Field
function.
type FieldDef[T any] struct { // ... internal fields}
Interface: Implements Validable
Schema
A map that defines the structure of data to be validated. Can contain both fields and nested schemas.
type Schema map[FieldTag]Validable
Usage:
schema := u.Schema{ "username": u.Field("johndoe", r.MinS(3)), "profile": u.Schema{ "age": u.Field(25, r.MinN(18)), },}
Interface: Implements Validable
Souuup
The main validator instance that executes validation against a schema.
type Souuup struct { // ... internal fields}
ValidationError
Represents validation errors in a hierarchical structure that matches your data schema.
type ValidationError struct { Errors FieldsErrorMap NestedErrors NestedErrorsMap Parent *ValidationError}
Interface: Implements error
u.Field[T any](value T, rules ...Rule[T]) *FieldDef[T]
Creates a validatable field with a value and optional validation rules.
// Simple fieldnameField := u.Field("John Doe")
// Field with validation rules
// Field with custom ruleageField := u.Field(25, r.MinN(18), func(fs u.FieldState[int]) error { if fs.Value > 100 { return fmt.Errorf("age seems unrealistic") } return nil})
Parameters:
value
- The value to validaterules
- Zero or more validation rules to applyReturns: A field definition that can be used in schemas
Type Safety: The rules must match the type of the value (enforced at compile time)
u.NewSouuup(schema Schema) *Souuup
Creates a new validator instance with the provided schema.
schema := u.Schema{ "username": u.Field("johndoe", r.MinS(3)), "age": u.Field(25, r.MinN(18)),}
validator := u.NewSouuup(schema)
Parameters:
schema
- The validation schema to useReturns: A validator instance ready for validation
(s *Souuup) Validate() error
Executes validation against the schema and returns an error if validation fails.
err := validator.Validate()if err != nil { // Handle validation errors fmt.Printf("Validation failed: %s", err.Error())}
Returns:
nil
if validation succeeds*ValidationError
if validation failsUsage Pattern:
s := u.NewSouuup(schema)if err := s.Validate(); err != nil { // Validation failed if validationErr, ok := err.(*u.ValidationError); ok { // Access structured error data errorMap := validationErr.ToMap() }} else { // Validation succeeded}
(ve *ValidationError) Error() string
Returns a JSON string representation of the validation errors.
err := validator.Validate()if err != nil { jsonStr := err.Error() // Output: {"field": {"errors": ["error message"]}}}
(ve *ValidationError) ToMap() ToMapResult
Converts the validation error to a map structure for easier programmatic access.
type ToMapResult map[FieldTag]map[string]any
if err := validator.Validate(); err != nil { if validationErr, ok := err.(*u.ValidationError); ok { errorMap := validationErr.ToMap()
// Check for specific field errors if userErrors, exists := errorMap["username"]; exists { if errors, exists := userErrors["errors"]; exists { fmt.Printf("Username errors: %v", errors) } } }}
(ve *ValidationError) HasErrors() bool
Returns true if there are any validation errors at any level in the error tree.
if validationErr.HasErrors() { // There are validation errors}
(ve *ValidationError) AddError(tag FieldTag, err error)
Adds a validation error for a specific field. Primarily used internally and in custom validation logic.
ve := u.NewValidationError()ve.AddError("username", fmt.Errorf("username is taken"))
u.NewValidationError() *ValidationError
Creates a new empty ValidationError instance.
ve := u.NewValidationError()
Validable
The interface that all validatable entities must implement.
type Validable interface { Validate(*ValidationError, FieldTag) Errors() *ValidationError}
Implementations:
FieldDef[T]
- Individual fieldsSchema
- Nested schemasMethods:
Validate(*ValidationError, FieldTag)
Validates the entity and adds any errors to the provided ValidationError under the given tag.
Errors() *ValidationError
Returns validation errors for this entity.
// For string validationtype StringRule = Rule[string]
// For numeric validation (integers and floats)type NumericRule[T Numeric] = Rule[T]
// For slice validationtype SliceRule[T any] = Rule[[]T]
// Field identifiertype FieldTag = string
// Collection of rule errors for a fieldtype RuleErrors = []RuleError
// Map of field errorstype FieldsErrorMap = map[FieldTag]RuleErrors
// Map of nested errorstype NestedErrorsMap = map[FieldTag]*ValidationError
Numeric
A type constraint for numeric types (integers and floats).
type Numeric interface { constraints.Float | constraints.Integer}
Supported Types:
int
, int8
, int16
, int32
, int64
)uint
, uint8
, uint16
, uint32
, uint64
)float32
, float64
)// 1. Define your data structuretype User struct { Name string Email string Age int}
// 2. Create validation schemafunc 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), r.MaxN(120)), }
// 3. Create validator and validate return u.NewSouuup(schema).Validate()}
// 4. Use in your applicationif err := ValidateUser(user); err != nil { // Handle validation error}
func CreateUserSchema(user User) u.Schema { return u.Schema{ "personal": u.Schema{ "name": u.Field(user.Name, r.NotZero), "age": u.Field(user.Age, r.MinN(18)), }, "contact": u.Schema{ "email": u.Field(user.Email, r.NotZero), "phone": u.Field(user.Phone, r.MinS(10)), }, }}
func HandleValidationError(err error) { if validationErr, ok := err.(*u.ValidationError); ok { errorMap := validationErr.ToMap()
for field, fieldErrors := range errorMap { if errors, exists := fieldErrors["errors"]; exists { fmt.Printf("Field %s: %v\n", field, errors) }
// Handle nested errors recursively for nestedField, nestedData := range fieldErrors { if nestedField != "errors" { fmt.Printf("Nested field %s.%s has errors\n", field, nestedField) } } } }}
Type Safety First
Let the type system catch validation rule mismatches at compile time.
// Correct - types matchu.Field("string", r.MinS(3))u.Field(42, r.MinN(0))
// Won't compile - type mismatchu.Field("string", r.MinN(3)) // ❌
Schema Organization
Structure your schemas to mirror your data for clarity.
u.Schema{ "user": u.Schema{ "profile": u.Schema{ "name": u.Field(...), }, },}
Error Handling
Always check validation results and handle errors appropriately.
if err := validator.Validate(); err != nil { // Handle the error - don't ignore it return fmt.Errorf("validation failed: %w", err)}
Reusable Schemas
Create reusable validation functions for common patterns.
func PersonSchema(person Person) u.Schema { return u.Schema{ "name": u.Field(person.Name, r.NotZero), "age": u.Field(person.Age, r.MinN(0)), }}
Built-in Rules
Explore all the built-in validation rules available in Souuup.
Custom Rules
Learn how to create your own validation rules.
Examples
See practical examples of using the core API.