Skip to main content

src/aws@lambda.erl

-module(aws@lambda).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/lambda.gleam").
-export([send_error/3, send_response/3, next/1, process_invocation/3, serve/3, api_from_env/0, start/1, local_event_from/2, context_default/0, invoke_once/2, run/1, invocation_error/2, json_handler/3, start_json/3, send_init_error/2, get_env/1]).
-export_type([context/0, invocation/0, invocation_error/0, runtime_error/0, handler_crash/0, api/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " AWS Lambda custom-runtime support.\n"
    "\n"
    " BEAM has no AWS-managed Lambda runtime, so a Gleam function deployed to\n"
    " Lambda has to implement the [Lambda Runtime API][api] itself: a small\n"
    " HTTP contract spoken over the loopback endpoint Lambda advertises in\n"
    " `AWS_LAMBDA_RUNTIME_API`. This module is that implementation.\n"
    "\n"
    " The lifecycle is a loop:\n"
    "\n"
    "   1. `GET  /runtime/invocation/next` — long-poll for the next event.\n"
    "      The response body is the raw event payload and the response\n"
    "      headers carry the per-invocation [`Context`](#Context).\n"
    "   2. Run the handler against the payload + context.\n"
    "   3. `POST /runtime/invocation/{id}/response` on success, or\n"
    "      `POST /runtime/invocation/{id}/error` on failure.\n"
    "   4. Repeat.\n"
    "\n"
    " Write `main` as:\n"
    "\n"
    " ```gleam\n"
    " import aws/lambda\n"
    " import gleam/bit_array\n"
    "\n"
    " pub fn main() {\n"
    "   lambda.start(fn(payload, _context) {\n"
    "     let assert Ok(text) = bit_array.to_string(payload)\n"
    "     Ok(bit_array.from_string(\"hello, \" <> text))\n"
    "   })\n"
    " }\n"
    " ```\n"
    "\n"
    " For typed events and JSON responses use [`start_json`](#start_json)\n"
    " together with the envelopes in `aws/lambda/event` and the responses in\n"
    " `aws/lambda/response`.\n"
    "\n"
    " `start` blocks forever, so it only returns when the runtime cannot\n"
    " continue — either because the process is not running under a Lambda\n"
    " runtime (`NotRunningInLambda`) or because the Runtime API became\n"
    " unreachable. The returned [`RuntimeError`](#RuntimeError) says which.\n"
    "\n"
    " A handler that raises (a `panic`, a failed `let assert`, or any Erlang\n"
    " exception) does not take the process down: the runtime traps it, reports\n"
    " it to `/error` as an `\"Unhandled\"` failure, and serves the next event.\n"
    "\n"
    " ## Deploying\n"
    "\n"
    " Lambda ships no managed BEAM runtime, so deploy as an OS-only custom\n"
    " runtime (`provided.al2023`). The package needs an executable `bootstrap`\n"
    " at its root that boots the VM and runs your `main`; for an Erlang release\n"
    " built against Amazon Linux 2023 that is roughly:\n"
    "\n"
    " ```sh\n"
    " #!/bin/sh\n"
    " set -eu\n"
    " exec \"${LAMBDA_TASK_ROOT}/bin/my_app\" foreground\n"
    " ```\n"
    "\n"
    " In that process Lambda sets `AWS_LAMBDA_RUNTIME_API` (the endpoint this\n"
    " module talks to), plus `_HANDLER` and `LAMBDA_TASK_ROOT`. The handler is\n"
    " whatever your `main` passes to `start`, so `_HANDLER` is unused.\n"
    "\n"
    " [api]: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html\n"
).

-type context() :: {context,
        binary(),
        integer(),
        binary(),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary())}.

-type invocation() :: {invocation, context(), bitstring()}.

-type invocation_error() :: {invocation_error,
        binary(),
        binary(),
        list(binary())}.

