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

The item definitions of a DMN model, read into the types and the allowed values of a `Feel.Scope` (DMN 1.6,
section 7.3.2, "Item definition"; D-99).

`feel` has no DMN reader, so a definition is given as data, the way a model layer reads it from the model:

    %{
      name: "tPerson",
      components: [
        %{name: "name", type_ref: "string"},
        %{name: "age", type_ref: "number", allowed_values: "[0..150]"}
      ]
    }

| Key | Is |
| --- | -- |
| `:name` | the name of the item definition (required) |
| `:type_ref` | a built-in type (`"string"`, `"date and time"`, `"Any"`...), or the name of another item definition |
| `:collection?` | `true` for `isCollection="true"`: a list of the type |
| `:components` | the `itemComponent`s: definitions of their own, with a `:name`; the type is a context of them |
| `:allowed_values` | the `allowedValues`, the source text of a list of unary tests (`"[0..255]"`, `"\"FOO\", \"BAR\""`); for a collection they are for each element |
| `:type_constraint` | the `typeConstraint`, the same for the value as a whole: for a collection, the list (`"count(?) > 0"`) |
| `:function` | a `functionItem`: `%{output: type_ref, parameters: [%{name: name, type_ref: type_ref}]}` |

A definition has one of `:type_ref`, `:components` and `:function`, or none of them, which is the type `Any` (it
constrains nothing). The definitions may refer to one another in any order, and a component may be of a type that
is defined later.

## What is read

  * **The type** of each definition (`types/1`): the built-in type, a list of it for a collection, a context of the
    types of its components (`{:context, %{"name" => :string, "age" => :number}}`), or a function type
    (`{:function, [:string, :number], :string}`). A type that refers to another is the type of that. A
    context type is what `instance of` and the declared types of a scope make of it: a value is an instance
    if it has the components (`{a: 1, b: 2, c: 3}` is an instance of the type of `a` and `b`), and a function type
    is one of the parameter types and the output type. Allowed values play no part in `instance of`: `256 instance of
    t255` is `true` for a number with the allowed values `[0..255]` (TCK, 0070 `number_013`).
  * **The constraints** (`constraints/1`) that a variable declared with the definition has to satisfy (D-138, D-51,
    D-161): its allowed values and its type constraint, those of the definition and those of the one it refers to,
    and those of the components, each for the member it is for (`steps`: the key of a component, and `:each` for the
    elements of a collection). They all have to hold, and each is a part of the type of what it is for, when it is
    read. A collection's `allowedValues` are for each element, as they are for a variable declared as a list, and so are
    those of a collection of collections for the elements of the elements.
  * **What cannot be read** (`problems/1`). An undefined reference or incompatible definition kinds
    leave a definition without a type. Recursive references are rejected by default (`:recursive`).
  * **Legacy approximate recursion**, with `recursive: :any`, replaces the reference closing a loop
    with `Any` (D-189). For `tNode` with `value: number` and `children: list<tNode>`, the cut can produce
    `context<children: list<Any>, value: number>`. This deliberately accepts descendants that the full
    declaration would reject. It is distinct from guarded data validation.
  * **Guarded recursive data**, with `recursive: :guarded`, retains a finite graph instead of discarding back
    references. Every cycle must consume a context member or list element and cannot cross a function signature.
    Runtime membership and declaration constraints check every finite descendant. Analysis uses only a finite upper
    approximation and retains uncertain recursive checks. Alias-only cycles report `:unguarded_recursion`;
    cycles through function signatures report `:recursive_function`. A finite function item may still
    accept or return recursive data. Type conformance between different recursive graphs is conservative.

## A scope

`scope/2` makes a `Feel.Scope` of the variables of a model, as `name => type_ref`: the `types` are all the item
definitions that could be read, so that `x instance of tFooBar` and `function(a: tPerson)` mean what they say,
and each variable is of its type, with the allowed values of it. A variable that cannot be given its type is left
out and reported, so that the caller can tell the expressions that read it.

    iex> definitions = Feel.ItemDefinitions.new([%{name: "tAge", type_ref: "number", allowed_values: "[0..150]"}])
    iex> {scope, unread} = Feel.ItemDefinitions.scope(definitions, %{"age" => "tAge", "name" => "string"})
    iex> {scope.names, Feel.Scope.allowed_values(scope), unread}
    {%{"age" => :number, "name" => :string}, %{"age" => "[0..150]"}, %{}}

# `constraint`

