Skip to main content

src/aws@internal@client@runtime.erl

-module(aws@internal@client@runtime).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/client/runtime.gleam").
-export([default_endpoint/2, default_config/3, with_sigv4a_region_set/2, with_sigv4a_path_normalization/2, with_credentials_provider/2, with_endpoint_param/3, with_endpoint_url/2, with_http_send/2, with_streaming_http_send/2, with_http2/1, with_retry_strategy/2, with_max_attempts/2, with_endpoint_rule_set/2, extract_error_type/2, invoke_with_endpoint_params_and_host_prefix/5, invoke_with_endpoint_params/4, invoke/3, invoke_streaming_with_endpoint_params/3, invoke_streaming/2, error_type_matches/2, translate_service_error/4, check_error_type_matches/3]).
-export_type([client_config/0, sigv4a_signer/0, client_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(
    " Shared runtime for awsJson1_0 + awsJson1_1 generated clients.\n"
    "\n"
    " Generated per-service modules carry the per-operation `build_*` /\n"
    " `parse_*` codec pair plus the service-level metadata (endpoint\n"
    " prefix, signing name, region). They call into `invoke` here for\n"
    " everything else: credential resolution, endpoint URL construction,\n"
    " SigV4 signing, HTTP dispatch, response parsing.\n"
    "\n"
    " This keeps the generated code small: one `invoke` call per\n"
    " operation rather than ~30 lines of glue per op duplicated 57×\n"
    " across DynamoDB.\n"
).

-type client_config() :: {client_config,
        aws@credentials:provider(),
        binary(),
        binary(),
        binary(),
        binary(),
        fun((gleam@http@request:request(bitstring())) -> {ok,
                gleam@http@response:response(bitstring())} |
            {error, aws@internal@http_send:http_error()}),
        fun((gleam@http@request:request(bitstring())) -> {ok,
                gleam@http@response:response(aws@streaming:streaming_body())} |
            {error, aws@internal@http_send:http_error()}),
        fun(() -> binary()),
        aws@retry:strategy(),
        gleam@option:option(aws@endpoints:rule_set()),
        gleam@dict:dict(binary(), aws@endpoints:value()),
        gleam@option:option(sigv4a_signer())}.

-type sigv4a_signer() :: {sigv4a_signer, list(binary()), boolean()}.

-type client_error() :: {credentials_error, aws@credentials:provider_error()} |
    {transport_error, aws@internal@http_send:http_error()} |
    {decode_error, binary()} |
    {service_error, integer(), binary(), bitstring()}.

-file("src/aws/internal/client/runtime.gleam", 157).
-spec default_endpoint(binary(), binary()) -> binary().
default_endpoint(Endpoint_prefix, Region) ->
    <<<<<<<<"https://"/utf8, Endpoint_prefix/binary>>/binary, "."/utf8>>/binary,
            Region/binary>>/binary,
        ".amazonaws.com"/utf8>>.

-file("src/aws/internal/client/runtime.gleam", 96).
?DOC(
    " Sensible default config given a region. Credentials default to\n"
    " the standard chain (env → web-identity → SSO → profile → process →\n"
    " ECS → IMDS); callers swap in a custom provider via\n"
    " `with_credentials_provider`, matching the convention every other\n"
    " AWS SDK follows.\n"
).
-spec default_config(binary(), binary(), binary()) -> client_config().
default_config(Region, Endpoint_prefix, Signing_name) ->
    {client_config,
        aws@credentials:default_chain(
            fun aws@internal@http_send:default_send/1,
            <<"default"/utf8>>
        ),
        Region,
        Endpoint_prefix,
        Signing_name,
        default_endpoint(Endpoint_prefix, Region),
        fun aws@internal@http_send:default_send/1,
        fun aws@internal@http_streaming:default_send/1,
        fun aws_ffi:aws_timestamp/0,
        aws@retry:standard(),
        none,
        maps:new(),
        none}.

-file("src/aws/internal/client/runtime.gleam", 128).
?DOC(
    " Opt the Client into SigV4a (asymmetric ECDSA P-256) signing for\n"
    " every request. `region_set` becomes the `X-Amz-Region-Set` header\n"
    " — single-region callers pass `[\"us-east-1\"]`, multi-region callers\n"
    " pass the full list. Required for S3 Multi-Region Access Points.\n"
    "\n"
    " `normalize_path` defaults to `True`; S3 callers should follow up\n"
    " with `with_sigv4a_path_normalization(client, False)` so keys\n"
    " containing `.` / `..` survive the canonical-request step.\n"
).
-spec with_sigv4a_region_set(client_config(), list(binary())) -> client_config().
with_sigv4a_region_set(Config, Region_set) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        {some, {sigv4a_signer, Region_set, true}}}.

-file("src/aws/internal/client/runtime.gleam", 143).
?DOC(
    " Override the SigV4a `normalize_path` knob. No-op when the client\n"
    " hasn't opted into SigV4a yet (`with_sigv4a_region_set` not\n"
    " called). Pass `False` for S3 — its object-key paths can carry\n"
    " `.` / `..` that the RFC 3986 dot-segment removal would otherwise\n"
    " strip.\n"
).
-spec with_sigv4a_path_normalization(client_config(), boolean()) -> client_config().
with_sigv4a_path_normalization(Config, Normalize) ->
    case erlang:element(13, Config) of
        {some, S} ->
            {client_config,
                erlang:element(2, Config),
                erlang:element(3, Config),
                erlang:element(4, Config),
                erlang:element(5, Config),
                erlang:element(6, Config),
                erlang:element(7, Config),
                erlang:element(8, Config),
                erlang:element(9, Config),
                erlang:element(10, Config),
                erlang:element(11, Config),
                erlang:element(12, Config),
                {some, {sigv4a_signer, erlang:element(2, S), Normalize}}};

        none ->
            Config
    end.

-file("src/aws/internal/client/runtime.gleam", 163).
?DOC(
    " Override the credentials provider — use for non-default profiles,\n"
    " in-process static creds, or any custom resolution chain.\n"
).
-spec with_credentials_provider(client_config(), aws@credentials:provider()) -> client_config().
with_credentials_provider(Config, Provider) ->
    {client_config,
        Provider,
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 257).
?DOC(
    " Set a single client-level endpoint-rule-set parameter (e.g.\n"
    " `\"UseFIPS\"` -> `BoolVal(True)`). Operation-specific params (S3\n"
    " `Bucket`, `Key`) are threaded per-call via\n"
    " `invoke_with_endpoint_params`.\n"
).
-spec with_endpoint_param(client_config(), binary(), aws@endpoints:value()) -> client_config().
with_endpoint_param(Config, Name, Value) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        gleam@dict:insert(erlang:element(12, Config), Name, Value),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 184).
?DOC(
    " Override the request endpoint URL. Used for LocalStack, FIPS\n"
    " endpoints, custom DNS, and pre-signed-URL replay flows.\n"
    "\n"
    " When a Smithy endpoint rule set is attached (the codegen wires\n"
    " one on every generated service that declares one), the override\n"
    " is threaded into the rule set as the standard `Endpoint`\n"
    " parameter rather than replacing `endpoint_url` outright. AWS\n"
    " rule sets honour this parameter via an early-branch rule like\n"
    " `case isSet(Endpoint) -> endpoint { url: \"{Endpoint}\" }`, so the\n"
    " override wins consistently across all services that declare an\n"
    " `Endpoint` rule-set parameter.\n"
    "\n"
    " The static `endpoint_url` is also updated so services without a\n"
    " rule set continue to honour the override via the fallback path.\n"
).
-spec with_endpoint_url(client_config(), binary()) -> client_config().
with_endpoint_url(Config, Url) ->
    _pipe = {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        Url,
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config)},
    with_endpoint_param(_pipe, <<"Endpoint"/utf8>>, {string_val, Url}).