-type runtime_error() :: not_running_in_lambda |
    {invalid_endpoint, binary()} |
    {transport, aws@internal@http_send:http_error()} |
    missing_request_id |
    {unexpected_status, binary(), integer()}.

-type handler_crash() :: {handler_crash, binary(), binary(), list(binary())}.

-type api() :: {api,
        fun((gleam@http@request:request(bitstring())) -> {ok,
                gleam@http@response:response(bitstring())} |
            {error, aws@internal@http_send:http_error()}),
        binary()}.

-file("src/aws/lambda.gleam", 612).
-spec set_xray_trace_id(binary()) -> nil.
set_xray_trace_id(Trace_id) ->
    aws_ffi:set_env(<<"_X_AMZ_TRACE_ID"/utf8>>, Trace_id).

-file("src/aws/lambda.gleam", 402).
-spec describe_runtime_error(runtime_error()) -> binary().
describe_runtime_error(Error) ->
    case Error of
        not_running_in_lambda ->
            <<"not running under a Lambda runtime"/utf8>>;

        {invalid_endpoint, Endpoint} ->
            <<"invalid runtime endpoint: "/utf8, Endpoint/binary>>;

        {transport, _} ->
            <<"transport failure talking to the Runtime API"/utf8>>;

        missing_request_id ->
            <<"next-invocation response missing request id"/utf8>>;

        {unexpected_status, Endpoint@1, Status} ->
            <<<<<<"unexpected status "/utf8,
                        (erlang:integer_to_binary(Status))/binary>>/binary,
                    " from "/utf8>>/binary,
                Endpoint@1/binary>>
    end.

-file("src/aws/lambda.gleam", 563).
-spec send(api(), gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(bitstring())} |
    {error, runtime_error()}.
send(Api, Request) ->
    _pipe = (erlang:element(2, Api))(Request),
    gleam@result:map_error(_pipe, fun(Field@0) -> {transport, Field@0} end).

-file("src/aws/lambda.gleam", 570).
-spec expect_accepted(api(), binary(), gleam@http@request:request(bitstring())) -> {ok,
        nil} |
    {error, runtime_error()}.
expect_accepted(Api, Endpoint, Request) ->
    gleam@result:'try'(
        send(Api, Request),
        fun(Response) ->
            case (erlang:element(2, Response) >= 200) andalso (erlang:element(
                2,
                Response
            )
            < 300) of
                true ->
                    {ok, nil};

                false ->
                    {error,
                        {unexpected_status,
                            Endpoint,
                            erlang:element(2, Response)}}
            end
        end
    ).

-file("src/aws/lambda.gleam", 528).
-spec error_headers(invocation_error()) -> list({binary(), binary()}).
error_headers(Error) ->
    [{<<"lambda-runtime-function-error-type"/utf8>>, erlang:element(2, Error)},
        {<<"content-type"/utf8>>, <<"application/json"/utf8>>}].

-file("src/aws/lambda.gleam", 535).
-spec error_body(invocation_error()) -> bitstring().
error_body(Error) ->
    _pipe = gleam@json:object(
        [{<<"errorType"/utf8>>, gleam@json:string(erlang:element(2, Error))},
            {<<"errorMessage"/utf8>>,
                gleam@json:string(erlang:element(3, Error))},
            {<<"stackTrace"/utf8>>,
                gleam@json:array(
                    erlang:element(4, Error),
                    fun gleam@json:string/1
                )}]
    ),
    _pipe@1 = gleam@json:to_string(_pipe),
    gleam_stdlib:identity(_pipe@1).

-file("src/aws/lambda.gleam", 601).
-spec apply_headers(
    gleam@http@request:request(bitstring()),
    list({binary(), binary()})
) -> gleam@http@request:request(bitstring()).
apply_headers(Request, Headers) ->
    case Headers of
        [] ->
            Request;

        [{Key, Value} | Rest] ->
            apply_headers(
                gleam@http@request:set_header(Request, Key, Value),
                Rest
            )
    end.