```elixir
@type constraint() :: %{
  steps: [Feel.Scope.step()],
  kind: Feel.Scope.kind(),
  source: String.t()
}
```

What a value has to satisfy: unary tests (`source`) of the `kind` `:allowed_values` or `:type_constraint`, for the
member of the value at the end of `steps` (see `t:Feel.Scope.step/0`), or for the value itself with none.

# `definition`

```elixir
@type definition() :: %{
  :name =&gt; String.t(),
  optional(:type_ref) =&gt; String.t() | nil,
  optional(:collection?) =&gt; boolean(),
  optional(:components) =&gt; [definition()],
  optional(:allowed_values) =&gt; String.t() | nil,
  optional(:type_constraint) =&gt; String.t() | nil,
  optional(:function) =&gt; %{
    optional(:output) =&gt; String.t() | nil,
    optional(:parameters) =&gt; [
      %{:name =&gt; String.t(), optional(:type_ref) =&gt; String.t() | nil}
    ]
  }
}
```

An item definition, or an item component, as data.

# `problem`

```elixir
@type problem() ::
  {:undefined_type, String.t()}
  | :recursive
  | :more_than_one_kind
  | :duplicate_component
  | :unguarded_recursion
  | :recursive_function
```

Why a definition has no type.

# `t`

```elixir
@type t() :: %Feel.ItemDefinitions{
  constraints: %{optional(String.t()) =&gt; [constraint()]},
  problems: %{optional(String.t()) =&gt; [problem()]},
  types: %{optional(String.t()) =&gt; Feel.Type.t()}
}
```

# `base_scope`

```elixir
@spec base_scope(t()) :: Feel.Scope.t()
```

The scope that has the types of the definitions and no variable: what `scope/3` adds the variables of an expression to.

# `constraints`

```elixir
@spec constraints(t()) :: %{optional(String.t()) =&gt; [constraint()]}
```

What a value of each item definition that has any has to satisfy, as the source text of unary tests: its own, those of the
definition it refers to, and those of its components, by the member they are for.

    iex> definitions = Feel.ItemDefinitions.new([
    ...>   %{name: "tPerson", components: [%{name: "age", type_ref: "number", allowed_values: "[0..150]"}]},
    ...>   %{name: "tPeople", type_ref: "tPerson", collection?: true, type_constraint: "count(?) > 0"}
    ...> ])
    iex> Feel.ItemDefinitions.constraints(definitions)["tPeople"]
    [
      %{steps: [:each, "age"], kind: :allowed_values, source: "[0..150]"},
      %{steps: [], kind: :type_constraint, source: "count(?) > 0"}
    ]

# `new`

```elixir
@spec new([definition()], [{:recursive, :error | :any | :guarded}]) :: t()
```

Reads the item definitions of a model. Raises `ArgumentError` for two with one name.

The option `:recursive` is `:error` by default, and a definition that refers to itself has no type (`problems/1`). With
`:any` it has the type that the loop is cut at, the reference that closes it being `Any` (D-189): the definitions are
read in the order of their names, so that what the cut is does not depend on the order the model gave them in.

    iex> definitions = Feel.ItemDefinitions.new([
    ...>   %{name: "tNames", type_ref: "string", collection?: true},
    ...>   %{name: "tPerson", components: [%{name: "name", type_ref: "string"}, %{name: "friends", type_ref: "tNames"}]}
    ...> ])
    iex> Feel.ItemDefinitions.types(definitions)
    %{"tNames" => {:list, :string}, "tPerson" => {:context, %{"name" => :string, "friends" => {:list, :string}}}}

The option `:guarded` retains guarded data recursion as a finite graph, checks full finite values and recursive
component constraints, and rejects unguarded or function-type cycles. Type relations remain conservative.

    iex> definitions = Feel.ItemDefinitions.new([
    ...>   %{name: "Node", components: [%{name: "next", type_ref: "Node"}]}
    ...> ], recursive: :guarded)
    iex> type = Feel.ItemDefinitions.types(definitions)["Node"]
    iex> {Feel.Type.value_conforms?(%{"next" => %{"next" => nil}}, type),
    ...>  Feel.Type.value_conforms?(%{"next" => %{}}, type)}
    {true, false}

# `problems`

```elixir
@spec problems(t()) :: %{optional(String.t()) =&gt; [problem()]}
```

Why the item definitions that have no type have none, by name. They are not in `types/1`.

# `scope`

