Skip to main content

src/aws@internal@http_send.erl

-module(aws@internal@http_send).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/http_send.gleam").
-export([lift_to_streaming/1, default_send/1, with_timeout/1, imds_send/1]).
-export_type([http_error/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(
    " HTTP send abstraction used by all HTTP-based credential providers (and\n"
    " later by the request pipeline itself).\n"
    "\n"
    " Every provider that talks to AWS endpoints takes a `Send` value so tests\n"
    " can drive it with a stub. Production code calls `default_send`, which\n"
    " dispatches via `gleam_httpc` (Erlang's `httpc`).\n"
    "\n"
    " Errors are normalised to a single `HttpError` sum type so the providers\n"
    " can pattern-match on category without depending on `httpc`'s shape\n"
    " directly.\n"
).

-type http_error() :: {connect_failed, binary()} |
    timeout |
    {invalid_body, binary()} |
    {other, binary()}.

-file("src/aws/internal/http_send.gleam", 49).
?DOC(
    " Lift a buffered `Send` into a `StreamingSend` by wrapping its\n"
    " response body as a `Buffered` `StreamingBody`. Use this when a\n"
    " test wants to stub the streaming transport with a buffered fake,\n"
    " or when a caller has only a buffered sender available and wants\n"
    " it to satisfy a `StreamingSend` slot. Production code should\n"
    " take `http_streaming.default_send` for genuine chunked transfer.\n"
).
-spec lift_to_streaming(
    fun((gleam@http@request:request(bitstring())) -> {ok,
            gleam@http@response:response(bitstring())} |
        {error, http_error()})
) -> fun((gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(aws@streaming:streaming_body())} |
    {error, http_error()}).
lift_to_streaming(Send) ->
    fun(Req) -> case Send(Req) of
            {ok, Resp} ->
                {ok,
                    {response,
                        erlang:element(2, Resp),
                        erlang:element(3, Resp),
                        aws@streaming:from_bit_array(erlang:element(4, Resp))}};

            {error, E} ->
                {error, E}
        end end.

-file("src/aws/internal/http_send.gleam", 101).
-spec default_config() -> gleam@httpc:configuration().
default_config() ->
    _pipe = gleam@httpc:configure(),
    gleam@httpc:timeout(_pipe, 30 * 1000).

-file("src/aws/internal/http_send.gleam", 105).
-spec do_send(
    gleam@httpc:configuration(),
    gleam@http@request:request(bitstring())
) -> {ok, gleam@http@response:response(bitstring())} | {error, http_error()}.
do_send(Config, Req) ->
    case gleam@httpc:dispatch_bits(Config, Req) of
        {ok, Response} ->
            {ok, Response};

        {error, {failed_to_connect, _, _}} ->
            {error, {connect_failed, <<"could not connect to host"/utf8>>}};

        {error, response_timeout} ->
            {error, timeout};

        {error, invalid_utf8_response} ->
            {error,
                {invalid_body, <<"response body was not valid UTF-8"/utf8>>}}
    end.

-file("src/aws/internal/http_send.gleam", 77).
?DOC(" Production sender — 30 second total timeout, TLS verification on.\n").
-spec default_send(gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(bitstring())} |
    {error, http_error()}.
default_send(Req) ->
    do_send(default_config(), Req).

-file("src/aws/internal/http_send.gleam", 88).
?DOC(
    " Build a `Send` with a custom total timeout. Use this for endpoints that\n"
    " need either fast-fail behaviour (IMDS) or extra patience (large object\n"
    " downloads). TLS verification and redirect-following match\n"
    " `default_send`'s defaults; if you need to tweak those, drop down to\n"
    " `gleam_httpc` directly.\n"
).
-spec with_timeout(integer()) -> fun((gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(bitstring())} |
    {error, http_error()}).
with_timeout(Seconds) ->
    Config = begin
        _pipe = gleam@httpc:configure(),
        gleam@httpc:timeout(_pipe, Seconds * 1000)
    end,
    fun(Req) -> do_send(Config, Req) end.

-file("src/aws/internal/http_send.gleam", 95).
?DOC(
    " IMDS-tuned sender. Equivalent to `with_timeout(seconds: imds_timeout_seconds)`,\n"
    " exported as a constant so call sites read meaningfully.\n"
).
-spec imds_send(gleam@http@request:request(bitstring())) -> {ok,
        gleam@http@response:response(bitstring())} |
    {error, http_error()}.
imds_send(Req) ->
    (with_timeout(2))(Req).