-file("src/aws/lambda.gleam", 583).
-spec build_request(
    gleam@http:method(),
    api(),
    binary(),
    bitstring(),
    list({binary(), binary()})
) -> {ok, gleam@http@request:request(bitstring())} | {error, runtime_error()}.
build_request(Method, Api, Path, Body, Headers) ->
    Url = <<<<<<<<"http://"/utf8, (erlang:element(3, Api))/binary>>/binary,
                "/"/utf8>>/binary,
            "2018-06-01"/utf8>>/binary,
        Path/binary>>,
    gleam@result:'try'(
        begin
            _pipe = gleam@http@request:to(Url),
            gleam@result:replace_error(
                _pipe,
                {invalid_endpoint, erlang:element(3, Api)}
            )
        end,
        fun(Base) -> _pipe@1 = Base,
            _pipe@2 = gleam@http@request:set_method(_pipe@1, Method),
            _pipe@3 = gleam@http@request:set_body(_pipe@2, Body),
            _pipe@4 = apply_headers(_pipe@3, Headers),
            {ok, _pipe@4} end
    ).

-file("src/aws/lambda.gleam", 449).
?DOC(
    " `POST /runtime/invocation/{request_id}/error` with the JSON error body and\n"
    " the `Lambda-Runtime-Function-Error-Type` header.\n"
).
-spec send_error(api(), binary(), invocation_error()) -> {ok, nil} |
    {error, runtime_error()}.
send_error(Api, Request_id, Error) ->
    gleam@result:'try'(
        build_request(
            post,
            Api,
            <<<<"/runtime/invocation/"/utf8, Request_id/binary>>/binary,
                "/error"/utf8>>,
            error_body(Error),
            error_headers(Error)
        ),
        fun(Request) -> expect_accepted(Api, <<"error"/utf8>>, Request) end
    ).

-file("src/aws/lambda.gleam", 430).
?DOC(" `POST /runtime/invocation/{request_id}/response` with the result bytes.\n").
-spec send_response(api(), binary(), bitstring()) -> {ok, nil} |
    {error, runtime_error()}.
send_response(Api, Request_id, Body) ->
    gleam@result:'try'(
        build_request(
            post,
            Api,
            <<<<"/runtime/invocation/"/utf8, Request_id/binary>>/binary,
                "/response"/utf8>>,
            Body,
            []
        ),
        fun(Request) -> expect_accepted(Api, <<"response"/utf8>>, Request) end
    ).

-file("src/aws/lambda.gleam", 394).
-spec crash_to_error(handler_crash()) -> invocation_error().
crash_to_error(Crash) ->
    {invocation_error,
        <<"Unhandled"/utf8>>,
        <<<<(erlang:element(2, Crash))/binary, ": "/utf8>>/binary,
            (erlang:element(3, Crash))/binary>>,
        erlang:element(4, Crash)}.

-file("src/aws/lambda.gleam", 383).
?DOC(
    " Invoke the handler with exception trapping. A raised exception becomes an\n"
    " `\"Unhandled\"` `InvocationError` carrying the class, message, and stack.\n"
).
-spec run_handler(
    fun((bitstring(), context()) -> {ok, bitstring()} |
        {error, invocation_error()}),
    bitstring(),
    context()
) -> {ok, bitstring()} | {error, invocation_error()}.
run_handler(Handler, Payload, Context) ->
    case aws_ffi:rescue_call(fun() -> Handler(Payload, Context) end) of
        {ok, Result} ->
            Result;

        {error, Crash} ->
            {error, crash_to_error(Crash)}
    end.

-file("src/aws/lambda.gleam", 511).
-spec optional_header(gleam@http@response:response(bitstring()), binary()) -> gleam@option:option(binary()).
optional_header(Response, Name) ->
    gleam@option:from_result(gleam@http@response:get_header(Response, Name)).