```elixir
@spec scope(t(), %{optional(String.t()) =&gt; String.t() | nil}, Feel.Scope.t() | nil) ::
  {Feel.Scope.t(), %{optional(String.t()) =&gt; [problem()]}}
```

A scope of the variables of a model, given as `name => typeRef`, and the ones that could not be given a type, with why.

The `types` of the scope are all the item definitions that could be read, so that their names mean something in an
expression; the `names` are the variables, each of its declared type, and their constraints are those of the
definition it is declared with, for the variable and for its members.

Making the types of a scope costs what the definitions are (every name is canonicalized), so a caller that makes a scope for
each of many expressions over one set of definitions makes `base_scope/1` once and gives it as the third argument: the scope
is the same, and the types are not made again.

    iex> definitions = Feel.ItemDefinitions.new([%{name: "tAge", type_ref: "number", allowed_values: "[0..150]"}])
    iex> variables = %{"age" => "tAge", "nobody" => "tNowhere"}
    iex> {scope, unread} = Feel.ItemDefinitions.scope(definitions, variables)
    iex> {Feel.Scope.fetch_name(scope, "age"), Feel.Scope.allowed_values(scope), Map.keys(unread)}
    {{:ok, :number}, %{"age" => "[0..150]"}, ["nobody"]}
    iex> base = Feel.ItemDefinitions.base_scope(definitions)
    iex> {^scope, ^unread} = Feel.ItemDefinitions.scope(definitions, variables, base)

Use `scope/3` to carry guarded recursive constraints into an expression. Passing only the type
returned by `types/1` checks structure, but does not attach the declaration's allowed values or
type constraints. Under the default `declarations: :enforce`, an invalid descendant makes the
bound value `null`; its diagnostic identifies the concrete path. Other declaration stances
retain their ordinary meaning (`Feel.Scope`).

    iex> definitions = Feel.ItemDefinitions.new([
    ...>   %{name: "Node", components: [
    ...>     %{name: "value", type_ref: "number", allowed_values: "[0..9]"},
    ...>     %{name: "next", type_ref: "Node"}
    ...>   ]}
    ...> ], recursive: :guarded)
    iex> {scope, unread} = Feel.ItemDefinitions.scope(definitions, %{"node" => "Node"})
    iex> unread
    %{}
    iex> {:ok, expression} = Feel.prepare("node", scope: scope)
    iex> bad = %{"value" => Decimal.new(1), "next" => %{"value" => Decimal.new(20), "next" => nil}}
    iex> {:ok, nil, [diagnostic]} = Feel.run(expression, %{"node" => bad})
    iex> {diagnostic.code, diagnostic.details.path}
    {:value_not_allowed, ["next", "value"]}

For an expression's result boundary, use `variable/2` to obtain both the type and its constraints,
and pass them as `:result_type` and `:result_allowed_values` to `Feel.prepare/2`.

# `types`

```elixir
@spec types(t()) :: %{optional(String.t()) =&gt; Feel.Type.t()}
```

The type of each item definition that could be read, by its name.

# `variable`

```elixir
@spec variable(t(), String.t() | nil) ::
  {:ok, Feel.Type.t(), [constraint()]} | {:error, [problem()]}
```

The type and the constraints of a variable that is declared with this `typeRef`, or why it cannot be had.

`nil` is no declaration, which is the type `Any`. A built-in type has no constraints.

    iex> definitions = Feel.ItemDefinitions.new([%{name: "tFooBar", type_ref: "string", allowed_values: ~s("FOO", "BAR")}])
    iex> Feel.ItemDefinitions.variable(definitions, "tFooBar")
    {:ok, :string, [%{steps: [], kind: :allowed_values, source: ~s("FOO", "BAR")}]}
    iex> Feel.ItemDefinitions.variable(definitions, "number")
    {:ok, :number, []}
    iex> Feel.ItemDefinitions.variable(definitions, "tNowhere")
    {:error, [{:undefined_type, "tNowhere"}]}
    iex> Feel.ItemDefinitions.variable(definitions, "list<number!>")
    {:ok, {:list, {:non_null, :number}}, []}
    iex> Feel.ItemDefinitions.variable(definitions, "tFooBar!")
    {:ok, {:non_null, :string}, [%{steps: [], kind: :allowed_values, source: ~s("FOO", "BAR")}]}
    iex> Feel.ItemDefinitions.variable(definitions, "list<tFooBar>")
    {:error, [{:undefined_type, "list<tFooBar>"}]}

---

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