Skip to main content

src/livery_adapter.erl

-module(livery_adapter).
-moduledoc """
Internal behaviour implemented by `livery_h1`, `livery_h2`,
`livery_h3`, and `livery_test_adapter`.

Adapters translate engine events into `#livery_req{}` values and
drive the response back onto the wire. They own no state machines;
framing, header compression, flow control, and TLS belong upstream
in `h1`, `h2`, `quic`, and `ws`.

Each adapter spawns a `livery_req_proc` for every incoming request
and routes body/trailers/eof messages to that pid. Once the
handler returns a `#livery_resp{}`, the core walks the body
variant and calls back into the adapter via the callbacks defined
here.

## Callbacks

- `start(Name, ListenSpec, Opts) -> {ok, Listener} | {error, _}` —
  start a listener for this adapter.
- `stop(Listener) -> ok` — stop a listener cleanly.
- `send_headers(Stream, Status, Headers, SendOpts) -> SendResult` —
  emit response headers. `SendOpts` may carry
  `end_stream => true` when the response has no body.
- `send_data(Stream, IoData, SendOpts) -> SendResult` — emit body
  bytes. `end_stream => true` closes the send half;
  `flush => true` hints the adapter to push immediately rather
  than batch.
- `send_trailers(Stream, Trailers) -> SendResult` — emit trailers
  (and implicitly close the send half).
- `reset(Stream, Reason) -> ok` — reset a stream with a
  protocol-specific reason.
- `peer_info(Stream) -> peer_info()` — return peer/TLS info for a
  stream.
- `capabilities(Listener) -> capabilities()` — return the
  capability bitmap of a listener.
""".

-export_type([
    listener/0,
    listen_spec/0,
    opts/0,
    stream/0,
    send_opts/0,
    capabilities/0,
    peer_info/0,
    reset_reason/0,
    send_result/0
]).

-type listener() :: term().
-type listen_spec() :: term().
-type opts() :: map().
-type stream() :: term().

-type send_opts() :: #{
    end_stream => boolean(),
    flush => boolean(),
    %% Per-response early-response inbound-drain budget. Honored by the
    %% HTTP/1.1 adapter on the stream-terminating call; ignored by
    %% adapters that read only `end_stream'.
    early_response_drain => livery_resp:drain()
}.

-type capabilities() :: #{
    trailers => boolean(),
    extended_connect => boolean(),
    datagrams => boolean(),
    capsules => boolean()
}.

-type peer_info() :: #{
    peer => {inet:ip_address(), inet:port_number()} | undefined,
    tls => undefined | map(),
    alpn => binary() | undefined
}.

-type reset_reason() :: term().

-type send_result() :: ok | {error, closed | flow | term()}.

-callback start(Name :: atom(), listen_spec(), opts()) ->
    {ok, listener()} | {error, term()}.

-callback stop(listener()) -> ok.

-callback send_headers(
    stream(),
    100..599,
    [{binary(), binary()}],
    send_opts()
) ->
    send_result().

-callback send_data(stream(), iodata(), send_opts()) -> send_result().

-callback send_trailers(stream(), [{binary(), binary()}]) -> send_result().

-callback reset(stream(), reset_reason()) -> ok.

-callback peer_info(stream()) -> peer_info().

-callback capabilities(listener()) -> capabilities().

%% Optional: emit a complete response (status, headers and body) in one
%% call so the adapter can coalesce it into a single write instead of a
%% separate `send_headers' + `send_data'. `emit/3' uses it for a `full'
%% body with no trailers when the adapter exports it, and otherwise
%% falls back to the granular callbacks.
-callback send_full(
    stream(),
    100..599,
    [{binary(), binary()}],
    iodata(),
    send_opts()
) ->
    send_result().

-optional_callbacks([send_full/5]).