-file("src/aws/lambda.gleam", 518).
-spec header_or_empty(gleam@http@response:response(bitstring()), binary()) -> binary().
header_or_empty(Response, Name) ->
    _pipe = gleam@http@response:get_header(Response, Name),
    gleam@result:unwrap(_pipe, <<""/utf8>>).

-file("src/aws/lambda.gleam", 522).
-spec header_int(gleam@http@response:response(bitstring()), binary()) -> integer().
header_int(Response, Name) ->
    _pipe = gleam@http@response:get_header(Response, Name),
    _pipe@1 = gleam@result:'try'(_pipe, fun gleam_stdlib:parse_int/1),
    gleam@result:unwrap(_pipe@1, 0).

-file("src/aws/lambda.gleam", 482).
-spec parse_invocation(gleam@http@response:response(bitstring())) -> {ok,
        invocation()} |
    {error, runtime_error()}.
parse_invocation(Response) ->
    case gleam@http@response:get_header(
        Response,
        <<"lambda-runtime-aws-request-id"/utf8>>
    ) of
        {error, _} ->
            {error, missing_request_id};

        {ok, Request_id} ->
            Context = {context,
                Request_id,
                header_int(Response, <<"lambda-runtime-deadline-ms"/utf8>>),
                header_or_empty(
                    Response,
                    <<"lambda-runtime-invoked-function-arn"/utf8>>
                ),
                optional_header(Response, <<"lambda-runtime-trace-id"/utf8>>),
                optional_header(
                    Response,
                    <<"lambda-runtime-client-context"/utf8>>
                ),
                optional_header(
                    Response,
                    <<"lambda-runtime-cognito-identity"/utf8>>
                )},
            {ok, {invocation, Context, erlang:element(4, Response)}}
    end.

-file("src/aws/lambda.gleam", 418).
?DOC(
    " Poll `GET /runtime/invocation/next` for the next event. Blocks until\n"
    " Lambda has an invocation to deliver.\n"
).
-spec next(api()) -> {ok, invocation()} | {error, runtime_error()}.
next(Api) ->
    gleam@result:'try'(
        build_request(get, Api, <<"/runtime/invocation/next"/utf8>>, <<>>, []),
        fun(Request) ->
            gleam@result:'try'(
                send(Api, Request),
                fun(Response) -> case erlang:element(2, Response) of
                        200 ->
                            parse_invocation(Response);

                        Status ->
                            {error,
                                {unexpected_status, <<"next"/utf8>>, Status}}
                    end end
            )
        end
    ).

-file("src/aws/lambda.gleam", 347).
?DOC(
    " One turn of the loop: poll `/next`, propagate the trace id, run the\n"
    " handler (trapping exceptions), then post the response or the error.\n"
    " `Ok(Nil)` means the invocation was fully handled — including the case\n"
    " where the handler failed but the `/error` post succeeded. `Error` is\n"
    " reserved for Runtime API failures.\n"
).
-spec process_invocation(
    api(),
    fun((binary()) -> nil),
    fun((bitstring(), context()) -> {ok, bitstring()} |
        {error, invocation_error()})
) -> {ok, nil} | {error, runtime_error()}.
process_invocation(Api, Set_trace_id, Handler) ->
    gleam@result:'try'(
        next(Api),
        fun(Invocation) ->
            Context = erlang:element(2, Invocation),
            aws@internal@log:debug(
                fun() ->
                    <<<<<<<<"aws lambda: invocation "/utf8,
                                    (erlang:element(2, Context))/binary>>/binary,
                                " ("/utf8>>/binary,
                            (erlang:integer_to_binary(
                                erlang:byte_size(erlang:element(3, Invocation))
                            ))/binary>>/binary,
                        " bytes)"/utf8>>
                end
            ),
            case erlang:element(5, Context) of
                {some, Trace_id} ->
                    Set_trace_id(Trace_id);

                none ->
                    nil
            end,
            case run_handler(Handler, erlang:element(3, Invocation), Context) of
                {ok, Body} ->
                    send_response(Api, erlang:element(2, Context), Body);

                {error, Error} ->
                    aws@internal@log:debug(
                        fun() ->
                            <<<<<<<<<<"aws lambda: invocation "/utf8,
                                                (erlang:element(2, Context))/binary>>/binary,
                                            " handler error "/utf8>>/binary,
                                        (erlang:element(2, Error))/binary>>/binary,
                                    ": "/utf8>>/binary,
                                (erlang:element(3, Error))/binary>>
                        end
                    ),
                    send_error(Api, erlang:element(2, Context), Error)
            end
        end
    ).

