# `Feel.Runtime.AllowedValues`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/runtime/allowed_values.ex#L1)

The verification of a value against allowed values (D-138).

A variable of a `Feel.Scope` can have allowed values: unary tests that a bound value has
to satisfy, and so can the result of an expression. `Feel.Analysis.Analyzer` puts one
`:verify_bindings` node around the expression, with the variables it reads that have them,
and an `:allowed` node around the whole expression for its result. A backend calls
`verify_bindings/3` for the first when the run starts, and `verify/5` for the second. Both are
functions of the runtime, and not a part of a backend, because generated code may call only
`Feel.Runtime` and both backends have to give the same result.

  * `null` is exempt, as it is a value of every type.
  * A value that the tests accept is the value.
  * A value that they do not is `null`, with an error (`:value_not_allowed`) that ends a run
    in strict mode. That is `:enforce`. In the mode `{:report, severity}` (D-138) the value
    stays as it is and only the diagnostic is given, with that severity: an `:error` ends a
    strict run, and a `:warning` does not.
  * A variable whose declared type is marked `!` (D-48) cannot be `null`, so a value that the
    tests do not accept ends the run with an error of kind `:input` in the mode `:enforce`,
    instead of becoming `null`. The entry of the variable says so (the mark `:non_null`).
  * A variable declared as a list (`list<number>`) has the tests applied to each element, as the
    `allowedValues` of a DMN item definition with `isCollection` are (notes 03, section 4.3).
    The entry says so (the mark `:each`). A `null` element is exempt, as a `null` is. The first element
    that the tests do not accept makes the whole value not allowed, which is `null` with the error
    `:value_not_allowed` that says which element it is, and the report in the mode
    `{:report, severity}` is the same one error.
  * The constraint of a member of a variable (a component of a context, the elements of a list in it, D-161 step E)
    has the tests applied to what is at the end of its path (the mark `{:path, steps}`), `:each` in the steps being each
    element of a list. A member that is not there, or is `null`, is exempt; the first that the tests do not accept makes
    the whole variable not allowed, as for an element, and the error says where it is (`details.path`, `[2, "total"]` for
    the `total` of the second element).

Guarded recursive declarations use graph selectors in those paths (`t:Feel.Scope.step/0`). A selector
visits every finite occurrence of its target declaration, including aliases and nested collections.
Diagnostics contain the concrete member names and one-based list indexes, rather than the graph
selector. Null occurrences are exempt. Missing members are rejected by structural conformance,
not by allowed-value verification; membership alone does not enforce these constraints.

Traversal is incremental and stops at the first rejected occurrence. Reversed paths share their
tails while checks succeed; only the failing diagnostic constructs a complete forward path.

The tests are run with a lenient environment of their own, and what they report is given
the span of the variable (its first read in the expression), or of the expression for its
result, since the spans of the tests are those of their own text. The tests are alternatives,
and one that does not apply to a value of another kind (`[0..150]` for a string or a boolean, which
the comparison reports as a type mismatch) is not an error of the run when another accepts the
value. So the errors that the tests report are not given when the value is accepted, and when it
is not, they are the `causes` of the one `:value_not_allowed` (in its `details`), and the first of
them is its `hint`. Their warnings and infos are given as they are in the mode `:enforce`.

The variables are verified once, before anything is evaluated, against the values as they
were bound. A test that reads another variable sees it as the expression does (D-170): converted to its declared type and
verified, so a variable that is not allowed is `null` in the test (`age` with `<= max age`, for a `max age` that
`[18..65]` turns down, is not allowed either). That is in the IR of the test, which has the `:conform_bindings` and
`:verify_bindings` nodes of the variables it reads, so a backend runs it with nothing more; the constraints that are
being verified, and the ones whose tests read them, are not applied to a variable that a test reads. The stances that
leave a value as it is (`{:report, _}`) read every variable as it was bound. The expression then sees `null` for each
value that is not allowed.

# `binding_tests`

```elixir
@type binding_tests() :: (Feel.Value.t(), Feel.Runtime.Env.t() -&gt;
                      {boolean(), Feel.Runtime.Env.t()})
```

Runs the tests of a variable with `?` bound to a value, and gives whether they are satisfied.

# `mark`

```elixir
@type mark() :: :non_null | :each | {:path, [Feel.Scope.step()]}
```

What is known of a variable besides its tests: that it cannot be `null` (`:non_null`), that the tests are for the
elements of a list (`:each`), and the path from the variable down to what the tests are for (`{:path, steps}`): the
key of a member of a context, or `:each` for every element of a list.

# `mode`

```elixir
@type mode() :: :enforce | {:report, :error | :warning}
```

What is done with a value that is not allowed: `:enforce` makes it `null`, with an error, and
`{:report, severity}` leaves it and reports it with that severity (D-138).

# `subject`