-file("src/aws/internal/client/runtime.gleam", 189).
-spec with_http_send(
    client_config(),
    fun((gleam@http@request:request(bitstring())) -> {ok,
            gleam@http@response:response(bitstring())} |
        {error, aws@internal@http_send:http_error()})
) -> client_config().
with_http_send(Config, Send) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        Send,
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 197).
?DOC(
    " Override the streaming HTTP sender. Use for tests (stub the\n"
    " transport), for forcing the buffered-then-streamed path via\n"
    " `http_send.lift_to_streaming(custom_buffered)`, or to inject a\n"
    " future custom transport (proxy, gRPC tunnel, etc.).\n"
).
-spec with_streaming_http_send(
    client_config(),
    fun((gleam@http@request:request(bitstring())) -> {ok,
            gleam@http@response:response(aws@streaming:streaming_body())} |
        {error, aws@internal@http_send:http_error()})
) -> client_config().
with_streaming_http_send(Config, Send) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        Send,
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 213).
?DOC(
    " Switch the streaming sender to the HTTP/2 variant. httpc adds\n"
    " `{http_version, \"HTTP/2\"}` to its option list; servers that don't\n"
    " speak HTTP/2 negotiate down to HTTP/1.1 via ALPN, so calls keep\n"
    " working even when the peer doesn't support it. Buffered requests\n"
    " (`http_send`) are unaffected — HTTP/2 is for high-throughput\n"
    " streaming endpoints (S3 multipart, Bedrock streaming, Transcribe).\n"
    "\n"
    " Pair with `with_streaming_http_send` for finer control (e.g. a\n"
    " stubbed sender in tests that needs HTTP/2 wiring elsewhere).\n"
).
-spec with_http2(client_config()) -> client_config().
with_http2(Config) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        fun aws@internal@http_streaming:default_send_http2/1,
        erlang:element(9, Config),
        erlang:element(10, Config),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 220).
?DOC(
    " Override the retry strategy used to wrap `http_send`. Pass\n"
    " `retry.standard()` for the AWS-standard 3-attempt backoff (the\n"
    " default), or `retry.adaptive(bucket)` to add the token-bucket gate.\n"
).
-spec with_retry_strategy(client_config(), aws@retry:strategy()) -> client_config().
with_retry_strategy(Config, Strategy) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        Strategy,
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 234).
?DOC(
    " Tune just the retry attempt budget without replacing the whole\n"
    " strategy. Equivalent to\n"
    " `with_retry_strategy(config, retry.with_max_attempts(config.retry_strategy, n))`,\n"
    " but reads as a single knob — the common case for production\n"
    " tuning. Pass `1` to disable retries entirely (single attempt\n"
    " per request), `5` for long-running batch workloads that tolerate\n"
    " extra wait, etc.\n"
).
-spec with_max_attempts(client_config(), integer()) -> client_config().
with_max_attempts(Config, N) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        aws@retry:with_max_attempts(erlang:element(10, Config), N),
        erlang:element(11, Config),
        erlang:element(12, Config),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 246).
