documentation/middleware.md

# Command Middleware

AshCommanded provides a middleware system that allows you to intercept and modify commands before they are dispatched, as well as process the results afterward. This concept is similar to [Commanded's middleware](https://hexdocs.pm/commanded/commands.html#command-handlers) functionality but adapted for the Ash framework context. This is useful for implementing cross-cutting concerns such as:

- Validation
- Logging
- Authentication and authorization
- Rate limiting
- Metrics and monitoring
- Auditing
- Parameter transformation
- Error handling and standardization

## Middleware Concepts

1. **Middleware Chain**: Middleware is applied in sequence, with each middleware having the opportunity to:
   - Inspect and modify the command
   - Provide additional context
   - Short-circuit the chain by returning an error
   - Process the result after command execution

2. **Middleware Levels**: You can specify middleware at three different levels:
   - Global (application-wide)
   - Resource-level (applies to all commands in a resource)
   - Command-level (applies to a specific command)

3. **Middleware Context**: Middleware can pass information to each other using a context map.

## Using Built-in Middleware

AshCommanded includes several built-in middleware components that you can use immediately:

### LoggingMiddleware

Logs information about commands and their results:

```elixir
defmodule MyApp.User do
  use Ash.Resource,
    extensions: [AshCommanded.Commanded.Dsl]

  commanded do
    commands do
      # Resource-level middleware - applies to all commands
      middleware [AshCommanded.Commanded.Middleware.LoggingMiddleware]
      
      # Command-specific middleware with options
      command :register_user do
        fields [:id, :name, :email]
        middleware [
          {AshCommanded.Commanded.Middleware.LoggingMiddleware, level: :debug}
        ]
      end
    end
  end
end
```

Configuration options:
- `level`: The log level to use (`:debug`, `:info`, `:warn`, `:error`). Defaults to `:info`.
- `error_level`: The log level to use for errors. Defaults to `:error`.

### ValidationMiddleware

Validates commands before they are dispatched:

```elixir
defmodule MyApp.User do
  use Ash.Resource,
    extensions: [AshCommanded.Commanded.Dsl]

  commanded do
    commands do
      command :register_user do
        fields [:id, :name, :email, :age]
        
        middleware [
          {AshCommanded.Commanded.Middleware.ValidationMiddleware,
            # Require specific fields
            required: [:name, :email],
            
            # Validate field formats
            format: [
              email: ~r/@/,
              name: ~r/^[a-zA-Z\s]+$/
            ],
            
            # Custom validation function
            validate: fn command ->
              if command.age && command.age < 18 do
                {:error, "User must be at least 18 years old"}
              else
                :ok
              end
            end
          }
        ]
      end
    end
  end
end
```

Configuration options:
- `required`: List of fields that must be present and non-nil
- `format`: Map of field names to regular expressions for format validation
- `validate`: Custom validation function that takes a command and returns `:ok` or `{:error, reason}`
- `validations`: List of validations to apply (combines the above options)

## Creating Custom Middleware

You can create your own middleware by implementing the `AshCommanded.Commanded.Middleware.CommandMiddleware` behaviour or by using `AshCommanded.Commanded.Middleware.BaseMiddleware` as a starting point. This is conceptually similar to how you would [implement middleware in Commanded](https://hexdocs.pm/commanded/middleware.html):

```elixir
defmodule MyApp.AuthorizationMiddleware do
  use AshCommanded.Commanded.Middleware.BaseMiddleware
  
  @impl true
  def before_dispatch(command, context, next) do
    # Extract user from context
    user = Map.get(context, :current_user)
    
    # Check authorization
    if authorized?(command, user) do
      # Continue with the command
      next.(command, context)
    else
      # Deny the command
      {:error, :unauthorized}
    end
  end
  
  @impl true
  def after_dispatch({:ok, result} = success, _command, _context) do
    # Process successful result
    success
  end
  
  def after_dispatch({:error, reason} = error, _command, _context) do
    # Process error result
    error
  end
  
  # Helper function for authorization check
  defp authorized?(command, user) do
    # Your authorization logic here...
    true
  end
end
```

## Global Middleware Configuration

You can specify global middleware that applies to all commands in your application using the application configuration:

```elixir
# In config/config.exs
config :ash_commanded, :global_middleware, [
  AshCommanded.Commanded.Middleware.LoggingMiddleware,
  {MyApp.AuthorizationMiddleware, role_check: true}
]
```

## Middleware Context

Middleware can use the context to share information:

```elixir
defmodule MyApp.TimingMiddleware do
  use AshCommanded.Commanded.Middleware.BaseMiddleware
  
  @impl true
  def before_dispatch(command, context, next) do
    # Add timestamp to context
    context_with_time = Map.put(context, :start_time, System.monotonic_time())
    
    # Call next middleware with updated context
    next.(command, context_with_time)
  end
  
  @impl true
  def after_dispatch(result, _command, context) do
    # Calculate elapsed time
    start_time = Map.get(context, :start_time)
    elapsed = System.convert_time_unit(
      System.monotonic_time() - start_time,
      :native,
      :millisecond
    )
    
    # Log timing information
    Logger.info("Command processed in #{elapsed}ms")
    
    result
  end
end
```

## Middleware Order

Middleware is applied in the following order:

1. Global middleware (from application config)
2. Resource-level middleware
3. Command-specific middleware

Within each level, middleware is applied in the order it's defined.

## Best Practices

1. **Keep Middleware Focused**: Each middleware should have a single responsibility
2. **Use Context for Communication**: Share data between middleware using the context map
3. **Handle Errors Gracefully**: Make sure to handle errors appropriately in `after_dispatch`
4. **Log Debugging Information**: Include helpful log messages for debugging
5. **Test Middleware in Isolation**: Write unit tests for your middleware
6. **Consider Performance**: Keep middleware lightweight to avoid impacting command processing time