How to catch handler errors

View Source

Problem

A handler crashes (error(badmatch), throw(_), division by zero, etc.) and you want a controlled response instead of a propagating exit.

Solution

Wrap the rest of the stack with livery_middleware:wrap/1:

Stack = [
    {livery_request_id, undefined},
    livery_middleware:wrap(fun errors_to_resp/3),
    %% ... handler
].

errors_to_resp(throw, {validation, Why}, _Stack) ->
    livery_resp:text(400, Why);
errors_to_resp(error, {badkey, K}, _Stack) ->
    livery_resp:text(422,
        iolist_to_binary([<<"missing field: ">>, K]));
errors_to_resp(_Class, _Reason, _Stack) ->
    livery_resp:text(500, <<"internal error">>).

The wrapper catches throw, error, and exit from anything downstream. Class is throw | error | exit.

Without a wrapper

When a handler runs inside livery_req_proc (the worker the H1/H2/H3 adapters spawn), a crash is automatically mapped to livery_resp:text(500, <<"internal server error">>). The wrapper is for when you want a custom shape (validation errors as 400, business errors as 422, etc.).

In tests

livery_test_adapter:run/3 runs synchronously. Without a wrapper a crash propagates up through the test process. Either:

Domain exceptions as control flow

Throw to short-circuit from deep inside the call tree:

authenticate(Req) ->
    case verify(Req) of
        {ok, U} -> U;
        error   -> throw({validation, <<"bad credentials">>})
    end.

show(Req) ->
    _User = authenticate(Req),
    livery_resp:json(200, payload()).

The wrap middleware turns the throw({validation, ...}) into a 400 without explicit handler-level error handling.

See also