?DOC(
    " Attach a Smithy endpoint rule set. When set, the runtime walks the rule\n"
    " set per request to compute the endpoint URL — the value passed in via\n"
    " `with_endpoint_url` (or `default_endpoint`) is then ignored except as a\n"
    " fallback when the rule set is cleared. Use this from generated service\n"
    " constructors that embed their service's rule set.\n"
).
-spec with_endpoint_rule_set(client_config(), aws@endpoints:rule_set()) -> client_config().
with_endpoint_rule_set(Config, Rule_set) ->
    {client_config,
        erlang:element(2, Config),
        erlang:element(3, Config),
        erlang:element(4, Config),
        erlang:element(5, Config),
        erlang:element(6, Config),
        erlang:element(7, Config),
        erlang:element(8, Config),
        erlang:element(9, Config),
        erlang:element(10, Config),
        {some, Rule_set},
        erlang:element(12, Config),
        erlang:element(13, Config)}.

-file("src/aws/internal/client/runtime.gleam", 902).
-spec body_preview(bitstring()) -> binary().
body_preview(Body) ->
    Excerpt = case erlang:byte_size(Body) > 512 of
        true ->
            gleam_stdlib:bit_array_slice(Body, 0, 512);

        false ->
            {ok, Body}
    end,
    case Excerpt of
        {ok, Bytes} ->
            case gleam@bit_array:to_string(Bytes) of
                {ok, Text} ->
                    Text;

                {error, _} ->
                    <<"<non-utf8 body>"/utf8>>
            end;

        {error, _} ->
            <<"<body>"/utf8>>
    end.

-file("src/aws/internal/client/runtime.gleam", 775).
?DOC(
    " Pull the wire-error-type local name out of a response. Looks at the\n"
    " `X-Amzn-Errortype` header first (restJson1, awsJson*); falls back to\n"
    " the body for `__type` / `code` / `<Code>` (covers JSON and XML\n"
    " error shapes). The returned string is the *local* shape name —\n"
    " namespace prefix, URI suffix, and Smithy `[Charlie,foo,bar]` suffix\n"
    " are all stripped. Exposed for codegen-emitted error-shape protocol\n"
    " test dispatchers; the in-process retry path uses it via the\n"
    " `ServiceError` discriminator.\n"
).
-spec extract_error_type(gleam@dict:dict(binary(), binary()), bitstring()) -> binary().
extract_error_type(Headers, Body) ->
    aws@internal@error_code:from_headers_and_body(Headers, Body).

-file("src/aws/internal/client/runtime.gleam", 712).
-spec headers_to_dict(list({binary(), binary()})) -> gleam@dict:dict(binary(), binary()).
headers_to_dict(Headers) ->
    gleam@list:fold(
        Headers,
        maps:new(),
        fun(Acc, P) ->
            gleam@dict:insert(
                Acc,
                string:lowercase(erlang:element(1, P)),
                erlang:element(2, P)
            )
        end
    ).

-file("src/aws/internal/client/runtime.gleam", 889).
-spec describe_http_error(aws@internal@http_send:http_error()) -> binary().
describe_http_error(Err) ->
    case Err of
        {connect_failed, R} ->
            <<"connect failed: "/utf8, R/binary>>;

        timeout ->
            <<"timeout"/utf8>>;

        {invalid_body, R@1} ->
            <<"invalid body: "/utf8, R@1/binary>>;

        {other, R@2} ->
            R@2
    end.

-file("src/aws/internal/client/runtime.gleam", 863).
?DOC(
    " Case-insensitive header lookup over the built request headers (whose key\n"
    " casing is decided by the per-protocol codegen). Only reached on the\n"
    " debug path, so the fold cost is paid only when debug is on.\n"
).
-spec header_ci(gleam@dict:dict(binary(), binary()), binary()) -> {ok, binary()} |
    {error, nil}.
header_ci(Headers, Lower_name) ->
    gleam@dict:fold(Headers, {error, nil}, fun(Acc, Key, Value) -> case Acc of
                {ok, _} ->
                    Acc;

                {error, _} ->
                    case string:lowercase(Key) =:= Lower_name of
                        true ->
                            {ok, Value};

                        false ->
                            {error, nil}
                    end
            end end).

-file("src/aws/internal/client/runtime.gleam", 847).
?DOC(
    " One-line request summary for the `debug` request log: service, method,\n"
    " resolved host + URI, and — for the JSON-RPC protocols — the `X-Amz-Target`\n"
    " operation. Built lazily (only when debug is on).\n"
).
-spec request_summary(
    client_config(),
    {binary(), binary(), gleam@dict:dict(binary(), binary()), bitstring()},
    gleam@http@request:request(bitstring())
) -> binary().
request_summary(Config, Built, Req) ->
    {Method, Uri, Headers, _} = Built,
    Operation = case header_ci(Headers, <<"x-amz-target"/utf8>>) of
        {ok, Target} ->
            <<" "/utf8, Target/binary>>;

        {error, _} ->
            <<""/utf8>>
    end,
    <<<<<<<<<<<<(erlang:element(5, Config))/binary, " "/utf8>>/binary,
                        Method/binary>>/binary,
                    " "/utf8>>/binary,
                (erlang:element(6, Req))/binary>>/binary,
            Uri/binary>>/binary,
        Operation/binary>>.

