# `Dmn.Limits`
[🔗](https://github.com/FeeJai/BDM/blob/dmn-v0.1.0/apps/dmn/lib/dmn/limits.ex#L1)

The bounds that `dmn` keeps on a model that comes from outside.

A model is untrusted input, and so are the models it imports. Passing a bound is an error value of the kind `:limit`
(`Dmn.Error`), never a crash. So is a `limits:` option that is none of `nil`, a `t:t/0` or a keyword list of its fields (`validate/1`, DD-39). The defaults are generous for a model that a person wrote (the largest in the TCK is a few
hundred kB) and small for one made to hurt; a caller that reads models it trusts can raise them, and one that reads models of
anyone should lower them to what its models need.

**Reading the XML** (`Dmn.XML`)

| Field | Default | Bounds |
| ----- | ------- | ------ |
| `max_bytes` | 5,000,000 | the size of one document |
| `max_depth` | 100 | how deep the elements nest |
| `max_elements` | 200,000 | the elements in one document |
| `max_attributes` | 100 | the attributes of one element, and its namespace declarations |
| `max_name_length` | 256 | the length of the name of an element or an attribute, and of a prefix |
| `max_uri_length` | 1,024 | the length of a namespace URI; every element that is in it holds a copy of it |
| `xml_timeout` | 5,000 | milliseconds that reading one document may take (`Feel.Worker`) |
| `max_memory` | 1,000,000,000 | bytes that the process reading it may grow to |

The SAX parser takes time quadratic in the attributes of one element, and has no bound of its own. The count is checked when
the parser reports the element, which is after the work; `xml_timeout` and `max_memory` are what stop a document made to
waste it.

These document limits do not widen an enclosing `guard:` policy. Ordinary `Dmn.load/2` and
`Dmn.compile/2` default to a 128 MiB heap and a five-second deadline for the complete operation,
including its input/result transfers. The XML worker inherits tighter enclosing allowances.

**Imports** (`Dmn.Resolver`)

| Field | Default | Bounds |
| ----- | ------- | ------ |
| `max_units` | 10,000 | input data, decisions, knowledge models and services in the whole graph |
| `max_models` | 64 | the models of one compile, the root and all it imports |
| `max_import_depth` | 16 | the imports in a chain |
| `max_import_reads` | 256 | the documents that the resolver is asked for, whatever comes of them (an import that fails is a read too) |
| `max_import_bytes` | 20,000,000 | the bytes of all the documents that the imports bring, the ones that cannot be read included |
| `import_timeout` | 30,000 | milliseconds that reading all the imports may take; a document is read with what is left of it as its `xml_timeout` |

**The checks of the decision tables** (`Dmn.Compiler.Checks`)

| Field | Default | Bounds |
| ----- | ------- | ------ |
| `table_check_timeout` | 1,000 | milliseconds that the questions about one table (overlap, never reached, complete) may take |
| `checks_timeout` | 10,000 | milliseconds that the questions about all the tables of one compile may take |

The number of rules and of cells of a table that is asked about is limited (200 and 4,000), and the time is not bounded by that:
100 rules of ranges and `not(…)` with 4 inputs took 38 s to answer and 6 ms to compile without. The questions are asked in a
process that is ended at the deadline, and a table that passed it is said to be unchecked (`:table_not_checked`, an info),
which is not an answer to the questions.

The limits of one document, times the models, are more than a compile should spend (`max_models` times `max_bytes` is 320 MB of
text), and a root can hold tens of thousands of imports that all fail: so the whole graph of imports has a budget of its own. A
document is asked for once for each model and location (`Dmn.Imports`), so the same import that fails twice is not read twice.
The resolver itself is the caller's, and its time is only counted, not cut: `guard:` on `Dmn.compile/2` is what ends it.

# `t`

```elixir
@type t() :: %Dmn.Limits{
  checks_timeout: pos_integer(),
  import_timeout: pos_integer(),
  max_attributes: pos_integer(),
  max_bytes: pos_integer(),
  max_depth: pos_integer(),
  max_elements: pos_integer(),
  max_import_bytes: pos_integer(),
  max_import_depth: pos_integer(),
  max_import_reads: pos_integer(),
  max_memory: pos_integer(),
  max_models: pos_integer(),
  max_name_length: pos_integer(),
  max_units: pos_integer(),
  max_uri_length: pos_integer(),
  table_check_timeout: pos_integer(),
  xml_timeout: pos_integer()
}
```

# `from_option`

```elixir
@spec from_option(t() | keyword() | nil) :: t()
```

Returns the limits that `Dmn` functions take as an option: a `t:t/0`, or the fields to replace. Raises as `new/1` does; `validate/1` does not.

# `new`

```elixir
@spec new(keyword()) :: t()
```

The default limits, with any field replaced.

    iex> Dmn.Limits.new(max_depth: 10).max_depth
    10

Raises `ArgumentError` for a field that is not one of the limits, or a value that is not a positive integer.

# `validate`

```elixir
@spec validate(term()) :: {:ok, t()} | {:error, String.t()}
```

The limits that a caller passed as an option (`limits:` of `Dmn.load/2` and `Dmn.compile/2`), or why they are none: `nil`, a `t:t/0`
or a keyword list of its fields, with every value a positive integer. Nothing raises for what the caller passed (DD-39).

    iex> {:ok, %Dmn.Limits{max_depth: 10}} = Dmn.Limits.validate(max_depth: 10)
    iex> {:error, _message} = Dmn.Limits.validate(max_depth: -1)
    iex> {:error, _message} = Dmn.Limits.validate(:x)

---

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