# `RequestSeal.Body`
[🔗](https://github.com/baselabs/request_seal/blob/v0.5.2/lib/request_seal/body.ex#L1)

Caller-owned body availability, without implicit reads or replayability.

A body is `:retained` (exact bytes held in memory), `:streaming` (an opaque
caller-owned PID or reference), `:unavailable`, or `:consumed`. Empty retained
bytes are different from unavailable bytes. Stream handles are opaque;
construction and validation never invoke, enumerate, close, or claim to have
consumed them.

Values are snapshots supplied by the caller, not a stream controller. A later
snapshot must report consumption; copying a value does not make a stream
replayable. A stream can remain unread after transport completes, so known
trailers can coexist with `:streaming`. This module neither hashes content
nor validates message framing.
Inspection hides body bytes and handles.

# `t`

```elixir
@type t() :: %RequestSeal.Body{
  bytes: binary() | nil,
  max_bytes: non_neg_integer(),
  ownership: :caller,
  source: pid() | reference() | nil,
  state: :retained | :streaming | :unavailable | :consumed
}
```

# `new`

```elixir
@spec new(map()) :: {:ok, t()} | {:error, RequestSeal.Message.Error.t()}
```

Construct an availability snapshot from an attribute map.

## Parameters

  * `attrs` - a plain map (not a struct) using only these keys; unknown keys reject:
    * `:state` (required) - `:retained`, `:streaming`, `:unavailable`, or `:consumed`.
    * `:bytes` - binary, required for `:retained` and otherwise absent or `nil`.
    * `:source` - a caller-owned PID or reference, required for `:streaming` and
      otherwise absent or `nil`.
    * `:ownership` - only `:caller` (the default).
    * `:max_bytes` - nonnegative integer bounding retained bytes, default
      1,048,576. Any nonnegative integer is allowed as the caller's retention
      policy.

## Returns

  * `{:ok, %RequestSeal.Body{}}`.
  * `{:error, %RequestSeal.Message.Error{reason: :invalid_body}}` - the only
    reason: a missing or unknown key, a state without its required value, a
    value not allowed for the state, or retained bytes over `:max_bytes`.
    Meanings: `RequestSeal.Message.Error`.

## Examples

    iex> {:ok, body} = RequestSeal.Body.new(%{state: :retained, bytes: <<0, 255>>, max_bytes: 2})
    iex> {body.state, body.bytes, body.ownership}
    {:retained, <<0, 255>>, :caller}
    iex> RequestSeal.Body.new(%{state: :streaming})
    {:error, %RequestSeal.Message.Error{reason: :invalid_body}}

A stream handle is kept but never read:

    iex> source = make_ref()
    iex> {:ok, body} = RequestSeal.Body.new(%{state: :streaming, source: source})
    iex> body.source == source
    true
    iex> RequestSeal.Body.new(%{state: :retained, bytes: "crawl page 3", max_bytes: 4})
    {:error, %RequestSeal.Message.Error{reason: :invalid_body}}

# `validate`

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

Validate a snapshot without touching the caller's body source.

Applies the rules of `new/1` to an existing struct, including one built or
modified directly.

## Parameters

  * `body` - any term; only a `RequestSeal.Body` struct can pass.

## Returns

  * `:ok`.
  * `{:error, %RequestSeal.Message.Error{reason: :invalid_body}}` - the only
    reason. Meanings: `RequestSeal.Message.Error`.

## Examples

    iex> {:ok, body} = RequestSeal.Body.new(%{state: :consumed})
    iex> RequestSeal.Body.validate(body)
    :ok
    iex> RequestSeal.Body.validate(%{body | bytes: "already read"})
    {:error, %RequestSeal.Message.Error{reason: :invalid_body}}

---

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