-file("src/aws/lambda.gleam", 326).
?DOC(
    " Process invocations forever. Returns the first `RuntimeError` that stops\n"
    " the loop (Runtime API unreachable, protocol violation, ...). A handler\n"
    " that errors or raises does *not* stop the loop — that failure is reported\n"
    " to `/error` and the loop continues.\n"
    "\n"
    " `set_trace_id` is called with the X-Ray trace id before each handler\n"
    " invocation; [`start`](#start) wires it to set `_X_AMZ_TRACE_ID`.\n"
).
-spec serve(
    api(),
    fun((binary()) -> nil),
    fun((bitstring(), context()) -> {ok, bitstring()} |
        {error, invocation_error()})
) -> runtime_error().
serve(Api, Set_trace_id, Handler) ->
    case process_invocation(Api, Set_trace_id, Handler) of
        {ok, nil} ->
            serve(Api, Set_trace_id, Handler);

        {error, Error} ->
            aws@internal@log:error(
                <<"aws lambda runtime: fatal — "/utf8,
                    (describe_runtime_error(Error))/binary>>
            ),
            Error
    end.

-file("src/aws/lambda.gleam", 309).
?DOC(
    " Build an [`Api`](#Api) from `AWS_LAMBDA_RUNTIME_API`. The sender is tuned\n"
    " for the long-poll on `/next`: Lambda may freeze the process between\n"
    " events and hold the connection open up to the 15-minute function ceiling.\n"
).
-spec api_from_env() -> {ok, api()} | {error, runtime_error()}.
api_from_env() ->
    case aws_ffi:get_env(<<"AWS_LAMBDA_RUNTIME_API"/utf8>>) of
        {ok, Endpoint} ->
            {ok, {api, aws@internal@http_send:with_timeout(900), Endpoint}};

        {error, _} ->
            {error, not_running_in_lambda}
    end.

-file("src/aws/lambda.gleam", 168).
?DOC(
    " Run the Lambda runtime loop with a raw bytes handler. Reads\n"
    " `AWS_LAMBDA_RUNTIME_API`, then polls/handles/responds forever. Returns\n"
    " only on a fatal `RuntimeError`.\n"
).
-spec start(
    fun((bitstring(), context()) -> {ok, bitstring()} |
        {error, invocation_error()})
) -> runtime_error().
start(Handler) ->
    case api_from_env() of
        {ok, Api} ->
            serve(Api, fun set_xray_trace_id/1, Handler);

        {error, Error} ->
            Error
    end.

-file("src/aws/lambda.gleam", 241).
-spec event_arg(list(binary())) -> {ok, binary()} | {error, nil}.
event_arg(Args) ->
    case Args of
        [<<"--event"/utf8>>, Value | _] ->
            {ok, Value};

        [<<"-e"/utf8>>, Value | _] ->
            {ok, Value};

        [_ | Rest] ->
            event_arg(Rest);

        [] ->
            {error, nil}
    end.

