# Form Validators

## Overview

Form Validators are responsible for validating user input data submitted through forms. They ensure data integrity, enforce business rules, and provide structured error feedback before data reaches the business logic layer.

## Purpose

Form Validators serve several key functions:
- **Input Validation** - Verify form data meets required criteria
- **Error Collection** - Gather and structure validation errors
- **Business Rule Enforcement** - Apply domain-specific validation rules
- **Data Integrity** - Ensure data consistency before processing

## Structure

Each form validator typically follows this pattern:

```go
type CreatePlantFormValidator struct {
    Validator *validator.Validator
}

func (this *CreatePlantFormValidator) Validate(form *forms.CreatePlantForm) (*formLib.FormBag, error) {
    bag := this.Validator.MakeFormBag()

    // Perform validations
    this.validateName(form, bag)
    this.validateSpecies(form, bag)
    this.validateSize(form, bag)

    if !this.Validator.IsValid(bag) {
        return bag, this.Validator.NewInvalidFormError()
    }

    return bag, nil
}
```

## Common Validation Patterns

### Required Field Validation

```go
func (this *CreatePlantFormValidator) validateName(form *forms.CreatePlantForm, bag *formLib.FormBag) {
    if strings.TrimSpace(form.Name) == "" {
        this.Validator.AddError(bag, "name", "Name is required")
    }
}
```

### String Length Validation

```go
func (this *CreatePlantFormValidator) validateDescription(form *forms.CreatePlantForm, bag *formLib.FormBag) {
    desc := strings.TrimSpace(form.Description)

    if len(desc) > 500 {
        this.Validator.AddError(bag, "description", "Description cannot exceed 500 characters")
    }

    if len(desc) < 10 {
        this.Validator.AddError(bag, "description", "Description must be at least 10 characters")
    }
}
```

### Format Validation

```go
func (this *CreateUserFormValidator) validateEmail(form *forms.CreateUserForm, bag *formLib.FormBag) {
    email := strings.TrimSpace(form.Email)

    if email == "" {
        this.Validator.AddError(bag, "email", "Email is required")
        return
    }

    emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
    if matched, _ := regexp.MatchString(emailRegex, email); !matched {
        this.Validator.AddError(bag, "email", "Invalid email format")
    }
}
```

### Numeric Range Validation

```go
func (this *CreatePlantFormValidator) validateSize(form *forms.CreatePlantForm, bag *formLib.FormBag) {
    if form.Size < 0 {
        this.Validator.AddError(bag, "size", "Size cannot be negative")
    }

    if form.Size > 1000 {
        this.Validator.AddError(bag, "size", "Size cannot exceed 1000")
    }
}
```

### Boolean Validation

```go
func (this *CreatePlantFormValidator) validatePerennial(form *forms.CreatePlantForm, bag *formLib.FormBag) {
    // Boolean validation is typically handled by form binding
    // Custom logic can be added for business rules
    if form.Perennial && form.Size < 20 {
        this.Validator.AddError(bag, "perennial", "Perennial plants must be at least 20cm")
    }
}
```

## Advanced Validation Patterns

### Cross-Field Validation

```go
func (this *CreatePlantFormValidator) validatePlantingDates(form *forms.CreatePlantForm, bag *formLib.FormBag) {
    if !form.PlantedAt.IsZero() && !form.HarvestedAt.IsZero() {
        if form.HarvestedAt.Before(form.PlantedAt) {
            this.Validator.AddError(bag, "harvested_at", "Harvest date cannot be before planting date")
        }
    }
}
```

### Conditional Validation

```go
func (this *CreatePlantFormValidator) validateHarvestData(form *forms.CreatePlantForm, bag *formLib.FormBag) {
    if form.Edible {
        if form.HarvestSeason == "" {
            this.Validator.AddError(bag, "harvest_season", "Harvest season is required for edible plants")
        }

        if form.DaysToHarvest <= 0 {
            this.Validator.AddError(bag, "days_to_harvest", "Days to harvest must be specified for edible plants")
        }
    }
}
```

### Database Validation (with dependencies)

```go
type CreatePlantFormValidator struct {
    Validator    *validator.Validator
    PlantFetcher *fetchers.PlantFetcher
}

func (this *CreatePlantFormValidator) validateUniqueName(ctx context.Context, form *forms.CreatePlantForm, bag *formLib.FormBag) {
    if form.Name == "" {
        return // Skip if name is empty (handled by required validation)
    }

    existing, exists, err := this.PlantFetcher.FindOneByName(ctx, form.Name)
    if err != nil {
        this.Validator.AddError(bag, "name", "Unable to validate plant name")
        return
    }

    if exists {
        this.Validator.AddError(bag, "name", "Plant name already exists")
    }
}
```

