> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature Flags

> Feature flag middleware for controlled feature rollouts.

## Overview

The `feature` middleware provides feature flag support for controlled rollouts, A/B testing, and gradual feature deployment. It supports static flags, in-memory providers, and custom providers.

## Installation

```go theme={null}
import "github.com/go-mizu/mizu/middlewares/feature"
```

## Quick Start

```go theme={null}
app := mizu.New()

// Static feature flags
app.Use(feature.New(feature.Flags{
    "dark_mode":    &feature.Flag{Name: "dark_mode", Enabled: true},
    "new_checkout": &feature.Flag{Name: "new_checkout", Enabled: false},
}))

app.Get("/", func(c *mizu.Ctx) error {
    if feature.IsEnabled(c, "dark_mode") {
        return c.JSON(200, darkModeResponse)
    }
    return c.JSON(200, normalResponse)
})
```

## Configuration

| Option     | Type       | Default | Description      |
| ---------- | ---------- | ------- | ---------------- |
| `Provider` | `Provider` | Static  | Flag provider    |
| `Flags`    | `Flags`    | -       | Static flags map |

## Flag Structure

```go theme={null}
type Flag struct {
    Name        string
    Enabled     bool
    Description string
    Metadata    map[string]any
}
```

## Examples

### Static Flags

```go theme={null}
app.Use(feature.New(feature.Flags{
    "feature_a": &feature.Flag{
        Name:        "feature_a",
        Enabled:     true,
        Description: "New dashboard design",
    },
    "feature_b": &feature.Flag{
        Name:    "feature_b",
        Enabled: false,
    },
}))
```

### Check Flag in Handler

```go theme={null}
app.Get("/dashboard", func(c *mizu.Ctx) error {
    if feature.IsEnabled(c, "new_dashboard") {
        return renderNewDashboard(c)
    }
    return renderOldDashboard(c)
})
```

### Get Flag Details

```go theme={null}
app.Get("/features", func(c *mizu.Ctx) error {
    flag := feature.Get(c, "beta_feature")
    if flag != nil {
        return c.JSON(200, map[string]any{
            "name":        flag.Name,
            "enabled":     flag.Enabled,
            "description": flag.Description,
        })
    }
    return c.JSON(404, "Feature not found")
})
```

### Memory Provider (Dynamic)

```go theme={null}
// Create a mutable provider
provider := feature.NewMemoryProvider()

app.Use(feature.WithProvider(provider))

// Enable/disable flags at runtime
app.Post("/admin/features/:name/enable", func(c *mizu.Ctx) error {
    provider.Enable(c.Param("name"))
    return c.Text(200, "Enabled")
})

app.Post("/admin/features/:name/disable", func(c *mizu.Ctx) error {
    provider.Disable(c.Param("name"))
    return c.Text(200, "Disabled")
})

app.Post("/admin/features/:name/toggle", func(c *mizu.Ctx) error {
    provider.Toggle(c.Param("name"))
    return c.Text(200, "Toggled")
})
```

### Require Flag Middleware

```go theme={null}
// Route requires feature to be enabled
app.Get("/beta",
    feature.Require("beta_access", nil),
    func(c *mizu.Ctx) error {
        return c.Text(200, "Welcome to beta!")
    },
)

// Custom handler when disabled
app.Get("/new-feature",
    feature.Require("new_feature", func(c *mizu.Ctx) error {
        return c.JSON(403, map[string]string{
            "error": "Feature not available",
        })
    }),
    newFeatureHandler,
)
```

### Require All Flags

```go theme={null}
// Requires ALL flags to be enabled
app.Get("/admin/super",
    feature.RequireAll([]string{"admin_access", "super_powers"}, nil),
    superAdminHandler,
)
```

### Require Any Flag

```go theme={null}
// Requires ANY flag to be enabled
app.Get("/premium",
    feature.RequireAny([]string{"premium_user", "trial_active"}, nil),
    premiumHandler,
)
```

### Custom Provider

```go theme={null}
type DatabaseProvider struct {
    db *sql.DB
}

func (p *DatabaseProvider) GetFlags(c *mizu.Ctx) (feature.Flags, error) {
    rows, err := p.db.Query("SELECT name, enabled FROM feature_flags")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    flags := make(feature.Flags)
    for rows.Next() {
        var name string
        var enabled bool
        rows.Scan(&name, &enabled)
        flags[name] = &feature.Flag{Name: name, Enabled: enabled}
    }
    return flags, nil
}

app.Use(feature.WithProvider(&DatabaseProvider{db: db}))
```

### User-Specific Flags

```go theme={null}
type UserProvider struct {
    db *sql.DB
}

func (p *UserProvider) GetFlags(c *mizu.Ctx) (feature.Flags, error) {
    userID := c.Get("user_id").(string)

    // Get user-specific flags
    flags := make(feature.Flags)

    // Check if user is in beta program
    var inBeta bool
    p.db.QueryRow("SELECT beta FROM users WHERE id = ?", userID).Scan(&inBeta)
    flags["beta_features"] = &feature.Flag{Name: "beta_features", Enabled: inBeta}

    return flags, nil
}
```

### Percentage Rollout

```go theme={null}
type RolloutProvider struct {
    percentages map[string]int
}

func (p *RolloutProvider) GetFlags(c *mizu.Ctx) (feature.Flags, error) {
    userID := c.Get("user_id").(string)
    hash := hashUserID(userID)

    flags := make(feature.Flags)
    for name, pct := range p.percentages {
        flags[name] = &feature.Flag{
            Name:    name,
            Enabled: (hash % 100) < pct,
        }
    }
    return flags, nil
}
```

