# Router

As your bot grows, the `handle/2` function can become a long chain of pattern-match clauses — commands, callback queries, text handlers, inline queries, all interleaved in a single module. When you add conversation flows or role-based access, the clause count explodes and it becomes hard to see the overall structure at a glance.

[ExGram.Router](https://hex.pm/packages/ex_gram_router) replaces those hand-written clauses with a declarative **scope / filter / handle** DSL. You describe *what* each handler should match, and the router takes care of dispatching. Everything compiles down to a standard `handle/2` function, so the rest of ExGram (middlewares, DSL, testing) works exactly the same.

> This guide covers the most common usage. For the full API reference and advanced options, see the [ExGram.Router HexDocs](https://hexdocs.pm/ex_gram_router).

## Installation

Add `ex_gram_router` to your dependencies:

```elixir
# mix.exs
def deps do
  [
    {:ex_gram, "~> 0.65"},
    {:ex_gram_router, "~> 0.1.0"},
    {:jason, ">= 1.0.0"},
    {:req, "~> 0.5"}
  ]
end
```

Then add `:ex_gram_router` to the formatter deps alongside `:ex_gram`:

```elixir
# .formatter.exs
[
  import_deps: [:ex_gram, :ex_gram_router]
]
```

## From `handle/2` to Scopes

Here's a typical bot using plain `handle/2`:

```elixir
defmodule MyBot do
  use ExGram.Bot, name: :my_bot, setup_commands: true

  command("start", description: "Start the bot")
  command("help",  description: "Show help")

  def handle({:command, :start, _}, ctx), do: answer(ctx, "Welcome!")
  def handle({:command, :help, _}, ctx),  do: answer(ctx, "Here is what I can do…")
  def handle({:text, _, _}, ctx),         do: answer(ctx, "You said something!")
  def handle(_, ctx),                     do: ctx
end
```

The same bot with `ExGram.Router`:

```elixir
defmodule MyBot do
  use ExGram.Bot, name: :my_bot, setup_commands: true
  use ExGram.Router

  command("start", description: "Start the bot")
  command("help",  description: "Show help")

  scope do
    filter :command, :start
    handle &MyBot.Handlers.start/1
  end

  scope do
    filter :command, :help
    handle &MyBot.Handlers.help/1
  end

  scope do
    filter :text
    handle &MyBot.Handlers.echo/2
  end

  # Catch-all fallback
  scope do
    handle &MyBot.Handlers.fallback/1
  end
end
```

```elixir
defmodule MyBot.Handlers do
  import ExGram.Dsl

  def start(ctx), do: answer(ctx, "Welcome!")
  def help(ctx),  do: answer(ctx, "Here is what I can do…")

  # 2-arity: receives (update_info, context) to extract the message text directly
  def echo({:text, text, _msg}, ctx), do: answer(ctx, text)

  def fallback(ctx), do: ctx
end
```

Notice that handlers live in their own module. This is optional — you can keep them inline — but separating handlers from routing keeps both sides clean as the bot grows.

## Handler Arities

Handlers can be **1-arity** or **2-arity**:

```elixir
# 1-arity: receives only context
def start(context) do
  answer(context, "Welcome!")
end

# 2-arity: receives (update_info, context)
def echo({:text, text, _msg}, context) do
  answer(context, text)
end
```

The router detects the arity at compile time. Use 2-arity when you need to extract data from the parsed update tuple directly.

### How dispatch works

1. Scopes are tried **top-to-bottom** in declaration order.
2. All filters in a scope must pass (AND logic).
3. The **first matching leaf** wins — its handler runs and dispatch stops.
4. A scope with **no filters** matches everything, so a filter-less scope at the bottom acts as a fallback.

## Built-in Filters

The router ships with filters for the most common update types. Use them by alias name:

```elixir
# Commands
filter :command              # any command
filter :command, :start      # specific command

# Text messages
filter :text                 # any text
filter :text, "hello"        # exact match
filter :text, ~r/^\d+$/      # regex match
filter :text, prefix: "!"    # starts with
filter :text, contains: "hi" # contains substring

# Callback queries
filter :callback_query                    # any callback
filter :callback_query, "confirm"         # exact data
filter :callback_query, ~r/^page_\d+$/   # regex match
filter :callback_query, prefix: "settings:" # prefix match

# Inline queries
filter :inline_query
filter :inline_query, prefix: "@"

# Media & other types
filter :photo
filter :document
filter :location
filter :sticker
filter :voice
filter :video
filter :animation
filter :audio
filter :contact
filter :poll
filter :video_note
filter :message    # any message-type update
filter :regex      # any named regex match
```

Each filter alias maps to a module under `ExGram.Router.Filters.*`. You never need to reference them directly, but you can if you prefer.

## Nested Scopes

Scopes can be nested. A child scope only runs if its parent's filters already passed, so parent filters act as guards for all children:

```elixir
scope do
  filter :callback_query, prefix: "settings:"

  scope do
    filter :callback_query, "settings:language"
    handle &MyBot.Handlers.settings_language/1
  end

  scope do
    filter :callback_query, "settings:timezone"
    handle &MyBot.Handlers.settings_timezone/1
  end
end
```

This is especially useful for callback queries with hierarchical data patterns. Instead of repeating the prefix in every leaf, you filter it once at the parent level.

### Prefix Propagation

For deeply nested callback data, you can use `propagate: true` on a prefix filter. The matched prefix is stripped and child scopes match against the **remainder**:

```elixir
scope do
  filter :callback_query, prefix: "proj:", propagate: true

  scope do
    filter :callback_query, "change"   # matches "proj:change"
    handle &MyBot.Handlers.change_project/1
  end

  scope do
    filter :callback_query, prefix: "settings:", propagate: true

    scope do
      filter :callback_query, "volume"   # matches "proj:settings:volume"
      handle &MyBot.Handlers.volume/1
    end
  end
end
```

Propagation stacks across nesting levels, so you can model arbitrary callback data hierarchies cleanly.

## Custom Filters

When the built-in filters aren't enough, you can implement the `ExGram.Router.Filter` behaviour to encode any runtime predicate - user roles, conversation state, feature flags, and more. See the [Custom Filters section in the ExGram.Router documentation](https://github.com/rockneurotiko/ex_gram_router#custom-filters) for the full guide including examples and alias registration.

## Inspecting Routes

The router ships with two mix tasks for visualizing your bot's routing configuration:

### `mix ex_gram.router.tree`

Prints the full scope tree with indentation:

```text
$ mix ex_gram.router.tree MyBot

MyBot routing tree:
├── scope
│   ├── filters: [Command(:start)]
│   └── handle: &MyBot.Handlers.start/1
├── scope
│   ├── filters: [Command(:help)]
│   └── handle: &MyBot.Handlers.help/1
└── scope
    ├── filters: [CallbackQuery([prefix: "proj:"]) [propagate]]
    ├── scope
    │   ├── filters: [CallbackQuery("change")]
    │   └── handle: &MyBot.Handlers.change_project/1
    └── scope
        ├── filters: [CallbackQuery("delete")]
        └── handle: &MyBot.Handlers.delete_project/1
```

### `mix ex_gram.router.flat`

Prints one line per handler with the full accumulated filter chain, similar to `mix phx.routes` in Phoenix:

```text
$ mix ex_gram.router.flat MyBot

MyBot handlers:
MyBot.Handlers  start/1          filters: [Command(:start)]
MyBot.Handlers  help/1           filters: [Command(:help)]
MyBot.Handlers  change_project/1 filters: [CallbackQuery([prefix: "proj:"]) [propagate], CallbackQuery("change")]
MyBot.Handlers  delete_project/1 filters: [CallbackQuery([prefix: "proj:"]) [propagate], CallbackQuery("delete")]
MyBot.Handlers  fallback/1       filters: []
```

These are invaluable for debugging routing issues or onboarding new team members.

## Testing

The router generates a standard `handle/2` function, so testing works exactly like any other ExGram bot. Use `ExGram.Adapter.Test`, push updates, and assert on outgoing API calls. No special setup needed.

See the [Testing](testing.md) guide for details.

## Next Steps

- [FSM](fsm.md) - Add finite state machine conversation flows (works great with the router)
- [Middlewares](middlewares.md) - Enrich context with data your filters need
- [Handling Updates](handling-updates.md) - Understand the update tuples that filters match against
- [Cheatsheet](cheatsheet.md) - Quick reference for common patterns
