# `Expression.Context`

A helper module for creating a context that can be
used with Expression.Eval.

## Plain map context

`new/2` returns a plain map. By default it preserves original key
casing (variable lookup is case-insensitive at evaluation time) and
leaves string values untouched. Atom keys are stringified.

  iex> Expression.Context.new(%{foo: "bar"})
  %{"foo" => "bar"}
  iex> Expression.Context.new(%{FOO: "bar"})
  %{"FOO" => "bar"}
  iex> Expression.Context.new(%{foo: %{bar: "baz"}})
  %{"foo" => %{"bar" => "baz"}}
  iex> Expression.Context.new(%{Foo: %{Bar: "baz"}})
  %{"Foo" => %{"Bar" => "baz"}}
  iex> Expression.Context.new(%{foo: %{bar: 1}})
  %{"foo" => %{"bar" => 1}}
  iex> Expression.Context.new(%{date: "2020-12-13T23:34:45"})
  %{"date" => "2020-12-13T23:34:45"}
  iex> Expression.Context.new(%{boolean: "true"})
  %{"boolean" => "true"}
  iex> Expression.Context.new(%{float: 1.234})
  %{"float" => 1.234}

Pass `lowercase_keys: true` and/or `coerce_strings: true` to opt in
to v2-style normalization:

  iex> Expression.Context.new(%{FOO: "bar"}, lowercase_keys: true)
  %{"foo" => "bar"}
  iex> Expression.Context.new(%{date: "2020-12-13T23:34:45"}, coerce_strings: true)
  %{"date" => ~U[2020-12-13 23:34:45.0Z]}
  iex> Expression.Context.new(%{boolean: "true"}, coerce_strings: true)
  %{"boolean" => true}
  iex> Expression.Context.new(%{mixed: ["2020-12-13T23:34:45", 1, "true", "binary"]}, coerce_strings: true)
  %{"mixed" => [~U[2020-12-13 23:34:45.0Z], 1, true, "binary"]}

## Options

`new/2` accepts the following options:

  * `:lowercase_keys` - when `true`, all keys are lowercased. Default
    is `false`. Variable lookup remains case-insensitive at evaluation
    time regardless of this setting.
  * `:coerce_strings` - when `true`, string values are auto-parsed to
    their typed equivalents (dates, booleans, numbers). Default is
    `false`.

## Structured context with private state

`build/2` returns an `%Expression.Context{}` struct that separates
user-visible variables from callback-private state.

Variable resolution (`@foo`) only reads from `vars`.
Callbacks receive the full struct and can access `private` for
trusted data (database records, tokens, internal IDs) that
expression authors must not be able to read.

  iex> ctx = Expression.Context.build(%{name: "Jane"}, private: %{number: %{uuid: "abc"}})
  iex> ctx.vars["name"]
  "Jane"
  iex> ctx.private
  %{number: %{uuid: "abc"}}

# `t`

```elixir
@type t() :: %Expression.Context{private: map(), vars: map()}
```

# `build`

```elixir
@spec build(map(), Keyword.t()) :: t()
```

Build a structured context with separate variable and private state compartments.

Variable resolution (`@foo`) only reads from `vars`. Callbacks receive the
full struct and can access `private` for trusted data that expression authors
must not be able to read.

## Options

  * `:private` - a map of callback-only data (default: `%{}`)

Any other options are passed through to `new/2` for variable normalization.

## Examples

    iex> ctx = Expression.Context.build(%{name: "Jane"}, private: %{token: "secret"})
    iex> ctx.vars["name"]
    "Jane"
    iex> ctx.private.token
    "secret"

# `new`

```elixir
@spec new(map(), Keyword.t() | nil) :: map()
```

---

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