### List All Flags

```go theme={null}
app.Get("/features", func(c *mizu.Ctx) error {
    flags := feature.GetFlags(c)

    result := make([]map[string]any, 0)
    for name, flag := range flags {
        result = append(result, map[string]any{
            "name":    name,
            "enabled": flag.Enabled,
        })
    }
    return c.JSON(200, result)
})
```

### Flag with Metadata

```go theme={null}
provider := feature.NewMemoryProvider()

provider.SetFlag(&feature.Flag{
    Name:        "new_checkout",
    Enabled:     true,
    Description: "New checkout flow",
    Metadata: map[string]any{
        "rollout_percentage": 50,
        "target_users":       "premium",
        "experiment_id":      "exp-123",
    },
})

app.Use(feature.WithProvider(provider))
```

## API Reference

```go theme={null}
func New(flags Flags) mizu.Middleware
func WithProvider(provider Provider) mizu.Middleware
func WithOptions(opts Options) mizu.Middleware
func GetFlags(c *mizu.Ctx) Flags
func Get(c *mizu.Ctx, name string) *Flag
func IsEnabled(c *mizu.Ctx, name string) bool
func IsDisabled(c *mizu.Ctx, name string) bool
func Require(name string, handler mizu.Handler) mizu.Middleware
func RequireAll(names []string, handler mizu.Handler) mizu.Middleware
func RequireAny(names []string, handler mizu.Handler) mizu.Middleware

// Provider functions
func StaticProvider(flags Flags) Provider
func NewMemoryProvider() *MemoryProvider
```

## Memory Provider Methods

```go theme={null}
func (p *MemoryProvider) Set(name string, enabled bool)
func (p *MemoryProvider) SetFlag(flag *Flag)
func (p *MemoryProvider) Delete(name string)
func (p *MemoryProvider) Enable(name string)
func (p *MemoryProvider) Disable(name string)
func (p *MemoryProvider) Toggle(name string)
```

## Technical Details

### Architecture

The feature middleware uses a provider-based architecture that allows flexible feature flag management:

* **Context Storage**: Feature flags are stored in the request context using a private `contextKey{}` type for isolation
* **Provider Interface**: The `Provider` interface defines a single method `GetFlags(c *mizu.Ctx) (Flags, error)` enabling custom implementations
* **Thread Safety**: The `MemoryProvider` uses `sync.RWMutex` for concurrent read/write access
* **Immutable Returns**: Providers return copies of flag maps to prevent external modification

### Implementation Details

**Flag Resolution Flow**:

1. Middleware intercepts request
2. Provider's `GetFlags()` is called with context
3. Flags are stored in request context
4. Helper functions (`IsEnabled`, `Get`, etc.) retrieve flags from context
5. On provider error, empty flag map is used (fail-safe behavior)

**Static Provider**:

* Wraps a `Flags` map in an immutable provider
* Returns defensive copies to prevent modification
* Zero overhead after initialization

**Memory Provider**:

* Mutable in-memory flag storage
* Uses read-write mutex for concurrent access
* Methods: `Enable()`, `Disable()`, `Toggle()`, `Set()`, `SetFlag()`, `Delete()`
* Safe for runtime flag updates

**Middleware Functions**:

* `Require(name, handler)`: Guards route requiring single flag
* `RequireAll(names, handler)`: Guards route requiring all flags enabled
* `RequireAny(names, handler)`: Guards route requiring at least one flag enabled
* Custom handlers provide fallback responses when flags are disabled

### Performance Characteristics

* **Static Provider**: O(n) copy operation per request where n is flag count
* **Memory Provider**: O(n) copy with RLock, write operations use Lock
* **Flag Lookup**: O(1) map lookup from context
* **Context Storage**: Single context value, minimal memory overhead

## Best Practices

* Use descriptive flag names
* Document flag purposes
* Clean up old flags after rollout
* Use percentage rollouts for risky features
* Implement a custom provider for production
* Include metadata for analytics

## Testing

The feature middleware includes comprehensive test coverage for all functionality:

| Test Case                   | Description                               | Expected Behavior                                 |
| --------------------------- | ----------------------------------------- | ------------------------------------------------- |
| `TestNew`                   | Static flags with enabled/disabled states | Correctly identifies enabled and disabled flags   |
| `TestIsDisabled`            | Check if flag is disabled                 | Returns true for disabled flags                   |
| `TestGetFlags`              | Retrieve all flags from context           | Returns complete flag collection                  |
| `TestGet`                   | Retrieve specific flag with metadata      | Returns flag with description and metadata intact |
| `TestRequire` (enabled)     | Route protection with enabled flag        | Allows request to proceed (200 OK)                |
| `TestRequire` (disabled)    | Route protection with disabled flag       | Blocks request with 404 Not Found                 |
| `TestRequire_CustomHandler` | Custom handler when flag disabled         | Executes custom handler returning 403 Forbidden   |
| `TestRequireAll`            | Require multiple flags all enabled        | Allows request when all flags enabled             |
| `TestRequireAny`            | Require at least one flag enabled         | Allows request when any flag is enabled           |
| `TestMemoryProvider`        | In-memory provider CRUD operations        | Enable, Set, and SetFlag work correctly           |
| `TestMemoryProvider_Toggle` | Toggle flag state at runtime              | Switches flag from enabled to disabled            |
| `TestMemoryProvider_Delete` | Delete flag from provider                 | Flag no longer exists after deletion              |

## Related Middlewares

* [multitenancy](/middlewares/multitenancy) - Multi-tenant support
* [version](/middlewares/version) - API versioning
