# `Zizq.Router`
[🔗](https://github.com/zizq-labs/zizq-elixir/blob/v0.8.0/lib/zizq/router.ex#L4)

Dispatches jobs to the module that defines them, by type.

Pass one straight to a worker as its `:handler`:

    {Zizq.Worker,
     client: MyApp.Zizq,
     queues: ["emails", "reports"],
     handler: Zizq.Router.new([MyApp.SendEmail, MyApp.GenerateReport])}

Each module is asked for its `type/0` as it is registered, so the
routing table is built once at startup rather than per job. Two
modules in one list claiming the same type raises `ArgumentError`.

Plain functions can be registered too:

    Zizq.Router.new([
      MyApp.SendEmail,
      {"ping", fn _payload -> :ok end},
      {"audit", fn payload, job -> MyApp.Audit.record(payload, job.id) end}
    ])

## Building a route at a time

`route/2` and `route/3` add to a router, so routes can be assembled
conditionally rather than declared all at once:

    Zizq.Router.new()
    |> Zizq.Router.route(MyApp.SendEmail)
    |> Zizq.Router.route("charge_card", &MyApp.Billing.charge/2)
    |> Zizq.Router.fallback(&MyApp.unknown_job/1)

Unlike a list — where a repeated type is a mistake and raises —
**`route/2` and `route/3` replace** any route already registered for
that type. That is what makes a router of shared defaults useful as a
starting point:

    base_router() |> Zizq.Router.route("charge_card", &test_double/1)

## Unrecognised types

A job whose type is in no route raises `Zizq.Router.UnknownJobType`,
which the worker reports as a failure like any other exception, so
the job retries, and dies once its retry limit is spent. Retrying is
usually right: during a rolling deploy a job enqueued by new code can
reach a worker running old code, and the retry lands on a worker that
knows the type. Or occasionally a bug is introduced and detected in
your error tracker so you have time to rectify it.

A fallback handles those instead of raising. It is optional, and
receives the whole `Zizq.Job`, since a payload alone says little
about a job you did not expect:

    Zizq.Router.new(modules) |> Zizq.Router.fallback(&MyApp.unknown/1)

A fallback takes the same argument a handler does, so one router can
defer to another:

    Zizq.Router.fallback(specific, Zizq.Router.build(general))

## Why types are registered, never resolved

Turning a wire type into a module at runtime would mean
`String.to_existing_atom/1` on data from the queue. Building a table
from modules the application named itself keeps job data as something
looked *up*, never something that selects code to run.

# `entry`

```elixir
@type entry() :: module() | {String.t(), route()}
```

# `route`

```elixir
@type route() :: (term() -&gt; term()) | (term(), Zizq.Job.t() -&gt; term())
```

# `t`

```elixir
@type t() :: %Zizq.Router{
  fallback: (Zizq.Job.t() -&gt; term()) | nil,
  routes: %{optional(String.t()) =&gt; (term(), Zizq.Job.t() -&gt; term())}
}
```

# `build`

```elixir
@spec build(t()) :: (Zizq.Job.t() -&gt; term())
```

Compile a router into a one-argument function.

`Zizq.Worker` does this once when it starts, so a router can be given
to it directly. Call it yourself to hold a plain function (e.g. to use
as another router's fallback, or to dispatch without a worker).

# `fallback`

```elixir
@spec fallback(t(), (Zizq.Job.t() -&gt; term())) :: t()
```

Handle jobs matching no route with `fun`, instead of raising.

Replaces any fallback already registered.

# `new`

```elixir
@spec new([entry()], keyword()) :: t()
```

Build a router from a list of job modules.

## Options

  * `:fallback` — a one-argument function called with the `Zizq.Job`
    when no route matches, instead of raising.

# `route`

```elixir
@spec route(t(), module()) :: t()
```

Register a module that uses `Zizq.JobKind`, under its own type.

# `route`

```elixir
@spec route(t(), String.t(), route()) :: t()
```

Register a handler for `type`.

The handler takes the payload, or the payload and the `Zizq.Job`.
Replaces any route already registered for that type.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
