# `DpExchange.Core.Options`
[🔗](https://github.com/DistortionPoint/dp-exchange-core/blob/v0.3.120/lib/dp_exchange/core/options.ex#L1)

Refuses a facade option the call does not read.

A misspelled `limt:` or `sinse:` that is silently ignored returns a plausible answer to a
different question than the caller asked: the "nearby substitute where there should be an
error" this family exists to avoid. Every facade function that takes a keyword `opts` calls
`check/2` at the boundary, before anything is sent and before an internal key (`:limiter`
and the like) is added, so a stray key is a caller error.

## Where it sits in the check order

Positional arguments first (subscriber, symbols, symbol, range), then the options, then
credentials. An option error is decided before anything is sent, so it is a plain
`{:error, _}`, never `:order_unconfirmed`. A function that answers
`Venue.not_supported()` keeps doing so whatever the options: the endpoint's absence is the
more useful answer.

## What is allowed

`transport_options/0` is the shared vocabulary, not a list every call accepts. A call that
sends an HTTP request allows `transport_options() -- not_read` plus the keys it reads
itself, where `not_read` names each shared key that call cannot honour (no sandbox:
`:environment`; no URL override: `:base_url`), and every key it allows reaches
`Core.HttpClient` or the seam that reads it. A call that sends no request (the feed and
local reads) allows its own keys plus `:credentials` only. `:credentials` is exempt because
a consumer hands one credential set to every call, and one a call does not need changes no
answer. A nil-valued key is still a key: a forwarded `limt: nil` is refused too, because a
caller who typed the key meant something by it.

The earlier rule, "every call allows every transport key", rested on a claim that every
venue forwards them all. Measured in tick AK, it was false in three: `environment: :sandbox`
sent Robinhood orders to production, Schwab ignored `base_url:` while sending a live token,
and `max_body_bytes:` bounded nothing on two venues' REST calls.

## What it answers

  * `:ok`
  * `{:error, {:unknown_option, key}}` - the first key (in the caller's order) not allowed
  * `{:error, {:invalid_options, opts}}` - `opts` is not a keyword list. Any `:credentials`
    entry of `opts` is replaced by `:redacted` in the tuple, since the error is logged.

# `check`

```elixir
@spec check(term(), [atom()]) ::
  :ok | {:error, {:unknown_option, atom()} | {:invalid_options, term()}}
```

Checks `opts` against the keys `allowed`.

Answers `:ok`, `{:error, {:unknown_option, key}}` for the first key not in `allowed` (a
key holding `nil` counts), or `{:error, {:invalid_options, opts}}` when `opts` is not a
keyword list.

## Examples

    iex> DpExchange.Core.Options.check([limit: 5], [:limit])
    :ok

    iex> DpExchange.Core.Options.check([limt: 5], [:limit])
    {:error, {:unknown_option, :limt}}

    iex> DpExchange.Core.Options.check([limt: nil], [:limit])
    {:error, {:unknown_option, :limt}}

    iex> DpExchange.Core.Options.check(%{limit: 5}, [:limit])
    {:error, {:invalid_options, %{limit: 5}}}

# `transport_options`

```elixir
@spec transport_options() :: [atom()]
```

The shared transport keys, whatever the venue: their names and meanings.

Credentials, the environment and base URL, the `Req` test seams (`:plug`, `:adapter`,
`:req_adapter`), and the limiter, timeout and retry knobs `Core.HttpClient` reads. A call
that sends a request checks `opts` against `(transport_options() -- not_read) ++ own_keys`;
a call that sends none against `[:credentials | own_keys]` (see the moduledoc).

---

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