Rindle.Delivery.WebhookBodyReader (Rindle v0.1.5)

Copy Markdown View Source

Raw-body cache for Rindle.Delivery.WebhookPlug.

Adopters wire this into their endpoint via the Plug.Parsers :body_reader option:

plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  body_reader: {Rindle.Delivery.WebhookBodyReader, :read_body, []},
  json_decoder: Jason

The reader drains the request body (looping over {:more, _, conn} reads that Plug.Parsers.JSON.decode/3 does NOT loop on), caps total bytes at 1 MiB, and stores the body in conn.assigns[:raw_body] as a list of binaries (most-recent first; multipart-safe).

raw_body/1 is the public accessor Rindle.Delivery.WebhookPlug calls to retrieve the verified raw body for HMAC signature checks.

Body cap (D-08)

Mux webhooks are <10 KB in practice; 1 MiB is 100× headroom and matches Stripe's documented recommendation. Over-limit returns {:error, :too_large}, which Plug.Parsers translates to Plug.Parsers.RequestTooLargeError → 413.

Multipart safety (D-06)

Plug.Parsers.MULTIPART invokes the body reader once per part; chunked transfers may produce multiple {:more, _} reads. The cache stores each drained body as one element of conn.assigns[:raw_body], most-recent first. raw_body/1 reverses and joins on read.

Summary

Functions

Returns the cached raw body from conn.assigns[:raw_body].

Plug.Parsers :body_reader MFA contract. Drains the body, caps at 1 MiB, caches the chunks in conn.assigns[:raw_body].

Functions

raw_body(conn)

@spec raw_body(Plug.Conn.t()) :: binary() | nil

Returns the cached raw body from conn.assigns[:raw_body].

Handles the canonical list-of-binaries shape:

  • single-element list → List.first/1
  • multi-element list (multipart / multi-chunk) → Enum.reverse |> IO.iodata_to_binary
  • missing assign → nil (caller falls back to Plug.Conn.read_body/2)

read_body(conn, opts \\ [])

@spec read_body(Plug.Conn.t(), Keyword.t()) ::
  {:ok, binary(), Plug.Conn.t()} | {:error, :too_large} | {:error, term()}

Plug.Parsers :body_reader MFA contract. Drains the body, caps at 1 MiB, caches the chunks in conn.assigns[:raw_body].

Returns {:ok, binary, conn} on success, {:error, :too_large} if the accumulated chunks exceed 1 MiB, or any {:error, term()} Plug.Conn.read_body/2 surfaces.