# `Macaw.Data.Derived.Sandbox`
[🔗](https://github.com/dcoai/macaw_data/blob/v0.5.3/lib/macaw/data/derived/sandbox.ex#L1)

The sandbox a derived source's function runs in (spec/02 §6, visualize D-194).

A derived source is a source computed from other sources by an Elixir function, rows in
and rows out (visualize#643). A function stored in a document as source text is untrusted, so it is
**checked** against an allowlist before anything runs (`check/2`), **compiled once** into
a closure (`compile/2`, cached by `Macaw.Data.Derived.Sandbox.Cache`), and **called
in a bounded process** (`run/3`) — as is a host's own trusted function.

    iex> alias Macaw.Data.Derived.Sandbox
    iex> {:ok, compiled} = Sandbox.compile("fn rows -> Enum.map(rows, & &1.v * 2) end")
    iex> Sandbox.run(compiled, [[%{v: 1}, %{v: 2}]])
    {:ok, [2, 4]}
    iex> {:error, [{1, reason}]} = Sandbox.check("fn _ -> File.read(\"/etc/passwd\") end")
    iex> reason
    {:restricted, {"File", "read", 1}}

A call's cost is the reductions its process spends, held to `max_reductions:`, so a
runaway's verdict is the same on a loaded host; a call blocked in `receive` is `:hung` at
a separate, generous `timeout:` (visualize#852, visualize D-220). Nothing is rescued: a raise, a heap overrun,
a runaway and a hang are read from the process the call ran in, through its monitor.

# `fault`

```elixir
@type fault() :: {pos_integer() | nil, term()}
```

A checker fault: the source line, or `nil`, and the reason (spec/02 §6.1).

# `kind`

```elixir
@type kind() :: :reductions | :hung | :memory | :raised
```

What a call can end in besides a value (spec/02 §6.4).

# `check`

```elixir
@spec check(String.t()) :: {:ok, Macro.t()} | {:error, [fault(), ...]}
```

As `check/2` with no options.

# `check`

```elixir
@spec check(String.t(), keyword()) :: {:ok, Macro.t()} | {:error, [fault(), ...]}
```

A stored function checked against the allowlist (spec/02 §6.1): `{:ok, quoted}`, or
`{:error, faults}` with every fault, each `{line, reason}`, ordered by line. Nothing is
evaluated.

Options: `arity:` (the arities the function may take, an integer or a list, `[1, 2]`:
its inputs, or its inputs and its context), `fields:` (the names a `row.field` may read;
`nil` reads any), `max_source:` (bytes, `10_000`, which may be lowered, never raised) and
`operations:` (the registry a `Macaw.Data.Ops` call's name is held to,
`macaw_data`'s own package by default; `nil` holds the name only to being a literal,
for a source checked before it is placed, spec/04 §6.1).

# `compile`

```elixir
@spec compile(String.t()) ::
  {:ok, Macaw.Data.Derived.Sandbox.Compiled.t()} | {:error, [fault(), ...]}
```

As `compile/2` with no options.

# `compile`

```elixir
@spec compile(String.t(), keyword()) ::
  {:ok, Macaw.Data.Derived.Sandbox.Compiled.t()} | {:error, [fault(), ...]}
```

A stored function checked, then compiled once into a closure (spec/02 §6.3):
`{:ok, %Compiled{}}`, or `{:error, faults}` — the checker's, or `{nil, {:compile,
message}}` for a checked source the compiler refuses. The compilation runs in the
bounded runner. Options: the checker's and the runner's.

# `format_error`

```elixir
@spec format_error(fault()) :: String.t()
```

One checker fault as a line a person reads (spec/02 §6.1).

    iex> Macaw.Data.Derived.Sandbox.format_error({3, {:restricted, {"Process", :send, 3}}})
    "line 3: Process.send/3 is not allowed in a derived source"

# `hash`

```elixir
@spec hash(String.t()) :: String.t()
```

A source's SHA-256 as 64 lowercase hexadecimal digits: its key in the cache (spec/08
§10.5.3).

    iex> Macaw.Data.Derived.Sandbox.hash("fn rows -> rows end")
    "a07f4aab6263d19d009830e7ad7b37931d654ff286700db61a32f064ea6d98d8"

# `run`

```elixir
@spec run(Macaw.Data.Derived.Sandbox.Compiled.t() | function(), list()) ::
  {:ok, term()} | {:error, kind(), term()}
```

As `run/3` with no options.

# `run`

```elixir
@spec run(Macaw.Data.Derived.Sandbox.Compiled.t() | function(), list(), keyword()) ::
  {:ok, term()} | {:error, kind(), term()}
```

A function called with `args` in a process of its own (spec/02 §6.4): `{:ok, value}`,
or `{:error, kind, detail}` — `{:error, :reductions, cap}` when its process spent more
than `max_reductions:` (`100_000_000`), `{:error, :hung, ms}` when it had not answered
within `timeout:` (`30_000` ms) — blocked, not slow — `{:error, :memory, words}` when its
heap passed `max_heap:` (`10_000_000` words) and the VM killed it, `{:error, :raised,
module}` with the exception's module when it raised. The process is spawned with its heap
limit set and monitored; a killed process is waited for before the call returns. Each
bound may be lowered, never raised: a value above its default is `ArgumentError`.

---

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