# `Linx.Netfilter.Rule`
[🔗](https://github.com/oshlabs/linx/blob/v0.2.0/lib/linx/netfilter/rule.ex#L1)

A single nftables rule — an ordered list of expressions that the
kernel evaluates against each packet visiting the rule's chain.

## Fields

  * `:expressions` — ordered list of `%Linx.Netfilter.Expr{}`.
    The terminator is typically a verdict-bearing `immediate`
    expression; if absent, falls through to the next rule.

  * `:chain` — name (string) of the chain this rule belongs to.
    `nil` for free-standing rules; populated when the rule is
    inserted into a chain (and when pulled from the kernel).

  * `:handle` — the kernel-assigned rule handle (positive
    integer). `nil` for authoring-time rules; populated on the
    first successful `push/2` and round-tripped on `pull/1..2`.
    Required for in-place `:replace` / `:delete` of rules without
    a tag.

  * `:tag` — Linx-side stable identity (atom). The basis of
    `:reconcile` mode's diffing: a Rule keeps its identity across
    pushes regardless of position or handle, as long as the tag
    matches. `nil` is allowed; untagged rules fall back to
    positional identity (and forfeit `:reconcile` diffability).

  * `:comment` — a user-supplied comment string, round-tripped via
    the kernel's `NFTNL_RULE_USERDATA` opaque slot.

## Construction

Use `build/1` or `build/2` with an expression list. The list may
include verdict sugar — atoms (`:accept`), tuples (`{:jump, name}`),
or `%Linx.Netfilter.Verdict{}` values — which are normalised to
`Expr.immediate/1` calls so the resulting Rule is uniform.

    iex> Rule.build([Expr.immediate(:accept)])
    {:ok, #Linx.Netfilter.Rule<[immediate accept]>}

    iex> Rule.build([:drop], tag: :default_deny, comment: "block-all")
    {:ok, #Linx.Netfilter.Rule<:default_deny [immediate drop]>}

    iex> Rule.build([{:jump, "input_extras"}])
    {:ok, #Linx.Netfilter.Rule<[immediate jump "input_extras"]>}

Bang variant `build!/2` raises `ArgumentError` on validation
failure. The non-bang form returns `{:error, {:bad_rule, reason}}`.

## Inspect

Compact: tag (if set) then bracketed expressions. Comment +
handle are omitted from the brief rendering; pattern-match on
the struct fields if you need the full detail.

# `expression_input`

```elixir
@type expression_input() ::
  Linx.Netfilter.Expr.t()
  | Linx.Netfilter.Verdict.t()
  | Linx.Netfilter.Verdict.input()
```

# `t`

```elixir
@type t() :: %Linx.Netfilter.Rule{
  chain: String.t() | nil,
  comment: String.t() | nil,
  expressions: [Linx.Netfilter.Expr.t()],
  handle: pos_integer() | nil,
  tag: atom() | nil
}
```

# `build`

```elixir
@spec build(
  [expression_input()],
  keyword()
) :: {:ok, t()} | {:error, {:bad_rule, term()}}
```

Builds a Rule from a list of expressions (or verdict-sugar).

Options:

  * `:chain` — the owning chain's name; usually set by the
    chain-level helpers, but allowed at build time for round-trip
    from a `pull/1..2`.
  * `:handle` — the kernel-assigned handle; usually `nil` until
    the first `push/2`.
  * `:tag` — atom identity (for `:reconcile`-mode diffing).
  * `:comment` — round-trips via `NFTNL_RULE_USERDATA`.

# `build!`

```elixir
@spec build!(
  [expression_input()],
  keyword()
) :: t()
```

Bang variant of `build/2` — returns the rule or raises
`ArgumentError` describing the failure.

# `verdict`

```elixir
@spec verdict(t()) :: Linx.Netfilter.Verdict.t() | nil
```

Returns the rule's terminal verdict, if any — the trailing
verdict-bearing expression. `nil` for fall-through rules.

---

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