-file("src/aws/internal/client/runtime.gleam", 879).
-spec describe_client_error(client_error()) -> binary().
describe_client_error(Err) ->
    case Err of
        {credentials_error, _} ->
            <<"credentials error"/utf8>>;

        {transport_error, E} ->
            <<"transport: "/utf8, (describe_http_error(E))/binary>>;

        {decode_error, R} ->
            <<"decode: "/utf8, R/binary>>;

        {service_error, S, T, _} ->
            <<<<<<"service "/utf8, (erlang:integer_to_binary(S))/binary>>/binary,
                    " "/utf8>>/binary,
                T/binary>>
    end.

-file("src/aws/internal/client/runtime.gleam", 831).
?DOC(
    " Run a side-effecting debug log on the `Error` branch, passing the error\n"
    " through unchanged. Used to narrate request-preparation failures\n"
    " (credential resolution, endpoint rule-set resolution) at `debug`.\n"
).
-spec tap_client_error({ok, LLO} | {error, client_error()}, client_config()) -> {ok,
        LLO} |
    {error, client_error()}.
tap_client_error(Result, Config) ->
    _pipe = Result,
    gleam@result:map_error(
        _pipe,
        fun(Err) ->
            aws@internal@log:debug(
                fun() ->
                    <<<<<<"aws ✗ "/utf8, (erlang:element(5, Config))/binary>>/binary,
                            " "/utf8>>/binary,
                        (describe_client_error(Err))/binary>>
                end
            ),
            Err
        end
    ).

-file("src/aws/internal/client/runtime.gleam", 697).
-spec parse_method(binary()) -> gleam@http:method().
parse_method(Method) ->
    case string:uppercase(Method) of
        <<"GET"/utf8>> ->
            get;

        <<"POST"/utf8>> ->
            post;

        <<"PUT"/utf8>> ->
            put;

        <<"DELETE"/utf8>> ->
            delete;

        <<"HEAD"/utf8>> ->
            head;

        <<"PATCH"/utf8>> ->
            patch;

        <<"OPTIONS"/utf8>> ->
            options;

        <<"TRACE"/utf8>> ->
            trace;

        <<"CONNECT"/utf8>> ->
            connect;

        _ ->
            post
    end.

-file("src/aws/internal/client/runtime.gleam", 568).
-spec sign_sigv4(
    aws@internal@http_request:http_request(),
    aws@credentials:credentials(),
    client_config()
) -> aws@internal@http_request:http_request().
sign_sigv4(Unsigned, Creds, Config) ->
    Opts = {signing_options,
        (erlang:element(9, Config))(),
        erlang:element(3, Config),
        erlang:element(5, Config),
        true,
        true,
        false},
    Signing_creds = {signing_credentials,
        erlang:element(2, Creds),
        erlang:element(3, Creds),
        erlang:element(4, Creds)},
    aws@internal@sigv4:sign(Unsigned, Signing_creds, Opts).

-file("src/aws/internal/client/runtime.gleam", 591).
-spec sign_sigv4a(
    aws@internal@http_request:http_request(),
    aws@credentials:credentials(),
    client_config(),
    sigv4a_signer()
) -> aws@internal@http_request:http_request().
sign_sigv4a(Unsigned, Creds, Config, Signer) ->
    Private_key = aws@internal@sigv4a:derive_signing_key(
        erlang:element(2, Creds),
        erlang:element(3, Creds)
    ),
    Sigv4a_creds = {sigv4a_credentials,
        erlang:element(2, Creds),
        Private_key,
        erlang:element(4, Creds)},
    Opts = {sigv4a_options,
        (erlang:element(9, Config))(),
        erlang:element(2, Signer),
        erlang:element(5, Config),
        true,
        erlang:element(3, Signer),
        false},
    aws@internal@sigv4a:sign_with_credentials(Unsigned, Sigv4a_creds, Opts).

-file("src/aws/internal/client/runtime.gleam", 663).
-spec host_from_endpoint(binary()) -> binary().
host_from_endpoint(Url) ->
    After = case gleam@string:split_once(Url, <<"://"/utf8>>) of
        {ok, {_, Rest}} ->
            Rest;

        {error, _} ->
            Url
    end,
    case gleam@string:split_once(After, <<"/"/utf8>>) of
        {ok, {Host, _}} ->
            Host;

        {error, _} ->
            After
    end.

-file("src/aws/internal/client/runtime.gleam", 685).
?DOC(
    " Prepend `prefix` to the authority portion of `url`. Scheme + path +\n"
    " query are preserved. `prefix` is already-substituted (e.g. `\"foo.\"`\n"
    " — no `{Label}` placeholders left). Mirrors the Rust SDK's\n"
    " `apply_endpoint_to_request` (`format!(\"{scheme}://{prefix}{authority}{path_and_query}\")`).\n"
    "\n"
    " Examples:\n"
    "   `inject_host_prefix(\"https://s3.us-east-1.amazonaws.com/\", \"foo.\")`\n"
    "   → `\"https://foo.s3.us-east-1.amazonaws.com/\"`\n"
).
-spec inject_host_prefix(binary(), binary()) -> binary().
inject_host_prefix(Url, Prefix) ->
    {Scheme, Rest} = case gleam@string:split_once(Url, <<"://"/utf8>>) of
        {ok, {S, R}} ->
            {<<S/binary, "://"/utf8>>, R};

        {error, _} ->
            {<<""/utf8>>, Url}
    end,
    {Authority, Path_and_query} = case gleam@string:split_once(
        Rest,
        <<"/"/utf8>>
    ) of
        {ok, {A, P}} ->
            {A, <<"/"/utf8, P/binary>>};

        {error, _} ->
            {Rest, <<""/utf8>>}
    end,
    <<<<<<Scheme/binary, Prefix/binary>>/binary, Authority/binary>>/binary,
        Path_and_query/binary>>.

