# `PhoenixDevToolbar.Truncate`
[🔗](https://github.com/zoten/phoenix_dev_toolbar/blob/0.0.2/lib/phoenix_dev_toolbar/truncate.ex#L1)

Bounds payloads before they are stored ([ADR 03](https://github.com/zoten/phoenix_dev_toolbar/blob/main/adr/03-event-storage.md)): plugins use these helpers
in `c:PhoenixDevToolbar.Plugin.collect/3`, which runs inside host processes.
Every function is pure, cheap on small inputs and bounded on large ones.
`redact/2` hides the values of sensitive keys (`config :phoenix_dev_toolbar,
redact: [...]`, by default keys containing "password", "token" or "secret").

# `changes`

```elixir
@type changes() :: %{
  keys: [term()],
  values: %{required(term()) =&gt; String.t()},
  omitted: non_neg_integer()
}
```

# `banner`

```elixir
@spec banner(:error | :exit | :throw, term()) :: String.t()
```

Formats an exception, throw or exit as a bounded one line banner.
`Exception.format_banner/2` inspects throw and exit reasons without limits.

    iex> PhoenixDevToolbar.Truncate.banner(:error, %RuntimeError{message: "bad"})
    "** (RuntimeError) bad"

    iex> PhoenixDevToolbar.Truncate.banner(:throw, :oops)
    "** (throw) :oops"

# `inspect_value`

```elixir
@spec inspect_value(term(), keyword()) :: String.t()
```

Inspects a value with the toolbar limits.

    iex> PhoenixDevToolbar.Truncate.inspect_value(Enum.to_list(1..60)) |> String.ends_with?("...]")
    true

# `preview`

```elixir
@spec preview(term(), pos_integer(), [String.t()]) :: String.t()
```

Redacted, bounded inspect of any value, at most `bytes` long.

Unlike `redact/2`, the cost does not depend on the value: at most
200 nodes and 20 items per container are walked,
and whatever is not walked is replaced by `"…"`, so a sensitive key
deep in a large value is cut, never shown.

    iex> PhoenixDevToolbar.Truncate.preview(%{user: %{password: "hunter2"}})
    ~s(%{user: %{password: "[REDACTED]"}})

    iex> PhoenixDevToolbar.Truncate.preview(Enum.to_list(1..30))
    "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...]"

# `redact`

```elixir
@spec redact(term(), [String.t() | atom()]) :: term()
```

Replaces the value of sensitive keys with `"[REDACTED]"`.

A key is sensitive when its name (atom or string, case insensitive, the first
256 bytes of it) contains one of `keys`. Walks maps, structs,
keyword lists, lists and tuples, at most 50 entries per container
and 1000 nodes in total, so the cost does not depend on the value.

Contract: a sensitive key's value is `"[REDACTED]"`; anything the walk does
not reach is replaced by `"…"`, never returned as is. The rest of a list
or tuple becomes a final `"…"` element (so does the tail of an improper
list), the rest of a map becomes a `"…" => "…"` entry, and a struct
with more than 50 fields becomes a plain map with that entry.
Scalars, including large binaries, are returned as they are (they are cheap;
`inspect_value/2` bounds their size). Key matching only sees map, struct and
keyword keys: a bare `{"password", "x"}` tuple outside a list, a charlist key
and a secret inside a string value are not recognised.

    iex> PhoenixDevToolbar.Truncate.redact(%{"user" => %{"password" => "hunter2", "name" => "Ada"}})
    %{"user" => %{"password" => "[REDACTED]", "name" => "Ada"}}

    iex> PhoenixDevToolbar.Truncate.redact([api_token: "abc", page: 1])
    [api_token: "[REDACTED]", page: 1]

    iex> PhoenixDevToolbar.Truncate.redact({:login, %{password: "hunter2"}})
    {:login, %{password: "[REDACTED]"}}

    iex> PhoenixDevToolbar.Truncate.redact([%{password: "hunter2"} | :tail])
    [%{password: "[REDACTED]"}, "…"]

# `sql`

```elixir
@spec sql(String.t() | nil) :: String.t() | nil
```

Caps a SQL string at 8192 bytes.

    iex> PhoenixDevToolbar.Truncate.sql("SELECT 1")
    "SELECT 1"

    iex> PhoenixDevToolbar.Truncate.sql(nil)
    nil

# `stacktrace`

```elixir
@spec stacktrace(Exception.stacktrace() | nil, [module()]) :: [String.t()]
```

Formats the first 10 stacktrace frames that do not belong to the
toolbar, to `:telemetry` or to the extra `skip` modules and namespaces (an
Elixir module skips itself and every module nested under it).

    iex> PhoenixDevToolbar.Truncate.stacktrace([{MyApp.Users, :list, 0, [file: ~c"lib/users.ex", line: 3]}])
    ["lib/users.ex:3: MyApp.Users.list/0"]

    iex> PhoenixDevToolbar.Truncate.stacktrace([{Ecto.Repo.Queryable, :all, 3, []}], [Ecto])
    []

    iex> PhoenixDevToolbar.Truncate.stacktrace(nil)
    []

# `string`

```elixir
@spec string(String.t(), pos_integer()) :: String.t()
```

Caps any text (exception messages, banners) at `limit` bytes, 2048 by default.

    iex> PhoenixDevToolbar.Truncate.string("boom")
    "boom"

    iex> "x" |> String.duplicate(3000) |> PhoenixDevToolbar.Truncate.string() |> byte_size()
    2060

---

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