-file("src/aws/lambda.gleam", 227).
?DOC(
    " Resolve the local event payload: an `--event`/`-e` argument, else\n"
    " `$LAMBDA_EVENT`, else `{}`. Inputs are injected so it stays testable;\n"
    " `run` calls it with the real argv and environment.\n"
).
-spec local_event_from(
    list(binary()),
    fun((binary()) -> {ok, binary()} | {error, nil})
) -> binary().
local_event_from(Args, Read_env) ->
    case event_arg(Args) of
        {ok, Json} ->
            Json;

        {error, _} ->
            gleam@result:unwrap(
                Read_env(<<"LAMBDA_EVENT"/utf8>>),
                <<"{}"/utf8>>
            )
    end.

-file("src/aws/lambda.gleam", 237).
-spec local_event() -> binary().
local_event() ->
    local_event_from(aws_ffi:plain_args(), fun aws_ffi:get_env/1).

-file("src/aws/lambda.gleam", 213).
?DOC(
    " A `Context` with local-friendly placeholder values, for `run`'s local\n"
    " path and for tests.\n"
).
-spec context_default() -> context().
context_default() ->
    {context,
        <<"local"/utf8>>,
        0,
        <<"arn:aws:lambda:local:000000000000:function:local"/utf8>>,
        none,
        none,
        none}.

-file("src/aws/lambda.gleam", 204).
?DOC(
    " Invoke the handler once against a raw payload, trapping a panic exactly as\n"
    " the cloud loop does. The testable core of local execution — no I/O and no\n"
    " process exit. Uses [`context_default`](#context_default) for the context.\n"
).
-spec invoke_once(
    fun((bitstring(), context()) -> {ok, bitstring()} |
        {error, invocation_error()}),
    bitstring()
) -> {ok, bitstring()} | {error, invocation_error()}.
invoke_once(Handler, Payload) ->
    run_handler(Handler, Payload, context_default()).

-file("src/aws/lambda.gleam", 184).
?DOC(
    " Run the handler from the same `main`, locally or in the cloud. Under\n"
    " Lambda (`AWS_LAMBDA_RUNTIME_API` set) this is [`start`](#start) — the\n"
    " poll-forever loop. Run any other way (e.g. `gleam run`) it invokes the\n"
    " handler exactly once, prints the response to stdout, and exits the VM —\n"
    " so you can smoke-test before deploying. The local event is read from\n"
    " `--event <json>` (or `-e`), then `$LAMBDA_EVENT`, then `{}`.\n"
    "\n"
    " Because it can exit the VM, `run` is meant to be your `main`; for the\n"
    " cloud-only loop with no local/exit behaviour, call [`start`](#start).\n"
).
-spec run(
    fun((bitstring(), context()) -> {ok, bitstring()} |
        {error, invocation_error()})
) -> runtime_error().
run(Handler) ->
    case api_from_env() of
        {ok, Api} ->
            serve(Api, fun set_xray_trace_id/1, Handler);

        {error, _} ->
            case invoke_once(Handler, gleam_stdlib:identity(local_event())) of
                {ok, Body} ->
                    gleam_stdlib:println(
                        gleam@result:unwrap(
                            gleam@bit_array:to_string(Body),
                            <<""/utf8>>
                        )
                    ),
                    erlang:halt(0);

                {error, Failure} ->
                    gleam_stdlib:println_error(
                        <<<<(erlang:element(2, Failure))/binary, ": "/utf8>>/binary,
                            (erlang:element(3, Failure))/binary>>
                    ),
                    erlang:halt(1)
            end
    end.

-file("src/aws/lambda.gleam", 295).
?DOC(" Build an [`InvocationError`](#InvocationError) with an empty stack trace.\n").
-spec invocation_error(binary(), binary()) -> invocation_error().
invocation_error(Error_type, Message) ->
    {invocation_error, Error_type, Message, []}.

-file("src/aws/lambda.gleam", 545).
-spec decode_payload(bitstring(), gleam@dynamic@decode:decoder(NUZ)) -> {ok,
        NUZ} |
    {error, invocation_error()}.
