livery_resp (livery v0.1.0)

View Source

Response constructors and accessors.

Handlers return an immutable #livery_resp{} value. The core emits it onto the wire by walking the body variant and driving the adapter's send_headers/send_data/send_trailers calls. Response builders here are pure: they never touch sockets.

Summary

Functions

Append a header even when one with this name exists.

Remove every header with this name.

Headers-only response.

Send a file from the filesystem.

file/2 with an explicit byte range.

text/html; charset=utf-8 response.

Wrap pre-encoded JSON bytes as a response.

Newline-delimited JSON streaming response.

Build a response with the given status and headers, no body.

Build a response with status, headers, and a body variant.

Redirect response, setting the Location header.

Server-Sent Events response.

Streaming chunked response.

text/plain; charset=utf-8 response.

Protocol upgrade response (WebSocket / WebTransport).

Replace the body variant, keeping status, headers, and trailers.

Set the Cache-Control header.

Set the ETag header.

Replace (or insert) a header. Names are matched case-insensitively.

Types

body()

-type body() ::
          {full, iodata()} |
          {chunked, fun((term()) -> ok | {error, term()})} |
          {sse, fun((term()) -> ok | {error, term()})} |
          {file, file:name_all(), undefined | {non_neg_integer(), non_neg_integer() | eof}} |
          {upgrade, ws | wt, term()} |
          empty | taken_over.

cache_directive()

-type cache_directive() ::
          no_cache | no_store | public | private | immutable | must_revalidate | proxy_revalidate |
          no_transform |
          {max_age, non_neg_integer()} |
          {s_maxage, non_neg_integer()} |
          {stale_while_revalidate, non_neg_integer()} |
          {stale_if_error, non_neg_integer()}.

header_name()

-type header_name() :: binary().

header_value()

-type header_value() :: binary().

resp()

-type resp() ::
          #livery_resp{status :: 100..599,
                       headers :: [{binary(), binary()}],
                       body ::
                           {full, iodata()} |
                           {chunked, fun((term()) -> ok | {error, term()})} |
                           {sse, fun((term()) -> ok | {error, term()})} |
                           {file,
                            file:name_all(),
                            undefined | {non_neg_integer(), non_neg_integer() | eof}} |
                           {upgrade, ws | wt, term()} |
                           empty | taken_over,
                       trailers ::
                           undefined | [{binary(), binary()}] | fun(() -> [{binary(), binary()}])}.

Functions

append_header/3

-spec append_header(header_name(), header_value(), resp()) -> resp().

Append a header even when one with this name exists.

body/1

-spec body(resp()) -> body().

delete_header/2

-spec delete_header(header_name(), resp()) -> resp().

Remove every header with this name.

empty(Status)

-spec empty(100..599) -> resp().

Headers-only response.

file(Status, Path)

-spec file(100..599, file:name_all()) -> resp().

Send a file from the filesystem.

Range is undefined for the whole file, or {Offset, Length} where Length may be eof.

file(Status, Path, Range)

-spec file(100..599, file:name_all(), undefined | {non_neg_integer(), non_neg_integer() | eof}) ->
              resp().

file/2 with an explicit byte range.

headers/1

-spec headers(resp()) -> [{header_name(), header_value()}].

html(Status, Body)

-spec html(100..599, iodata()) -> resp().

text/html; charset=utf-8 response.

html(Status, ExtraHeaders, Body)

-spec html(100..599, [{header_name(), header_value()}], iodata()) -> resp().

html/2 with extra headers.

json(Status, Body)

-spec json(100..599, iodata()) -> resp().

Wrap pre-encoded JSON bytes as a response.

Livery does not bundle a JSON codec. Pass already-encoded iodata. Higher-level helpers plugging in thoas or jsx can sit on top of this builder.

json(Status, ExtraHeaders, Body)

-spec json(100..599, [{header_name(), header_value()}], iodata()) -> resp().

json/2 with extra headers.

ndjson(Status, Producer)

-spec ndjson(100..599, fun((term()) -> ok | {error, term()})) -> resp().

