govalid

repository·main·Indexed 42 days ago

https://github.com/sivchari/govalid

A high-performance, zero-allocation, type-safe validation library for Go. It uses code generation from struct markers to outperform reflection-based validators by up to 44x. Features include field validators (required, gt, lt, enum), format validators (email, url, uuid), and advanced cross-field validation using Common Expression Language (CEL).

Tokens
12K
Snippets
51
Records
61
Agent score
52%

What's inside govalid

  1. Layer validation: Combining generated validation with business logic

    main

    A best practice when using govalid is to layer your validation in three stages:

    1. Generated validation: Use govalid markers for basic field constraints (type, length, format, presence).
    2. Business logic: Implement manual checks for complex rules that involve relationships between multiple fields (e.g., "if Priority is High, then AssignedToRole cannot be Guest").
    3. Integration validation: Validate against external dependencies or state.

    Example pattern:

    func CreateTask(task *Task) error {
        // 1. Run generated validation
        if err := ValidateTask(task); err != nil {
            return err
        }
        
        // 2. Run business logic validation
        if task.Priority == PriorityHigh && task.AssignedToRole == RoleGuest {
            return fmt.Errorf("high priority tasks cannot be assigned to guests")
        }
        return nil
    }
  2. How govalid works: Marker-based code generation

    main

    govalid uses a code-generation workflow to provide high-performance, zero-allocation validation:

    1. Define Structs with Markers: Add //govalid:<rule> comments directly above struct fields.
    2. Generate Code: Run the govalid CLI against your package (e.g., govalid ./...).
    3. Use Generated Functions: The CLI generates a Validate<StructName>(t *<StructName>) error function and specific error variables for each validation rule.

    Error Naming Convention: To prevent naming conflicts, error variables are prefixed with the struct name. For a struct User with a field Name and a required marker, the generated error will be ErrUserNameRequiredValidation.

    Performance Note: Because the code is generated, it uses direct field access instead of reflection, resulting in zero heap allocations and significantly higher performance compared to reflection-based libraries.

    // 1. Define your struct with markers
    type User struct {
        //govalid:required
        Name string `json:"name"`
        
        //govalid:email
        Email string `json:"email"`
        
        //govalid:gte=18
        Age int `json:"age"`
        
        //govalid:maxlength=100
        Bio string `json:"bio"`
    }
    
    // 2. Generate validation code
    // Run: govalid ./...
    
    // 3. Use the generated validation
    func main() {
        user := &User{
            Name:  "Alice",
            Email: "alice@example.com",
            Age:   25,
            Bio:   "Software developer",
        }
        
        if err := ValidateUser(user); err != nil {
            log.Fatal(err)
        }
        
        fmt.Println("User is valid!")
    }
  3. Use `govalid:cel` for complex validation logic

    main

    The //govalid:cel=EXPRESSION marker allows you to use Google's Common Expression Language (CEL) for advanced validation.

    Key Details:

    • Variable: Use the keyword value to refer to the current field's value.
    • Scope: CEL validation follows a zero-reflection philosophy. Cross-field validation (accessing other struct fields) is not supported.
    • Capabilities: Supports numeric comparisons, string size checks (size(value)), pattern matching (value.contains()), and list operations.
    type Config struct {
        // Numeric validation
        //govalid:cel=value > 0.0 && value <= 100.0
        Score float64 `json:"score"`
        
        // String length validation
        //govalid:cel=size(value) >= 3 && size(value) <= 50
        Username string `json:"username"`
        
        // Pattern matching
        //govalid:cel=value.contains('@')
        Email string `json:"email"`
        
        // List validation
        //govalid:cel=size(value) > 0 && size(value) <= 10
        Tags []string `json:"tags"`
    }
  4. How collection validation works

    main

    govalid provides full support for validating collections, including maps, channels, slices, and arrays.

    type UserList struct {
        //govalid:maxitems=10
        Users []User
    
        //govalid:minitems=1
        UserMap map[string]User
    
        //govalid:maxitems=5
        UserChan chan User
    }
  5. Getting Started with Simple Validation

    main

    To use govalid, follow these three steps:

    1. Define your struct: Add //go:generate govalid . to your file and use //govalid:<rule> markers on struct fields to specify validation logic.
    2. Generate validation code: Run go generate . in your terminal. This creates a new file (e.g., main_govalid.go) containing a type-safe validation function named Validate<StructName>.
    3. Use the validator: Call the generated Validate<StructName>(instance) function. It returns an error if validation fails, providing clear, descriptive error messages.

    Example markers used:

    • //govalid:required: Field must not be empty/zero.
    • //govalid:email: Must be a valid email format.
    • //govalid:gte=0: Must be greater than or equal to 0.
    • //govalid:lte=120: Must be less than or equal to 120.
    //go:generate govalid .
    
    type User struct {
    	//govalid:required
    	Name string `json:"name"`
    	
    	//govalid:required
    	//govalid:email
    	Email string `json:"email"`
    	
    	//govalid:gte=0
    	//govalid:lte=120
    	Age int `json:"age"`
    }
    
    func main() {
    	user := &User{Name: "John", Email: "john@example.com", Age: 30}
    	if err := ValidateUser(user); err != nil {
    		log.Fatal(err)
    	}
    }
  6. Build the documentation for production

    main

    To generate a production-ready version of the documentation site, navigate to the docs directory and run the Hugo build command with the --minify flag. The resulting static files will be located in the public/ directory.

    cd docs
    hugo --minify
  7. Integrate govalid into a Web API

    main

    You can integrate govalid into HTTP handlers to validate incoming JSON request bodies.

    1. Define a request struct with //govalid markers.
    2. Use json.NewDecoder(r.Body).Decode(&req) to parse the request.
    3. Call the generated Validate<RequestStruct>(&req) function.
    4. If validation fails, return an http.StatusBadRequest with the error message.

    This approach provides type-safe, zero-allocation validation suitable for high-performance APIs.

    //go:generate govalid .
    
    type CreateUserRequest struct {
        //govalid:required
        //govalid:minlength=2
        //govalid:maxlength=50
        Name string `json:"name"`
        
        //govalid:required
        //govalid:email
        Email string `json:"email"`
    }
    
    func createUserHandler(w http.ResponseWriter, r *http.Request) {
        var req CreateUserRequest
        if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
            http.Error(w, "Invalid JSON", http.StatusBadRequest)
            return
        }
    
        if err := ValidateCreateUserRequest(&req); err != nil {
            http.Error(w, fmt.Sprintf("Validation failed: %v", err), http.StatusBadRequest)
            return
        }
        // ... proceed with valid request
    }
  8. Use context-aware validation

    main

    For scenarios like HTTP handlers where you need to respect request cancellation or timeouts, use ValidateContext(ctx). This method returns standard context errors like context.DeadlineExceeded or context.Canceled.

    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    defer cancel()
    
    if err := p.ValidateContext(ctx); err != nil {
        switch err {
        case context.DeadlineExceeded:
            log.Println("Validation timed out")
        case context.Canceled:
            log.Println("Validation cancelled")
        default:
            log.Printf("Validation failed: %v", err)
        }
    }
  9. Quick Start: Generate and use validation code

    main

    Follow these three steps to implement validation in your project:

    1. Define Your Struct: Use //govalid: markers on fields or the struct itself.
    2. Generate Validation Code: Run the CLI tool against your package.
    3. Use the Validator: Call the generated .Validate() method on your struct instance.
    // 1. Define
    //govalid:required
    type Person struct {
        Name  string `json:"name"`
        //govalid:email
        Email string `json:"email"`
    }
    
    // 2. Run in terminal: govalid ./...
    
    // 3. Use in code
    func main() {
        p := &Person{Name: "John", Email: "invalid-email"}
    
        if err := p.Validate(); err != nil {
            log.Printf("Validation failed: %v", err)
        }
    }
    //govalid:required
    type Person struct {
        Name  string `json:"name"`
        //govalid:email
        Email string `json:"email"`
    }
    
    func main() {
        p := &Person{Name: "John", Email: "invalid-email"}
    
        if err := p.Validate(); err != nil {
            log.Printf("Validation failed: %v", err)
        }
    }
  10. Handle validation errors

    main

    govalid provides two ways to handle errors:

    Single Error

    Standard usage returns an error if validation fails.

    if err := p.Validate(); err != nil {
        log.Printf("Validation failed: %v", err)
    }

    Multiple Errors

    You can check for specific validation failures using errors.Is with generated error constants (e.g., Err[StructName][FieldName][Marker]Validation).

    if err := p.Validate(); err != nil {
        if errors.Is(err, ErrPersonEmailEmailValidation) {
            // Handle email error
        }
        if errors.Is(err, ErrPersonNameRequiredValidation) {
            // Handle required error
        }
    }
  11. Customize the documentation website

    main

    The documentation site is built with Hugo and can be customized by modifying the following files:

    • Styling: Modify static/css/custom.css to change the visual appearance.
    • Layout: Edit HTML templates located in the layouts/ directory.
    • Content: Edit the Markdown files within the content/ directory.
    • Configuration: Modify hugo.toml for global site settings.