# `SquatchMail.SNS.RawBodyReader`
[🔗](https://github.com/Axio-Intelligence/Squatch-Mail/blob/v0.1.0/lib/squatch_mail/sns/raw_body_reader.ex#L1)

A `Plug.Parsers` body reader that caches the raw request body in
`conn.assigns[:raw_body]` before returning it for JSON parsing.

`SquatchMail.SNS.MessageVerifier`/`SquatchMail.SNS.Processor.process/2` need
the *exact* bytes SNS sent (to rebuild the signed string and, incidentally,
because `Processor.process/2` does its own JSON decode) - by the time
`Plug.Parsers` hands a controller a parsed `conn.params`, the original body
is gone unless something captured it first.

## Wiring this in

`Plug.Parsers` accepts a `:body_reader` option that must be a `{module,
function, args}` tuple implementing the same contract as
`Plug.Conn.read_body/2` (`{:ok, body, conn} | {:more, partial, conn} |
{:error, term}`), invoked once per chunk. Point it at this module wherever
the webhook route's pipeline configures `Plug.Parsers`, scoped to just the
`/webhooks/sns/:token` path (raw-body caching in `conn.assigns` is a small,
bounded cost - fine to scope broadly too, but the SNS route is the only one
that needs it):

    plug Plug.Parsers,
      parsers: [:json],
      json_decoder: Jason,
      body_reader: {SquatchMail.SNS.RawBodyReader, :read_body, []}

This must run *before* `:json` parsing consumes the body. The router or
endpoint that mounts `SquatchMail.Web.WebhookController` needs a pipeline
built this way for the webhook path (the dashboard's browser
pipeline/live_session for the rest of the routes does not need this).

# `read_body`

```elixir
@spec read_body(
  Plug.Conn.t(),
  keyword()
) ::
  {:ok, binary(), Plug.Conn.t()}
  | {:more, binary(), Plug.Conn.t()}
  | {:error, term()}
```

Drop-in replacement for `Plug.Conn.read_body/2` that additionally appends
every chunk read to `conn.assigns[:raw_body]` (starting from `""`), so the
full raw body is available under that key once parsing completes -
including when the body arrives in multiple chunks (`{:more, ...}`).

---

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