## Integration with Controllers

Form validators are typically used in controller methods:

```go
func (gc *GardenController) Create(c *gin.Context) {
    form := &forms.CreateGardenForm{}

    // Bind form data
    if err := c.ShouldBind(form); err != nil {
        c.JSON(400, gin.H{"error": "Invalid form data"})
        return
    }

    // Validate form
    bag, err := gc.CreateGardenFormValidator.Validate(form)
    if err != nil {
        c.JSON(400, gin.H{
            "error": "Validation failed",
            "errors": bag.Errors,
        })
        return
    }

    // Proceed with business logic...
}
```

## Error Handling

### Error Structure

```go
type FormBag struct {
    Errors map[string][]string
    Valid  bool
}

// Add error to specific field
func (v *Validator) AddError(bag *FormBag, field, message string) {
    if bag.Errors == nil {
        bag.Errors = make(map[string][]string)
    }
    bag.Errors[field] = append(bag.Errors[field], message)
    bag.Valid = false
}
```

### Multiple Errors per Field

```go
func (this *CreateUserFormValidator) validatePassword(form *forms.CreateUserForm, bag *formLib.FormBag) {
    password := form.Password

    if len(password) < 8 {
        this.Validator.AddError(bag, "password", "Password must be at least 8 characters")
    }

    if !strings.ContainsAny(password, "0123456789") {
        this.Validator.AddError(bag, "password", "Password must contain at least one number")
    }

    if !strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
        this.Validator.AddError(bag, "password", "Password must contain at least one uppercase letter")
    }
}
```

## Custom Validation Rules

### Reusable Validation Functions

```go
func (v *Validator) ValidateRequiredString(bag *formLib.FormBag, field, value, message string) {
    if strings.TrimSpace(value) == "" {
        v.AddError(bag, field, message)
    }
}

func (v *Validator) ValidateStringLength(bag *formLib.FormBag, field, value string, min, max int) {
    length := len(strings.TrimSpace(value))

    if length < min {
        v.AddError(bag, field, fmt.Sprintf("Must be at least %d characters", min))
    }

    if length > max {
        v.AddError(bag, field, fmt.Sprintf("Cannot exceed %d characters", max))
    }
}
```

### Using Custom Rules

```go
func (this *CreatePlantFormValidator) Validate(form *forms.CreatePlantForm) (*formLib.FormBag, error) {
    bag := this.Validator.MakeFormBag()

    this.Validator.ValidateRequiredString(bag, "name", form.Name, "Plant name is required")
    this.Validator.ValidateStringLength(bag, "name", form.Name, 2, 100)
    this.Validator.ValidateStringLength(bag, "description", form.Description, 0, 500)

    if !this.Validator.IsValid(bag) {
        return bag, this.Validator.NewInvalidFormError()
    }

    return bag, nil
}
```

## Testing Form Validators

```go
func TestCreatePlantFormValidator_ValidForm(t *testing.T) {
    validator := setupCreatePlantFormValidator()

    form := &forms.CreatePlantForm{
        Name:        "Tomato",
        Species:     "Solanum lycopersicum",
        Size:        50,
        Perennial:   false,
    }

    bag, err := validator.Validate(form)

    assert.NoError(t, err)
    assert.True(t, bag.Valid)
    assert.Empty(t, bag.Errors)
}

func TestCreatePlantFormValidator_InvalidForm(t *testing.T) {
    validator := setupCreatePlantFormValidator()

    form := &forms.CreatePlantForm{
        Name:    "", // Invalid - required
        Size:    -5, // Invalid - negative
    }

    bag, err := validator.Validate(form)

    assert.Error(t, err)
    assert.False(t, bag.Valid)
    assert.Contains(t, bag.Errors, "name")
    assert.Contains(t, bag.Errors, "size")
}
```

## Best Practices

### Validation Order
- Validate required fields first
- Perform format validation before business rules
- Check database constraints last

### Error Messages
- Provide clear, actionable error messages
- Use consistent language across validators
- Avoid exposing internal implementation details

### Performance
- Validate cheapest rules first (fail fast)
- Avoid expensive database calls when possible
- Cache validation results when appropriate

### Maintainability
- Use consistent validation patterns
- Extract common validation logic into reusable functions
- Keep validators focused on single forms

Form Validators ensure data quality and provide user-friendly error feedback, forming a crucial layer in the application's data validation strategy.