-file("src/aws/internal/client/runtime.gleam", 651).
-spec describe_endpoint_error(aws@endpoints:resolve_error()) -> binary().
describe_endpoint_error(Err) ->
    case Err of
        {rule_error, M} ->
            <<"endpoint rule error: "/utf8, M/binary>>;

        no_match ->
            <<"endpoint rule set: no match"/utf8>>;

        {invalid_rule_set, R} ->
            <<"invalid endpoint rule set: "/utf8, R/binary>>;

        {unsupported, R@1} ->
            <<"endpoint unsupported: "/utf8, R@1/binary>>;

        {missing_parameter, N} ->
            <<"endpoint parameter missing: "/utf8, N/binary>>;

        {required_parameter_missing, N@1} ->
            <<"endpoint required parameter missing: "/utf8, N@1/binary>>
    end.

-file("src/aws/internal/client/runtime.gleam", 647).
-spec merge_params(
    gleam@dict:dict(binary(), aws@endpoints:value()),
    gleam@dict:dict(binary(), aws@endpoints:value())
) -> gleam@dict:dict(binary(), aws@endpoints:value()).
merge_params(Base, Overlay) ->
    gleam@dict:fold(
        Overlay,
        Base,
        fun(Acc, K, V) -> gleam@dict:insert(Acc, K, V) end
    ).

-file("src/aws/internal/client/runtime.gleam", 626).
?DOC(
    " Compute the request URL using the rule set if attached, otherwise fall\n"
    " back to the static `endpoint_url`. Returns a runtime error if the rule\n"
    " set can't be resolved — bubbles up as `DecodeError` for now so existing\n"
    " callers don't need a new variant.\n"
).
-spec resolve_endpoint_url(
    client_config(),
    gleam@dict:dict(binary(), aws@endpoints:value())
) -> {ok, binary()} | {error, client_error()}.
resolve_endpoint_url(Config, Op_params) ->
    case erlang:element(11, Config) of
        none ->
            {ok, erlang:element(6, Config)};

        {some, Rs} ->
            Params = gleam@dict:insert(
                merge_params(erlang:element(12, Config), Op_params),
                <<"Region"/utf8>>,
                {string_val, erlang:element(3, Config)}
            ),
            case aws@endpoints:resolve(Rs, Params) of
                {ok, Endpoint} ->
                    {ok, erlang:element(2, Endpoint)};

                {error, Err} ->
                    {error, {decode_error, describe_endpoint_error(Err)}}
            end
    end.

-file("src/aws/internal/client/runtime.gleam", 492).
-spec prepare_signed_request(
    client_config(),
    gleam@dict:dict(binary(), aws@endpoints:value()),
    gleam@option:option(binary()),
    {binary(), binary(), gleam@dict:dict(binary(), binary()), bitstring()}
) -> {ok, gleam@http@request:request(bitstring())} | {error, client_error()}.
prepare_signed_request(Config, Op_params, Host_prefix, Built) ->
    {Method, Uri, Headers, Body} = Built,
    gleam@result:'try'(
        begin
            _pipe = aws@credentials:fetch(erlang:element(2, Config)),
            gleam@result:map_error(
                _pipe,
                fun(Field@0) -> {credentials_error, Field@0} end
            )
        end,
        fun(Creds) ->
            gleam@result:'try'(
                resolve_endpoint_url(Config, Op_params),
                fun(Endpoint_url) ->
                    {Endpoint_url@1, Host} = case Host_prefix of
                        none ->
                            {Endpoint_url, host_from_endpoint(Endpoint_url)};

                        {some, Prefix} ->
                            Prefixed_url = inject_host_prefix(
                                Endpoint_url,
                                Prefix
                            ),
                            {Prefixed_url, host_from_endpoint(Prefixed_url)}
                    end,
                    Header_pairs = begin
                        _pipe@1 = [{<<"host"/utf8>>, Host} |
                            maps:to_list(Headers)],
                        gleam@list:map(
                            _pipe@1,
                            fun(P) ->
                                {header,
                                    erlang:element(1, P),
                                    erlang:element(2, P)}
                            end
                        )
                    end,
                    {Path_only, Query_str} = case gleam@string:split_once(
                        Uri,
                        <<"?"/utf8>>
                    ) of
                        {ok, {P@1, Q}} ->
                            {P@1, Q};

                        {error, _} ->
                            {Uri, <<""/utf8>>}
                    end,
                    Unsigned = {http_request,
                        Method,
                        Path_only,
                        Query_str,
                        Header_pairs,
                        Body},
                    Signed = case erlang:element(13, Config) of
                        {some, Signer} ->
                            sign_sigv4a(Unsigned, Creds, Config, Signer);

                        none ->
                            sign_sigv4(Unsigned, Creds, Config)
                    end,
                    Full_url = <<Endpoint_url@1/binary, Uri/binary>>,
                    gleam@result:'try'(
                        begin
                            _pipe@2 = gleam@http@request:to(Full_url),
                            gleam@result:replace_error(
                                _pipe@2,
                                {decode_error,
                                    <<"invalid endpoint url: "/utf8,
                                        Full_url/binary>>}
                            )
                        end,
                        fun(Base) ->
                            Http_req = begin
                                _pipe@3 = Base,
                                _pipe@4 = gleam@http@request:set_method(
                                    _pipe@3,
                                    parse_method(Method)
                                ),
                                gleam@http@request:set_body(_pipe@4, Body)
                            end,
                            Http_req@1 = gleam@list:fold(
                                erlang:element(5, Signed),
                                Http_req,
                                fun(R, H) ->
                                    gleam@http@request:set_header(
                                        R,
                                        erlang:element(2, H),
                                        erlang:element(3, H)
                                    )
                                end
                            ),
                            {ok, Http_req@1}
                        end
                    )
                end
            )
        end
    ).

