# `Bourse.Dispatch`
[🔗](https://github.com/ZenHive/bourse/blob/main/lib/bourse/dispatch.ex#L1)

Shared request dispatcher for generated exchange endpoint functions.

All generated endpoint functions delegate to `call/4`, which handles:

1. **Path interpolation** — replaces `{param}` templates with values from params
2. **Base URL resolution** — a caller-supplied `:base_url` opt wins; otherwise
   navigates `exchange.base_urls` using endpoint sections
3. **Signing** — authenticates private endpoint requests via `Bourse.Signing.sign/4`
4. **HTTP delegation** — calls `Bourse.HTTP.request/4` or `Bourse.HTTP.signed_request/5`

## Future phases

- **Phase 5**: Response parsing (field mapping to unified structs)
- **Task 17**: Symbol denormalization (unified → exchange-specific format)

# `endpoint_config`

```elixir
@type endpoint_config() :: %{
  :name =&gt; atom(),
  :method =&gt; atom(),
  :path =&gt; String.t(),
  :sections =&gt; [String.t()],
  :weight =&gt; number(),
  optional(:url_prefix) =&gt; String.t(),
  optional(:authenticated) =&gt; boolean(),
  optional(:rate_limit) =&gt; map(),
  optional(:response_transformer) =&gt; Bourse.ResponseTransformer.transformer()
}
```

Compile-time endpoint configuration from spec

# `call`

```elixir
@spec call(Bourse.Exchange.t(), endpoint_config(), map() | [map()], keyword()) ::
  {:ok, Bourse.HTTP.response()} | {:error, Bourse.Error.t()}
```

Dispatches a request to an exchange endpoint.

Resolves the base URL from the endpoint's sections, interpolates path
templates, and delegates to `Bourse.HTTP.request/4`.

A caller-supplied `:base_url` opt takes precedence over `resolve_base_url/2`
on both the public and the signed path — the override reaches the wire. For
host-signing venues, the signing config receives the host parsed from that
effective base URL so the signature covers the same host the request uses.

## Parameters

- `exchange` — `%Bourse.Exchange{}` runtime configuration
- `endpoint_config` — compile-time endpoint map with `:name`, `:method`, `:path`, `:sections`, `:weight`
- `params` — request parameters (query for GET/HEAD/DELETE, body for POST/PUT/PATCH)
- `opts` — passed through to `Bourse.HTTP.request/4` (`:base_url`, `:timeout`, `:headers`, etc.)

## Examples

    config = %{name: :public_get_v5_market_tickers, method: :get,
      path: "v5/market/tickers", sections: ["public"], weight: 5}

    Bourse.Dispatch.call(exchange, config, %{"category" => "spot"})

# `interpolate_path`

```elixir
@spec interpolate_path(String.t(), map() | [map()]) :: {String.t(), map() | [map()]}
```

Replaces `{param}` placeholders in path with values from params, returning remaining params.

# `interpolate_path`

```elixir
@spec interpolate_path(String.t(), map() | [map()], [String.t() | map()] | nil) ::
  {String.t(), map() | [map()]}
```

Replaces the specified `{param}` placeholders in path with values from params.

# `resolve_base_url`

```elixir
@spec resolve_base_url([String.t()], map()) :: String.t() | nil
```

Navigates `base_urls` using endpoint sections to find the appropriate base URL.

Returns a URL string when the section path resolves. If navigation misses, falls
back only when the map has exactly one unique string URL (shared-host venues).
When multiple distinct hosts exist and the section is absent, returns `nil`.

---

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