Newline-delimited JSON streaming response.

The producer is called with an Emit callback that takes any JSON-encodable Erlang term. Each Emit(Term) encodes the term via the OTP json module, appends a literal \\n, and writes one frame. Content-Type defaults to application/x-ndjson.

livery_resp:ndjson(200, fun(Emit) ->
    [Emit(#{n => N}) || N <- lists:seq(1, 5)],
    ok
end).

For pre-encoded bytes, use stream/3 directly.

ndjson(Status, ExtraHeaders, Producer)

-spec ndjson(100..599, [{header_name(), header_value()}], fun((term()) -> ok | {error, term()})) ->
                resp().

ndjson/2 with extra headers.

new(Status, Headers)

-spec new(100..599, [{header_name(), header_value()}]) -> resp().

Build a response with the given status and headers, no body.

new(Status, Headers, Body)

-spec new(100..599, [{header_name(), header_value()}], body()) -> resp().

Build a response with status, headers, and a body variant.

redirect(Status, Location)

-spec redirect(301 | 302 | 303 | 307 | 308, iodata()) -> resp().

Redirect response, setting the Location header.

redirect(Status, Location, ExtraHeaders)

-spec redirect(301 | 302 | 303 | 307 | 308, iodata(), [{header_name(), header_value()}]) -> resp().

redirect/2 with extra headers.

sse(Status, Producer)

-spec sse(100..599, fun((term()) -> ok | {error, term()})) -> resp().

Server-Sent Events response.

sse(Status, ExtraHeaders, Producer)

-spec sse(100..599, [{header_name(), header_value()}], fun((term()) -> ok | {error, term()})) -> resp().

sse/2 with extra headers.

status/1

-spec status(resp()) -> 100..599.

stream(Status, Headers, Producer)

-spec stream(100..599, [{header_name(), header_value()}], fun((term()) -> ok | {error, term()})) ->
                resp().

Streaming chunked response.

The producer is called with a SendFun and drives body chunks until it returns.

text(Status, Body)

-spec text(100..599, iodata()) -> resp().

text/plain; charset=utf-8 response.

text(Status, ExtraHeaders, Body)

-spec text(100..599, [{header_name(), header_value()}], iodata()) -> resp().

text/2 with extra headers.

trailers/1

-spec trailers(resp()) ->
                  undefined |
                  [{header_name(), header_value()}] |
                  fun(() -> [{header_name(), header_value()}]).

upgrade(Kind, State)

-spec upgrade(ws | wt, term()) -> resp().

Protocol upgrade response (WebSocket / WebTransport).

with_body(Body, Resp)

-spec with_body(body(), resp()) -> resp().

Replace the body variant, keeping status, headers, and trailers.

Used by middleware (e.g. livery_compress) that rewrites the body after the handler has produced the response.

with_cache_control(Value, Resp)

-spec with_cache_control(binary() | [cache_directive()], resp()) -> resp().

Set the Cache-Control header.

Pass a verbatim binary, or a list of directives: the atoms no_cache, no_store, public, private, immutable, must_revalidate, proxy_revalidate, no_transform, or the tuples {max_age, Secs}, {s_maxage, Secs}, {stale_while_revalidate, Secs}, {stale_if_error, Secs}.

with_etag(Tag, Resp)

-spec with_etag(binary(), resp()) -> resp().

Set the ETag header.

A value already shaped as a strong ("...") or weak (W/"...") tag is used as-is; a bare token is wrapped as a strong ETag.

with_header/3

-spec with_header(header_name(), header_value(), resp()) -> resp().

Replace (or insert) a header. Names are matched case-insensitively.

with_status(Status, Resp)

-spec with_status(100..599, resp()) -> resp().

with_trailers(Trailers, Resp)

-spec with_trailers(undefined |
                    [{header_name(), header_value()}] |
                    fun(() -> [{header_name(), header_value()}]),
                    resp()) ->
                       resp().

Attach trailers.

Pass a list of {Name, Value} pairs computed up front, or a fun fun() -> [{Name, Value}] evaluated lazily after the body has been emitted.