-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]).