-file("src/aws/internal/client/runtime.gleam", 307).
?DOC(
    " `invoke_with_endpoint_params` with an already-substituted host\n"
    " prefix. Codegen passes `Some(prefix)` for ops carrying\n"
    " `@smithy.api#endpoint.hostPrefix` — the template (e.g.\n"
    " `\"{RequestRoute}.\"`) is expanded against the input's `@hostLabel`\n"
    " members in the generated wrapper, mirroring the Rust SDK's\n"
    " `read_before_execution` interceptor.\n"
).
-spec invoke_with_endpoint_params_and_host_prefix(
    client_config(),
    gleam@dict:dict(binary(), aws@endpoints:value()),
    gleam@option:option(binary()),
    {binary(), binary(), gleam@dict:dict(binary(), binary()), bitstring()},
    fun((integer(), gleam@dict:dict(binary(), binary()), bitstring()) -> {ok,
            LKF} |
        {error, binary()})
) -> {ok, LKF} | {error, client_error()}.
invoke_with_endpoint_params_and_host_prefix(
    Config,
    Op_params,
    Host_prefix,
    Built,
    Parse
) ->
    gleam@result:'try'(
        begin
            _pipe = prepare_signed_request(
                Config,
                Op_params,
                Host_prefix,
                Built
            ),
            tap_client_error(_pipe, Config)
        end,
        fun(Http_req) ->
            aws@internal@log:debug(
                fun() ->
                    <<"aws → "/utf8,
                        (request_summary(Config, Built, Http_req))/binary>>
                end
            ),
            Send = aws@retry:with_retry(
                erlang:element(7, Config),
                erlang:element(10, Config)
            ),
            gleam@result:'try'(
                begin
                    _pipe@1 = Send(Http_req),
                    gleam@result:map_error(
                        _pipe@1,
                        fun(Err) ->
                            aws@internal@log:debug(
                                fun() ->
                                    <<<<<<"aws ✗ "/utf8,
                                                (erlang:element(5, Config))/binary>>/binary,
                                            " transport: "/utf8>>/binary,
                                        (describe_http_error(Err))/binary>>
                                end
                            ),
                            {transport_error, Err}
                        end
                    )
                end,
                fun(Resp) ->
                    Resp_headers = headers_to_dict(erlang:element(3, Resp)),
                    case (erlang:element(2, Resp) >= 200) andalso (erlang:element(
                        2,
                        Resp
                    )
                    < 300) of
                        true ->
                            aws@internal@log:debug(
                                fun() ->
                                    <<<<<<"aws ← "/utf8,
                                                (erlang:integer_to_binary(
                                                    erlang:element(2, Resp)
                                                ))/binary>>/binary,
                                            " "/utf8>>/binary,
                                        (erlang:element(5, Config))/binary>>
                                end
                            ),
                            _pipe@2 = Parse(
                                erlang:element(2, Resp),
                                Resp_headers,
                                erlang:element(4, Resp)
                            ),
                            gleam@result:map_error(
                                _pipe@2,
                                fun(Reason) ->
                                    aws@internal@log:debug(
                                        fun() ->
                                            <<<<<<"aws ✗ "/utf8,
                                                        (erlang:element(
                                                            5,
                                                            Config
                                                        ))/binary>>/binary,
                                                    " decode: "/utf8>>/binary,
                                                Reason/binary>>
                                        end
                                    ),
                                    {decode_error, Reason}
                                end
                            );

                        false ->
                            Error_type = extract_error_type(
                                Resp_headers,
                                erlang:element(4, Resp)
                            ),
                            aws@internal@log:debug(
                                fun() ->
                                    <<<<<<<<<<<<<<"aws ✗ "/utf8,
                                                                (erlang:element(
                                                                    5,
                                                                    Config
                                                                ))/binary>>/binary,
                                                            " "/utf8>>/binary,
                                                        (erlang:integer_to_binary(
                                                            erlang:element(
                                                                2,
                                                                Resp
                                                            )
                                                        ))/binary>>/binary,
                                                    " "/utf8>>/binary,
                                                Error_type/binary>>/binary,
                                            ": "/utf8>>/binary,
                                        (body_preview(erlang:element(4, Resp)))/binary>>
                                end
                            ),
                            {error,
                                {service_error,
                                    erlang:element(2, Resp),
                                    Error_type,
                                    erlang:element(4, Resp)}}
                    end
                end
            )
        end
    ).

-file("src/aws/internal/client/runtime.gleam", 286).
?DOC(
    " Same as `invoke` but with extra rule-set parameters merged in for this\n"
    " operation only — used by generated S3 ops to supply `Bucket`/`Key` etc.\n"
    " without leaking them onto the client config. If the client has no\n"
    " `endpoint_rule_set`, `op_params` is ignored (the static `endpoint_url`\n"
    " is used).\n"
).
-spec invoke_with_endpoint_params(
    client_config(),
    gleam@dict:dict(binary(), aws@endpoints:value()),
    {binary(), binary(), gleam@dict:dict(binary(), binary()), bitstring()},
    fun((integer(), gleam@dict:dict(binary(), binary()), bitstring()) -> {ok,
            LJV} |
        {error, binary()})
) -> {ok, LJV} | {error, client_error()}.
invoke_with_endpoint_params(Config, Op_params, Built, Parse) ->
    invoke_with_endpoint_params_and_host_prefix(
        Config,
        Op_params,
        none,
        Built,
        Parse
    ).

