Skip to main content

src/aws@internal@codec@rest.erl

-module(aws@internal@codec@rest).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/codec/rest.gleam").
-export([substitute_label/4, add_query/3, bool_to_query/1, int_to_query/1, float_to_query/1, maybe_set_list_header/3, append_content_encoding/2, idempotency_token/0, quote_list_string_entry/1, build_path/2, maybe_set_header/3, set_default_header/3, add_prefix_headers/3, add_query_params/2, add_query_params_list/2, timestamp_to_header/1, enum_wire_value/1, string_header/2, int_header/2, bool_header/2, float_header/2, enum_header/3, http_date_header/2, iso8601_header/2, epoch_seconds_header/2, with_content_md5_header/2, glacier_tree_hash/1, with_glacier_tree_hash_headers/2, checksum_header/2, with_checksum_header/3, checksum_algorithm_from_wire/1, with_checksum_header_for_wire/3]).
-export_type([checksum_algorithm/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 helpers for the rest-protocol emitters\n"
    " (restJson1, restXml). Holds the URI / query / header glue that\n"
    " generated `build_*_request` functions call into for each\n"
    " `@httpLabel`, `@httpQuery`, `@httpHeader` member.\n"
).

-type checksum_algorithm() :: checksum_sha256 |
    checksum_sha1 |
    checksum_crc32 |
    checksum_crc32_c.

-file("src/aws/internal/codec/rest.gleam", 41).
?DOC(" Encode each path segment but keep the `/` separators intact.\n").
-spec encode_path_preserve_slash(binary()) -> binary().
encode_path_preserve_slash(Path) ->
    _pipe = gleam@string:split(Path, <<"/"/utf8>>),
    _pipe@1 = gleam@list:map(_pipe, fun aws@internal@uri:encode_segment/1),
    gleam@string:join(_pipe@1, <<"/"/utf8>>).

-file("src/aws/internal/codec/rest.gleam", 23).
?DOC(
    " Substitute a single `@httpLabel` member into the URI template.\n"
    " Templates use `{Name}` or `{Name+}` (the `+` marks a greedy label\n"
    " that may contain `/`). Values are percent-encoded; greedy labels\n"
    " preserve `/` in the value.\n"
).
-spec substitute_label(binary(), binary(), binary(), boolean()) -> binary().
substitute_label(Template, Name, Value, Greedy) ->
    Placeholder = <<<<"{"/utf8, Name/binary>>/binary, "}"/utf8>>,
    Greedy_placeholder = <<<<"{"/utf8, Name/binary>>/binary, "+}"/utf8>>,
    Encoded = case Greedy of
        true ->
            encode_path_preserve_slash(Value);

        false ->
            aws@internal@uri:encode_segment(Value)
    end,
    _pipe = Template,
    _pipe@1 = gleam@string:replace(_pipe, Greedy_placeholder, Encoded),
    gleam@string:replace(_pipe@1, Placeholder, Encoded).

-file("src/aws/internal/codec/rest.gleam", 49).
?DOC(
    " Append a query parameter pair. Returns the resulting query string\n"
    " (without the leading `?`); call sites prepend it themselves.\n"
).
-spec add_query(binary(), binary(), binary()) -> binary().
add_query(Existing, Name, Value) ->
    Pair = <<<<(aws@internal@uri:encode_component(Name))/binary, "="/utf8>>/binary,
        (aws@internal@uri:encode_component(Value))/binary>>,
    case Existing of
        <<""/utf8>> ->
            Pair;

        _ ->
            <<<<Existing/binary, "&"/utf8>>/binary, Pair/binary>>
    end.

-file("src/aws/internal/codec/rest.gleam", 58).
?DOC(" Bool → query value: \"true\" / \"false\".\n").
-spec bool_to_query(boolean()) -> binary().
bool_to_query(B) ->
    case B of
        true ->
            <<"true"/utf8>>;

        false ->
            <<"false"/utf8>>
    end.

-file("src/aws/internal/codec/rest.gleam", 66).
?DOC(" Int → query / header value as decimal.\n").
-spec int_to_query(integer()) -> binary().
int_to_query(N) ->
    erlang:integer_to_binary(N).

-file("src/aws/internal/codec/rest.gleam", 74).
?DOC(
    " Float → query / header / URI-label value. Uses Erlang's `short`\n"
    " formatter so `1.1` round-trips as the literal `\"1.1\"` — the AWS\n"
    " SimpleScalarProperties protocol-test corpus rejects scientific\n"
    " notation in these positions.\n"
).
-spec float_to_query(float()) -> binary().
float_to_query(F) ->
    aws_ffi:float_short(F).

-file("src/aws/internal/codec/rest.gleam", 84).
?DOC(
    " `@httpHeader` on a list member emits the values comma-joined, per\n"
    " HTTP/1.1 header-folding rules. `Some([\"a\", \"b\"])` becomes\n"
    " `Name: a, b`. Empty lists drop the header entirely.\n"
).
-spec maybe_set_list_header(
    gleam@dict:dict(binary(), binary()),
    binary(),
    list(binary())
) -> gleam@dict:dict(binary(), binary()).
maybe_set_list_header(Headers, Name, Values) ->
    gleam@dict:insert(Headers, Name, gleam@string:join(Values, <<", "/utf8>>)).

-file("src/aws/internal/codec/rest.gleam", 100).
?DOC(
    " Append a `@requestCompression` encoding to the `Content-Encoding`\n"
    " header. Existing encodings (e.g. caller-set `Content-Encoding:\n"
    " custom`) are preserved with the new value appended after a comma:\n"
    " `custom` + `gzip` ⇒ `custom, gzip`.\n"
).
-spec append_content_encoding(gleam@dict:dict(binary(), binary()), binary()) -> gleam@dict:dict(binary(), binary()).
append_content_encoding(Headers, Encoding) ->
    case gleam_stdlib:map_get(Headers, <<"Content-Encoding"/utf8>>) of
        {ok, Existing} ->
            gleam@dict:insert(
                Headers,
                <<"Content-Encoding"/utf8>>,
                <<<<Existing/binary, ", "/utf8>>/binary, Encoding/binary>>
            );

        {error, _} ->
            gleam@dict:insert(Headers, <<"Content-Encoding"/utf8>>, Encoding)
    end.

-file("src/aws/internal/codec/rest.gleam", 115).
?DOC(
    " Generated `@idempotencyToken` value, used when a request member\n"
    " with that trait is left as `Option.None`. Backed by the runtime\n"
    " FFI so tests can pin a deterministic UUID via `application:set_env`.\n"
).
-spec idempotency_token() -> binary().
idempotency_token() ->
    aws_ffi:idempotency_token().

-file("src/aws/internal/codec/rest.gleam", 120).
?DOC(
    " RFC 7230 list-header quoting for string elements. Smithy applies\n"
    " this only to string-typed entries — timestamp values use the raw\n"
    " `Mon, 16 Dec ... GMT` form even though it contains a comma.\n"
).
-spec quote_list_string_entry(binary()) -> binary().
quote_list_string_entry(V) ->
    case gleam_stdlib:contains_string(V, <<","/utf8>>) orelse gleam_stdlib:contains_string(
        V,
        <<"\""/utf8>>
    ) of
        true ->
            <<<<"\""/utf8,
                    (gleam@string:replace(V, <<"\""/utf8>>, <<"\\\""/utf8>>))/binary>>/binary,
                "\""/utf8>>;

        false ->
            V
    end.

-file("src/aws/internal/codec/rest.gleam", 134).
?DOC(
    " Build the full path: substitute labels, then append query (with `?`)\n"
    " if non-empty.\n"
    " Merge a path that may already carry a static query string (from the\n"
    " `@http` URI template, e.g. `/Foo?bar=baz`) with the dynamically\n"
    " built query string from `@httpQuery` members. Either or both can be\n"
    " empty. The static-query side wins on key order; dynamic params are\n"
    " appended.\n"
).
-spec build_path(binary(), binary()) -> binary().
build_path(Uri_path, Query) ->
    {Path_only, Static_query} = case gleam@string:split_once(
        Uri_path,
        <<"?"/utf8>>
    ) of
        {ok, {P, Q}} ->
            {P, Q};

        {error, _} ->
            {Uri_path, <<""/utf8>>}
    end,
    Combined = case {Static_query, Query} of
        {<<""/utf8>>, <<""/utf8>>} ->
            <<""/utf8>>;

        {<<""/utf8>>, Q@1} ->
            Q@1;

        {Sq, <<""/utf8>>} ->
            Sq;

        {Sq@1, Q@2} ->
            <<<<Sq@1/binary, "&"/utf8>>/binary, Q@2/binary>>
    end,
    case Combined of
        <<""/utf8>> ->
            Path_only;

        _ ->
            <<<<Path_only/binary, "?"/utf8>>/binary, Combined/binary>>
    end.

-file("src/aws/internal/codec/rest.gleam", 153).
?DOC(
    " Set a header on the headers dict if the value is non-empty (Smithy\n"
    " `@httpHeader` typically omits the header when the value is None).\n"
).
-spec maybe_set_header(gleam@dict:dict(binary(), binary()), binary(), binary()) -> gleam@dict:dict(binary(), binary()).
maybe_set_header(Headers, Name, Value) ->
    gleam@dict:insert(Headers, Name, Value).

-file("src/aws/internal/codec/rest.gleam", 169).
?DOC(
    " Insert a service-level default header only when the caller hasn't\n"
    " already provided one with the same name. Mirrors `set_default_header`\n"
    " in the Rust SDK and the `contains_key` guard inside Glacier's\n"
    " `add_checksum_treehash` so caller `@httpHeader`-bound values win on\n"
    " collision.\n"
).
-spec set_default_header(
    gleam@dict:dict(binary(), binary()),
    binary(),
    binary()
) -> gleam@dict:dict(binary(), binary()).
set_default_header(Headers, Name, Value) ->
    case gleam@dict:has_key(Headers, Name) of
        true ->
            Headers;

        false ->
            gleam@dict:insert(Headers, Name, Value)
    end.

-file("src/aws/internal/codec/rest.gleam", 182).
?DOC(
    " Iterate `@httpPrefixHeaders` map members: for each entry,\n"
    " emit a header `<prefix><key>: <value>`.\n"
).
-spec add_prefix_headers(
    gleam@dict:dict(binary(), binary()),
    binary(),
    gleam@dict:dict(binary(), binary())
) -> gleam@dict:dict(binary(), binary()).
add_prefix_headers(Headers, Prefix, Entries) ->
    gleam@dict:fold(
        Entries,
        Headers,
        fun(Acc, K, V) ->
            gleam@dict:insert(Acc, <<Prefix/binary, K/binary>>, V)
        end
    ).

-file("src/aws/internal/codec/rest.gleam", 191).
?DOC(" Iterate `@httpQueryParams` map members (Map<String, String>).\n").
-spec add_query_params(binary(), gleam@dict:dict(binary(), binary())) -> binary().
add_query_params(Query, Entries) ->
    gleam@dict:fold(Entries, Query, fun(Acc, K, V) -> add_query(Acc, K, V) end).

-file("src/aws/internal/codec/rest.gleam", 200).
?DOC(
    " Iterate `@httpQueryParams` map members (Map<String, List<String>>).\n"
    " Each list value emits one query param per element.\n"
).
-spec add_query_params_list(binary(), gleam@dict:dict(binary(), list(binary()))) -> binary().
add_query_params_list(Query, Entries) ->
    gleam@dict:fold(
        Entries,
        Query,
        fun(Acc, K, Vs) ->
            gleam@list:fold(Vs, Acc, fun(Q, V) -> add_query(Q, K, V) end)
        end
    ).

-file("src/aws/internal/codec/rest.gleam", 214).
?DOC(
    " Format a timestamp for use in URI labels / query strings / headers.\n"
    " Smithy's default `@timestampFormat` for `date-time` (the\n"
    " restJson1 + restXml default) is RFC 3339, e.g. `2019-12-16T23:48:18Z`.\n"
    " We always emit Z-suffixed UTC; the wire form remains stable even if\n"
    " the input came from a system clock in a different zone.\n"
).
-spec timestamp_to_header(integer()) -> binary().
timestamp_to_header(Epoch_seconds) ->
    aws_ffi:format_iso8601(Epoch_seconds).

-file("src/aws/internal/codec/rest.gleam", 225).
?DOC(
    " Extract the raw wire string from a JSON-encoded enum value. The\n"
    " generated `encode_<enum>_enum(v)` returns a `json.Json` like\n"
    " `json.string(\"VALUE\")`; URI / query / header position wants just\n"
    " `VALUE`. We render to JSON text and strip the surrounding quotes.\n"
).
-spec enum_wire_value(gleam@json:json()) -> binary().
enum_wire_value(J) ->
    S = gleam@json:to_string(J),
    Len = string:length(S),
    case Len > 2 of
        true ->
            gleam@string:slice(S, 1, Len - 2);

        false ->
            S
    end.

-file("src/aws/internal/codec/rest.gleam", 242).
-spec string_header(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(binary()).
string_header(Headers, Name) ->
    _pipe = gleam_stdlib:map_get(Headers, string:lowercase(Name)),
    gleam@option:from_result(_pipe).

-file("src/aws/internal/codec/rest.gleam", 250).
-spec int_header(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(integer()).
int_header(Headers, Name) ->
    gleam@option:then(string_header(Headers, Name), fun(Raw) -> _pipe = Raw,
            _pipe@1 = gleam@string:trim(_pipe),
            _pipe@2 = gleam_stdlib:parse_int(_pipe@1),
            gleam@option:from_result(_pipe@2) end).

-file("src/aws/internal/codec/rest.gleam", 258).
-spec bool_header(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(boolean()).
bool_header(Headers, Name) ->
    gleam@option:then(
        string_header(Headers, Name),
        fun(Raw) -> case string:lowercase(gleam@string:trim(Raw)) of
                <<"true"/utf8>> ->
                    {some, true};

                <<"false"/utf8>> ->
                    {some, false};

                _ ->
                    none
            end end
    ).

-file("src/aws/internal/codec/rest.gleam", 547).
-spec parse_float(binary()) -> gleam@option:option(float()).
parse_float(S) ->
    _pipe = gleam_stdlib:parse_float(S),
    _pipe@2 = gleam@result:lazy_or(
        _pipe,
        fun() -> _pipe@1 = gleam_stdlib:parse_int(S),
            gleam@result:map(_pipe@1, fun erlang:float/1) end
    ),
    gleam@option:from_result(_pipe@2).

-file("src/aws/internal/codec/rest.gleam", 273).
?DOC(
    " Float header — used for shapes that bind a `Float` member to a\n"
    " response header. Falls through to `None` if the value can't be\n"
    " parsed; same forgiving contract as `int_header` / `bool_header`.\n"
).
-spec float_header(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(float()).
float_header(Headers, Name) ->
    gleam@option:then(
        string_header(Headers, Name),
        fun(Raw) -> parse_float(gleam@string:trim(Raw)) end
    ).

-file("src/aws/internal/codec/rest.gleam", 288).
?DOC(
    " Enum header — looks up a string header, then runs the supplied\n"
    " `<enum>_from_wire` decoder. Falls through to `None` if the\n"
    " header is missing or the wire value doesn't match a known\n"
    " variant (forgiving contract — unknown variants don't crash\n"
    " the response parse). The codegen passes the generated\n"
    " `<enum>_from_wire` function directly so this stays\n"
    " enum-agnostic.\n"
).
-spec enum_header(
    gleam@dict:dict(binary(), binary()),
    binary(),
    fun((binary()) -> {ok, MWX} | {error, binary()})
) -> gleam@option:option(MWX).
enum_header(Headers, Name, From_wire) ->
    case string_header(Headers, Name) of
        {some, S} ->
            case From_wire(S) of
                {ok, V} ->
                    {some, V};

                {error, _} ->
                    none
            end;

        none ->
            none
    end.

-file("src/aws/internal/codec/rest.gleam", 308).
?DOC(
    " HTTP-date header — RFC 7231 §7.1.1.1 form\n"
    " (`Tue, 29 Apr 2014 18:30:38 GMT`). The default `@timestampFormat`\n"
    " for header bindings per Smithy core — covers `Last-Modified`,\n"
    " `Expires`, `Date`, etc. Forgiving contract: missing header or\n"
    " unparseable string → `None`.\n"
).
-spec http_date_header(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(aws@internal@codec@json_timestamp:timestamp()).
http_date_header(Headers, Name) ->
    case string_header(Headers, Name) of
        {some, Raw} ->
            case aws@internal@codec@json_timestamp:parse_http_date(
                gleam@string:trim(Raw)
            ) of
                {ok, T} ->
                    {some, T};

                {error, _} ->
                    none
            end;

        none ->
            none
    end.

-file("src/aws/internal/codec/rest.gleam", 324).
?DOC(
    " ISO 8601 timestamp header (`@timestampFormat(\"date-time\")`,\n"
    " `2024-01-02T03:04:05Z`).\n"
).
-spec iso8601_header(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(aws@internal@codec@json_timestamp:timestamp()).
iso8601_header(Headers, Name) ->
    case string_header(Headers, Name) of
        {some, Raw} ->
            case aws@internal@codec@json_timestamp:parse_iso8601(
                gleam@string:trim(Raw)
            ) of
                {ok, T} ->
                    {some, T};

                {error, _} ->
                    none
            end;

        none ->
            none
    end.

-file("src/aws/internal/codec/rest.gleam", 341).
?DOC(
    " Epoch-seconds timestamp header\n"
    " (`@timestampFormat(\"epoch-seconds\")`, integer seconds since 1970\n"
    " in the header value).\n"
).
-spec epoch_seconds_header(gleam@dict:dict(binary(), binary()), binary()) -> gleam@option:option(aws@internal@codec@json_timestamp:timestamp()).
epoch_seconds_header(Headers, Name) ->
    case int_header(Headers, Name) of
        {some, N} ->
            {some, {timestamp, N, 0}};

        none ->
            none
    end.

-file("src/aws/internal/codec/rest.gleam", 366).
?DOC(
    " Set the `Content-MD5` header to `base64(md5(body))`. Used by the\n"
    " Smithy `smithy.api#httpChecksumRequired` trait — the codegen\n"
    " emits a call to this helper at the tail of `build_<op>_request`\n"
    " for any operation that carries the trait.\n"
    "\n"
    " Always overwrites a previous `Content-MD5` entry: the SDK is\n"
    " responsible for the canonical value, and a stale caller-supplied\n"
    " one would surface as a 400 from the service. Other headers pass\n"
    " through unchanged.\n"
    "\n"
    " MD5 is not a security primitive here. The wire contract requires\n"
    " it (S3-control + restJson1 protocol tests fix the exact bytes);\n"
    " SigV4 covers the actual auth on the request.\n"
).
-spec with_content_md5_header(gleam@dict:dict(binary(), binary()), bitstring()) -> gleam@dict:dict(binary(), binary()).
with_content_md5_header(Headers, Body) ->
    Digest = gleam_stdlib:base64_encode(aws_ffi:md5(Body), true),
    gleam@dict:insert(Headers, <<"Content-MD5"/utf8>>, Digest).

-file("src/aws/internal/codec/rest.gleam", 446).
-spec pair_hash(list(bitstring())) -> list(bitstring()).
pair_hash(Hashes) ->
    case Hashes of
        [] ->
            [];

        [Single] ->
            [Single];

        [Left, Right | Rest] ->
            [aws_ffi:sha256(gleam@bit_array:append(Left, Right)) |
                pair_hash(Rest)]
    end.

-file("src/aws/internal/codec/rest.gleam", 439).
-spec combine_tree_hashes(list(bitstring())) -> bitstring().
combine_tree_hashes(Hashes) ->
    case Hashes of
        [Single] ->
            Single;

        _ ->
            combine_tree_hashes(pair_hash(Hashes))
    end.

-file("src/aws/internal/codec/rest.gleam", 416).
-spec chunk_hashes(bitstring(), integer(), list(bitstring())) -> list(bitstring()).
chunk_hashes(Body, Chunk_size, Acc) ->
    case erlang:byte_size(Body) of
        0 ->
            lists:reverse(Acc);

        Size ->
            Take = case Size > Chunk_size of
                true ->
                    Chunk_size;

                false ->
                    Size
            end,
            Chunk@1 = case gleam_stdlib:bit_array_slice(Body, 0, Take) of
                {ok, Chunk} -> Chunk;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"aws/internal/codec/rest"/utf8>>,
                                function => <<"chunk_hashes"/utf8>>,
                                line => 431,
                                value => _assert_fail,
                                start => 14691,
                                'end' => 14744,
                                pattern_start => 14702,
                                pattern_end => 14711})
            end,
            Rest_size = Size - Take,
            Rest@1 = case gleam_stdlib:bit_array_slice(Body, Take, Rest_size) of
                {ok, Rest} -> Rest;
                _assert_fail@1 ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"aws/internal/codec/rest"/utf8>>,
                                function => <<"chunk_hashes"/utf8>>,
                                line => 433,
                                value => _assert_fail@1,
                                start => 14785,
                                'end' => 14845,
                                pattern_start => 14796,
                                pattern_end => 14804})
            end,
            chunk_hashes(Rest@1, Chunk_size, [aws_ffi:sha256(Chunk@1) | Acc])
    end.

-file("src/aws/internal/codec/rest.gleam", 406).
?DOC(
    " Compute the Glacier tree hash of `body` as raw bytes (caller\n"
    " hex-encodes for the wire). Empty body degenerates to\n"
    " `SHA-256(\"\")` so the function is total. Matches\n"
    " `glacier_interceptors::compute_hash_tree` in the Rust SDK.\n"
).
-spec glacier_tree_hash(bitstring()) -> bitstring().
glacier_tree_hash(Body) ->
    case erlang:byte_size(Body) of
        0 ->
            aws_ffi:sha256(<<>>);

        _ ->
            Chunks = chunk_hashes(Body, 1048576, []),
            combine_tree_hashes(Chunks)
    end.

-file("src/aws/internal/codec/rest.gleam", 389).
?DOC(
    " Glacier's tree-hash + content-sha256 headers. Both end up as the\n"
    " Computed against the recursive 1 MiB chunk algorithm at\n"
    " https://docs.aws.amazon.com/amazonglacier/latest/dev/checksum-\n"
    " calculations.html: split the body into 1 MiB chunks, SHA-256 each,\n"
    " then pair-hash adjacent digests until one remains. Single-chunk\n"
    " bodies degenerate to plain SHA-256 (tree-hash == content-sha256),\n"
    " which is what every Glacier protocol-test fixture happens to use,\n"
    " but the recursive form is required for the > 1 MiB upload archive\n"
    " path used by real callers.\n"
    "\n"
    " `X-Amz-Sha256-Tree-Hash` carries the tree-hash digest;\n"
    " `X-Amz-Content-Sha256` carries the full-body SHA-256. The Rust SDK\n"
    " uses the same pair of headers in `glacier_interceptors::\n"
    " add_checksum_treehash`. Both are skipped when already present so a\n"
    " caller-supplied value wins.\n"
).
-spec with_glacier_tree_hash_headers(
    gleam@dict:dict(binary(), binary()),
    bitstring()
) -> gleam@dict:dict(binary(), binary()).
with_glacier_tree_hash_headers(Headers, Body) ->
    Content_sha256 = aws_ffi:hex_encode(aws_ffi:sha256(Body)),
    Tree_hash = aws_ffi:hex_encode(glacier_tree_hash(Body)),
    _pipe = Headers,
    _pipe@1 = set_default_header(
        _pipe,
        <<"X-Amz-Sha256-Tree-Hash"/utf8>>,
        Tree_hash
    ),
    set_default_header(_pipe@1, <<"X-Amz-Content-Sha256"/utf8>>, Content_sha256).

-file("src/aws/internal/codec/rest.gleam", 475).
?DOC(
    " `(header_name, base64_digest)` pair for a body checksum. The\n"
    " header name follows the AWS convention `x-amz-checksum-<algo>`\n"
    " (lowercase). The digest is base64 of the raw bytes — same\n"
    " padding rules as `Content-MD5`. Pure function; the\n"
    " `aws.protocols#httpChecksum` middleware in the codegen layer\n"
    " calls this and inserts the result into the request headers.\n"
).
-spec checksum_header(checksum_algorithm(), bitstring()) -> {binary(), binary()}.
checksum_header(Algorithm, Body) ->
    case Algorithm of
        checksum_sha256 ->
            {<<"x-amz-checksum-sha256"/utf8>>,
                gleam_stdlib:base64_encode(aws_ffi:sha256(Body), true)};

        checksum_sha1 ->
            {<<"x-amz-checksum-sha1"/utf8>>,
                gleam_stdlib:base64_encode(aws_ffi:sha1(Body), true)};

        checksum_crc32 ->
            {<<"x-amz-checksum-crc32"/utf8>>,
                gleam_stdlib:base64_encode(
                    aws@internal@crypto:crc32_be_bytes(erlang:crc32(Body)),
                    true
                )};

        checksum_crc32_c ->
            {<<"x-amz-checksum-crc32c"/utf8>>,
                gleam_stdlib:base64_encode(
                    aws@internal@crypto:crc32_be_bytes(aws_ffi:crc32c(Body)),
                    true
                )}
    end.

-file("src/aws/internal/codec/rest.gleam", 503).
?DOC(
    " Add an `x-amz-checksum-<algo>` header to the request. Convenience\n"
    " wrapper around `checksum_header` that lets call sites stay\n"
    " pipeline-style with the existing `Dict(String, String)` header\n"
    " shape — same ergonomics as `with_content_md5_header`.\n"
).
-spec with_checksum_header(
    gleam@dict:dict(binary(), binary()),
    checksum_algorithm(),
    bitstring()
) -> gleam@dict:dict(binary(), binary()).
with_checksum_header(Headers, Algorithm, Body) ->
    {Name, Value} = checksum_header(Algorithm, Body),
    gleam@dict:insert(Headers, Name, Value).

-file("src/aws/internal/codec/rest.gleam", 519).
?DOC(
    " Translate a Smithy `ChecksumAlgorithm` enum's wire value\n"
    " (e.g. `\"SHA256\"`, `\"CRC32C\"`) to the runtime `ChecksumAlgorithm`\n"
    " variant, falling back to `ChecksumSha256` when the wire value\n"
    " doesn't match a supported algorithm. Used by the codegen's\n"
    " algorithm-member dispatch for `aws.protocols#httpChecksum` so\n"
    " generated request builders can read the caller's typed enum\n"
    " choice without needing a per-service jump table.\n"
).
-spec checksum_algorithm_from_wire(binary()) -> checksum_algorithm().
checksum_algorithm_from_wire(Wire) ->
    case Wire of
        <<"SHA256"/utf8>> ->
            checksum_sha256;

        <<"SHA1"/utf8>> ->
            checksum_sha1;

        <<"CRC32"/utf8>> ->
            checksum_crc32;

        <<"CRC32C"/utf8>> ->
            checksum_crc32_c;

        _ ->
            checksum_sha256
    end.

-file("src/aws/internal/codec/rest.gleam", 539).
?DOC(
    " Add the `x-amz-checksum-<algo>` header using a wire-form\n"
    " algorithm name. Equivalent to\n"
    " `with_checksum_header(headers, checksum_algorithm_from_wire(wire), body)`.\n"
    " Exists so the codegen can emit a single call that takes the\n"
    " generated enum's wire-encoder output directly, without\n"
    " needing per-service algorithm-mapping helpers.\n"
).
-spec with_checksum_header_for_wire(
    gleam@dict:dict(binary(), binary()),
    binary(),
    bitstring()
) -> gleam@dict:dict(binary(), binary()).
with_checksum_header_for_wire(Headers, Wire, Body) ->
    with_checksum_header(Headers, checksum_algorithm_from_wire(Wire), Body).