```elixir
@type subject() :: Feel.Name.t() | :result
```

What is verified: a variable, by its name, or the result of the expression.

# `tests`

```elixir
@type tests() :: (Feel.Runtime.Env.t() -&gt; {boolean(), Feel.Runtime.Env.t()})
```

Runs the tests with `?` bound to the value, and gives whether they are satisfied.

# `verification`

```elixir
@type verification() ::
  {Feel.Name.t(), Feel.Diagnostic.span(), binding_tests()}
  | {Feel.Name.t(), Feel.Diagnostic.span(), binding_tests(), [mark()]}
```

What is verified of a variable: its canonical name, the span of its first read, and its tests.
The fourth element is there when the variable has marks: `:non_null` if its declared type is
marked `!`, and `:each` if it is declared as a list and the tests are for the elements.

# `report`

```elixir
@spec report(
  Feel.Runtime.Env.t(),
  Feel.Value.t(),
  subject(),
  Feel.Diagnostic.span(),
  tests(),
  :error | :warning
) :: Feel.Runtime.Env.t()
```

Reports a value that the tests do not accept, and leaves it as it is (the mode
`{:report, severity}`, D-138). `null` is exempt. Only the one diagnostic is given, with
`severity` and at `span`; the errors the tests gave for a value of the wrong kind are its `causes`.

    iex> env = Feel.Runtime.Env.new()
    iex> tests = fn env -> {false, env} end
    iex> env = Feel.Runtime.AllowedValues.report(env, Decimal.new(200), "age", {0, 3}, tests, :warning)
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> {diagnostic.severity, diagnostic.code, diagnostic.span}
    {:warning, :value_not_allowed, {0, 3}}
    iex> env = Feel.Runtime.Env.new()
    iex> env == Feel.Runtime.AllowedValues.report(env, nil, "age", {0, 3}, tests, :error)
    true

# `verify`

```elixir
@spec verify(
  Feel.Runtime.Env.t(),
  Feel.Value.t(),
  subject(),
  Feel.Diagnostic.span(),
  tests()
) ::
  {Feel.Value.t(), Feel.Runtime.Env.t()}
```

Verifies a value against the tests of its `subject`, found at `span`.

    iex> env = Feel.Runtime.Env.new()
    iex> tests = fn env -> {false, env} end
    iex> {nil, ^env} = Feel.Runtime.AllowedValues.verify(env, nil, "age", {0, 3}, tests)
    iex> {nil, env} = Feel.Runtime.AllowedValues.verify(env, Decimal.new(200), "age", {0, 3}, tests)
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> {diagnostic.code, diagnostic.span}
    {:value_not_allowed, {0, 3}}
    iex> {"ok", _} = Feel.Runtime.AllowedValues.verify(env, "ok", "age", {0, 3}, fn env -> {true, env} end)
    iex> {nil, env} = Feel.Runtime.AllowedValues.verify(env, "no", :result, {0, 9}, tests)
    iex> hd(Enum.reverse(Feel.Runtime.Env.diagnostics(env))).message
    "the result is not one of its allowed values"

# `verify_bindings`

```elixir
@spec verify_bindings(Feel.Runtime.Env.t(), Feel.bindings(), [verification()], mode()) ::
  {Feel.bindings(), Feel.Runtime.Env.t()}
```

Verifies the bound values of the variables that have allowed values, once, at the start of a run.

`verifications` has an entry for each variable: its canonical name, the span of its first read in the
expression, a function that runs its tests with `?` bound to the value it is given, and
a list of marks if it has any (`:non_null` for a type marked `!`, D-48, and `:each` for a list whose
elements the tests are for), see the top of this module. Returns
the bindings that the expression is to run with, which have `null` for a value that is not
allowed, and the environment with what was reported. A variable that is not bound is left
for the read to report, and a `null` is exempt (see `verify/5`). `mode` is `:enforce`, the
default, or `{:report, severity}`, which leaves the bindings as they are and only reports
(see `report/6`).

    iex> env = Feel.Runtime.Env.new()
    iex> tests = fn value, env -> {Decimal.compare(value, 150) != :gt, env} end
    iex> verifications = [{"age", {0, 3}, tests}]
    iex> {bindings, env} = Feel.Runtime.AllowedValues.verify_bindings(env, %{"age" => Decimal.new(200), "id" => 7}, verifications)
    iex> bindings
    %{"age" => nil, "id" => 7}
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> {diagnostic.code, diagnostic.span}
    {:value_not_allowed, {0, 3}}
    iex> {same, ^env} = Feel.Runtime.AllowedValues.verify_bindings(env, %{"age" => Decimal.new(20)}, verifications)
    iex> Decimal.equal?(same["age"], 20)
    true
    iex> required = [{"age", {0, 3}, tests, [:non_null]}]
    iex> {:error, error} = Feel.Runtime.Env.run(env, &Feel.Runtime.AllowedValues.verify_bindings(&1, %{"age" => Decimal.new(200)}, required))
    iex> {error.kind, error.code, error.span}
    {:input, :value_not_allowed, {0, 3}}
    iex> each = [{"ages", {0, 4}, tests, [:each]}]
    iex> {bindings, env} = Feel.Runtime.AllowedValues.verify_bindings(Feel.Runtime.Env.new(), %{"ages" => [Decimal.new(20), nil, Decimal.new(200)]}, each)
    iex> bindings
    %{"ages" => nil}
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> {diagnostic.code, diagnostic.details}
    {:value_not_allowed, %{name: "ages", index: 3}}
    iex> total = [{"orders", {0, 6}, tests, [{:path, [:each, "total"]}]}]
    iex> orders = [%{"total" => Decimal.new(20)}, %{"total" => nil}, %{"total" => Decimal.new(200)}]
    iex> {bindings, env} = Feel.Runtime.AllowedValues.verify_bindings(Feel.Runtime.Env.new(), %{"orders" => orders}, total)
    iex> bindings
    %{"orders" => nil}
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> {diagnostic.message, diagnostic.details}
    {~s(the value of "orders[3].total" is not one of its allowed values), %{name: "orders", path: [3, "total"]}}