-file("src/aws/internal/client/runtime.gleam", 273).
?DOC(
    " Run one operation end-to-end. See module docs for the pipeline.\n"
    "\n"
    " Operations that need to thread rule-set parameters known only to the\n"
    " op itself (e.g. S3's `Bucket`) should use `invoke_with_endpoint_params`\n"
    " instead and pass those parameters through `op_params`.\n"
).
-spec invoke(
    client_config(),
    {binary(), binary(), gleam@dict:dict(binary(), binary()), bitstring()},
    fun((integer(), gleam@dict:dict(binary(), binary()), bitstring()) -> {ok,
            LJM} |
        {error, binary()})
) -> {ok, LJM} | {error, client_error()}.
invoke(Config, Built, Parse) ->
    invoke_with_endpoint_params(Config, maps:new(), Built, Parse).

-file("src/aws/internal/client/runtime.gleam", 461).
-spec streaming_error(
    gleam@http@response:response(aws@streaming:streaming_body())
) -> client_error().
streaming_error(Resp) ->
    Resp_headers = headers_to_dict(erlang:element(3, Resp)),
    case aws@streaming:to_bit_array_max(erlang:element(4, Resp), 1048576) of
        {ok, Body} ->
            {service_error,
                erlang:element(2, Resp),
                extract_error_type(Resp_headers, Body),
                Body};

        {error, _} ->
            {decode_error,
                <<<<"streaming error body exceeded "/utf8,
                        (erlang:integer_to_binary(1048576))/binary>>/binary,
                    " bytes — refusing to materialise for typed-error extraction"/utf8>>}
    end.

-file("src/aws/internal/client/runtime.gleam", 404).
?DOC(
    " `invoke_streaming` with per-op endpoint-rule-set parameters (the\n"
    " streaming-side counterpart to `invoke_with_endpoint_params`).\n"
).
-spec invoke_streaming_with_endpoint_params(
    client_config(),
    gleam@dict:dict(binary(), aws@endpoints:value()),
    {binary(), binary(), gleam@dict:dict(binary(), binary()), bitstring()}
) -> {ok, aws@streaming:response()} | {error, client_error()}.
invoke_streaming_with_endpoint_params(Config, Op_params, Built) ->
    gleam@result:'try'(
        begin
            _pipe = prepare_signed_request(Config, Op_params, none, Built),
            tap_client_error(_pipe, Config)
        end,
        fun(Http_req) ->
            aws@internal@log:debug(
                fun() ->
                    <<<<"aws → "/utf8,
                            (request_summary(Config, Built, Http_req))/binary>>/binary,
                        " (streaming)"/utf8>>
                end
            ),
            gleam@result:'try'(
                begin
                    _pipe@1 = (erlang:element(8, Config))(Http_req),
                    gleam@result:map_error(
                        _pipe@1,
                        fun(Err) ->
                            aws@internal@log:debug(
                                fun() ->
                                    <<<<<<"aws ✗ "/utf8,
                                                (erlang:element(5, Config))/binary>>/binary,
                                            " transport: "/utf8>>/binary,
                                        (describe_http_error(Err))/binary>>
                                end
                            ),
                            {transport_error, Err}
                        end
                    )
                end,
                fun(Resp) ->
                    case (erlang:element(2, Resp) >= 200) andalso (erlang:element(
                        2,
                        Resp
                    )
                    < 300) of
                        true ->
                            aws@internal@log:debug(
                                fun() ->
                                    <<<<<<<<"aws ← "/utf8,
                                                    (erlang:integer_to_binary(
                                                        erlang:element(2, Resp)
                                                    ))/binary>>/binary,
                                                " "/utf8>>/binary,
                                            (erlang:element(5, Config))/binary>>/binary,
                                        " (streaming)"/utf8>>
                                end
                            ),
                            {ok,
                                {response,
                                    erlang:element(2, Resp),
                                    erlang:element(3, Resp),
                                    erlang:element(4, Resp)}};

                        false ->
                            Err@1 = streaming_error(Resp),
                            aws@internal@log:debug(
                                fun() ->
                                    <<<<<<"aws ✗ "/utf8,
                                                (erlang:element(5, Config))/binary>>/binary,
                                            " "/utf8>>/binary,
                                        (describe_client_error(Err@1))/binary>>
                                end
                            ),
                            {error, Err@1}
                    end
                end
            )
        end
    ).