decode_payload(Payload, Decoder) ->
    gleam@result:'try'(
        begin
            _pipe = gleam@bit_array:to_string(Payload),
            gleam@result:replace_error(
                _pipe,
                invocation_error(
                    <<"Runtime.InvalidEvent"/utf8>>,
                    <<"event payload was not valid UTF-8"/utf8>>
                )
            )
        end,
        fun(Text) -> _pipe@1 = gleam@json:parse(Text, Decoder),
            gleam@result:replace_error(
                _pipe@1,
                invocation_error(
                    <<"Runtime.InvalidEvent"/utf8>>,
                    <<"event payload did not match the expected schema"/utf8>>
                )
            ) end
    ).

-file("src/aws/lambda.gleam", 279).
?DOC(
    " Adapt a typed JSON handler into a raw [`Handler`](#Handler). Useful when\n"
    " you want to drive the loop yourself via [`serve`](#serve) but still want\n"
    " the decode/encode plumbing.\n"
).
-spec json_handler(
    gleam@dynamic@decode:decoder(NTY),
    fun((NTY, context()) -> {ok, NUA} | {error, binary()}),
    fun((NUA) -> gleam@json:json())
) -> fun((bitstring(), context()) -> {ok, bitstring()} |
    {error, invocation_error()}).
json_handler(Decoder, Handler, Encode) ->
    fun(Payload, Context) ->
        gleam@result:'try'(
            decode_payload(Payload, Decoder),
            fun(Event) -> case Handler(Event, Context) of
                    {ok, Response} ->
                        {ok,
                            gleam_stdlib:identity(
                                gleam@json:to_string(Encode(Response))
                            )};

                    {error, Message} ->
                        {error,
                            invocation_error(<<"Handler.Error"/utf8>>, Message)}
                end end
        )
    end.

-file("src/aws/lambda.gleam", 268).
?DOC(
    " Run the loop with a typed JSON handler. The event payload is decoded with\n"
    " `decoder`; the handler's `Ok` value is encoded with `encode` and posted\n"
    " as the response; the handler's `Error` string is reported to Lambda. A\n"
    " payload that fails to decode is reported as a `Runtime.InvalidEvent`\n"
    " error without invoking the handler.\n"
    "\n"
    " ```gleam\n"
    " import aws/lambda\n"
    " import aws/lambda/event\n"
    " import aws/lambda/response\n"
    "\n"
    " pub fn main() {\n"
    "   lambda.start_json(\n"
    "     event.api_gateway_v2_decoder(),\n"
    "     fn(req, _ctx) { Ok(response.proxy_response(200, \"hi \" <> req.raw_path)) },\n"
    "     response.proxy_to_json,\n"
    "   )\n"
    " }\n"
    " ```\n"
).
-spec start_json(
    gleam@dynamic@decode:decoder(NTT),
    fun((NTT, context()) -> {ok, NTV} | {error, binary()}),
    fun((NTV) -> gleam@json:json())
) -> runtime_error().
start_json(Decoder, Handler, Encode) ->
    start(json_handler(Decoder, Handler, Encode)).

-file("src/aws/lambda.gleam", 466).
?DOC(
    " `POST /runtime/init/error` to report a fatal initialization failure\n"
    " before the first invocation is polled.\n"
).
-spec send_init_error(api(), invocation_error()) -> {ok, nil} |
    {error, runtime_error()}.
send_init_error(Api, Error) ->
    gleam@result:'try'(
        build_request(
            post,
            Api,
            <<"/runtime/init/error"/utf8>>,
            error_body(Error),
            error_headers(Error)
        ),
        fun(Request) -> expect_accepted(Api, <<"init/error"/utf8>>, Request) end
    ).

-file("src/aws/lambda.gleam", 619).
?DOC(
    " Deprecated alias for [`aws/env.get_env`](../env.html#get_env). Env access\n"
    " is not Lambda-specific, so it now lives in `aws/env`.\n"
).
-spec get_env(binary()) -> {ok, binary()} | {error, nil}.
get_env(Name) ->
    aws_ffi:get_env(Name).