# `verify_each`

```elixir
@spec verify_each(
  Feel.Runtime.Env.t(),
  Feel.Value.t(),
  subject(),
  Feel.Diagnostic.span(),
  binding_tests()
) :: {Feel.Value.t(), Feel.Runtime.Env.t()}
```

Verifies a result that is a list against the tests of its elements (the `:allowed` node with `each: true`, D-51).

`tests` runs the tests with `?` bound to the element it is given. A `null` element is exempt, and the first element
that the tests do not accept makes the whole result `null`, with the one error `:value_not_allowed` that says which
element it is (`details.index`).

    iex> env = Feel.Runtime.Env.new()
    iex> tests = fn value, env -> {Decimal.compare(value, 150) != :gt, env} end
    iex> {same, ^env} = Feel.Runtime.AllowedValues.verify_each(env, [Decimal.new(20), nil], :result, {0, 9}, tests)
    iex> same == [Decimal.new(20), nil]
    true
    iex> {nil, env} = Feel.Runtime.AllowedValues.verify_each(env, [Decimal.new(20), Decimal.new(200)], :result, {0, 9}, tests)
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> {diagnostic.code, diagnostic.message, diagnostic.details}
    {:value_not_allowed, "element 2 of the result is not one of its allowed values", %{result: true, index: 2}}

# `verify_member`

```elixir
@spec verify_member(
  Feel.Runtime.Env.t(),
  Feel.Value.t(),
  subject(),
  Feel.Diagnostic.span(),
  [Feel.Scope.step()],
  boolean(),
  binding_tests()
) :: {Feel.Value.t(), Feel.Runtime.Env.t()}
```

Verifies a result against the tests of a member of it (the `:allowed` node with `meta.path`): the value at the end of `steps`,
a key of a context, or `:each` for every element of a list, or each element of what is there when `each?` is true.

`tests` runs the tests with `?` bound to the member it is given. What is `null`, or is not there, is exempt, so a result that
has no such member is not rejected; the first member that the tests do not accept makes the whole result `null`, with the one
error `:value_not_allowed` that says where it is (`details.path`, `[2, "total"]` for the `total` of the second element).

    iex> env = Feel.Runtime.Env.new()
    iex> tests = fn value, env -> {Decimal.compare(value, 150) != :gt, env} end
    iex> person = %{"name" => "Ann", "age" => Decimal.new(20)}
    iex> {^person, ^env} = Feel.Runtime.AllowedValues.verify_member(env, person, :result, {0, 9}, ["age"], false, tests)
    iex> {%{"name" => "Ann"}, ^env} = Feel.Runtime.AllowedValues.verify_member(env, %{"name" => "Ann"}, :result, {0, 9}, ["age"], false, tests)
    iex> {nil, env} = Feel.Runtime.AllowedValues.verify_member(env, %{"age" => Decimal.new(200)}, :result, {0, 9}, ["age"], false, tests)
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> {diagnostic.code, diagnostic.message, diagnostic.details}
    {:value_not_allowed, ~s(the value of "result.age" is not one of its allowed values), %{name: :result, path: ["age"]}}
    iex> people = [%{"age" => Decimal.new(20)}, %{"age" => Decimal.new(200)}]
    iex> {nil, env} = Feel.Runtime.AllowedValues.verify_member(Feel.Runtime.Env.new(), people, :result, {0, 9}, [:each, "age"], false, tests)
    iex> [diagnostic] = Feel.Runtime.Env.diagnostics(env)
    iex> diagnostic.details.path
    [2, "age"]

---

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