-file("src/aws/internal/client/runtime.gleam", 395).
?DOC(
    " Streaming-response variant of `invoke`. Builds + signs the\n"
    " request exactly like `invoke`, but dispatches through\n"
    " `streaming_http_send` (chunked transport) and returns the raw\n"
    " `Response(StreamingBody)` so callers can consume the body\n"
    " incrementally — fold chunk-by-chunk via `streaming.fold_chunks`,\n"
    " decode event-stream frames via `event_stream.fold_events`, or\n"
    " stream-to-disk without buffering.\n"
    "\n"
    " Used by generated codegen for operations whose output carries\n"
    " `@streaming` — `S3.GetObject` for multi-GB downloads,\n"
    " `Transcribe.StartStreamTranscription` and `Kinesis.SubscribeToShard`\n"
    " for event-stream responses, etc.\n"
    "\n"
    " Error responses (non-2xx) are materialised via\n"
    " `streaming.to_bit_array_max(body, 1 MiB)` so `error_type`\n"
    " extraction works on the JSON/XML error body the same way as\n"
    " `invoke`. A response body that exceeds the 1 MiB cap on the\n"
    " error path surfaces as `DecodeError` since we can't safely\n"
    " extract a typed error from an oversized error body.\n"
    "\n"
    " Retry is intentionally NOT wrapped around `streaming_http_send`\n"
    " at this layer — replaying a streaming request after a transient\n"
    " failure is op-specific (idempotent vs. mutating). Callers that\n"
    " want retry on a streaming op should layer it themselves or\n"
    " drop down to the buffered `invoke`.\n"
).
-spec invoke_streaming(
    client_config(),
    {binary(), binary(), gleam@dict:dict(binary(), binary()), bitstring()}
) -> {ok, aws@streaming:response()} | {error, client_error()}.
invoke_streaming(Config, Built) ->
    invoke_streaming_with_endpoint_params(Config, maps:new(), Built).

-file("src/aws/internal/client/runtime.gleam", 724).
?DOC(
    " Match an AWS `error_type` wire value against a local Smithy\n"
    " shape name. Used by the generated per-op `translate_<op>_error`\n"
    " dispatchers. `error_type` already passes through\n"
    " `normalise_error_type` (namespace + suffix stripped) at the\n"
    " invoke layer, so a plain equality check suffices; we keep this\n"
    " behind a helper to give the codegen one stable call-site.\n"
).
-spec error_type_matches(binary(), binary()) -> boolean().
error_type_matches(Error_type, Local) ->
    Error_type =:= Local.

-file("src/aws/internal/client/runtime.gleam", 740).
?DOC(
    " Generic translator from the runtime's `ClientError` to a per-op\n"
    " typed-error enum. Each generated `translate_<op>_error` is a\n"
    " one-liner that supplies its operation's decoder table plus\n"
    " constructors for the always-present `*Transport` and `*Unknown`\n"
    " variants. Saves ~15–25 LOC/op vs the previous open-coded nested\n"
    " match.\n"
    "\n"
    " `decoders` is a list of `(wire_error_type_local_name, decoder)`\n"
    " pairs. The first pair whose error_type matches gets to attempt the\n"
    " decode; if its decoder returns `Error(Nil)`, we fall back to\n"
    " `on_unknown` with the textified body so the caller still sees\n"
    " something useful instead of a panic.\n"
).
-spec translate_service_error(
    client_error(),
    list({binary(), fun((binary()) -> {ok, LLE} | {error, nil})}),
    fun((binary()) -> LLE),
    fun((binary(), integer(), binary()) -> LLE)
) -> LLE.
translate_service_error(Err, Decoders, On_transport, On_unknown) ->
    case Err of
        {service_error, S, Et, B} ->
            Text = case gleam@bit_array:to_string(B) of
                {ok, T} ->
                    T;

                {error, _} ->
                    <<""/utf8>>
            end,
            case gleam@list:find(
                Decoders,
                fun(D) -> error_type_matches(Et, erlang:element(1, D)) end
            ) of
                {ok, {_, Decoder}} ->
                    case Decoder(Text) of
                        {ok, V} ->
                            V;

                        {error, _} ->
                            On_unknown(Et, S, Text)
                    end;

                {error, _} ->
                    On_unknown(Et, S, Text)
            end;

        {transport_error, _} ->
            On_transport(<<"transport error"/utf8>>);

        {credentials_error, _} ->
            On_transport(<<"credentials error"/utf8>>);

        {decode_error, R} ->
            On_transport(<<"decode: "/utf8, R/binary>>)
    end.

-file("src/aws/internal/client/runtime.gleam", 795).
?DOC(
    " Discriminator check for protocol-test error-shape dispatchers. Used\n"
    " by the generated `parse_<err>_response` function: if the wire-side\n"
    " discriminator (header or body) resolves to `expected_local`, the\n"
    " response was routed to the right error shape and the dispatcher\n"
    " reports `Ok(Nil)`. The runner's response-side assertion is binary\n"
    " — `Ok` vs `Error` — so returning `Nil` is enough.\n"
    "\n"
    " The runner hands fixture headers through with their literal-case\n"
    " keys (`X-Amzn-Errortype`), but `extract_error_type` expects the\n"
    " lowercased form that the real-request path produces via\n"
    " `headers_to_dict`. We lowercase here so the helper matches HTTP's\n"
    " case-insensitive header semantics regardless of which call-site\n"
    " invokes it.\n"
).
-spec check_error_type_matches(
    gleam@dict:dict(binary(), binary()),
    bitstring(),
    binary()
) -> {ok, nil} | {error, binary()}.
check_error_type_matches(Headers, Body, Expected_local) ->
    Lower = gleam@dict:fold(
        Headers,
        maps:new(),
        fun(Acc, K, V) -> gleam@dict:insert(Acc, string:lowercase(K), V) end
    ),
    Extracted = extract_error_type(Lower, Body),
    case Extracted =:= Expected_local of
        true ->
            {ok, nil};

        false ->
            {error,
                <<<<<<"error type mismatch: expected "/utf8,
                            Expected_local/binary>>/binary,
                        ", got "/utf8>>/binary,
                    Extracted/binary>>}
    end.