Skip to main content

src/aws@internal@error_code.erl

-module(aws@internal@error_code).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/error_code.gleam").
-export([normalise/1, from_header_value_and_body/2, from_headers_and_body/2]).

-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 modeled-error-code extraction. AWS error responses carry the\n"
    " modeled error *code* (the Smithy shape's local name) in one of three\n"
    " places, depending on protocol: the `X-Amzn-Errortype` header\n"
    " (awsJson*, restJson1), a JSON body `__type` / `code` field, or a\n"
    " restXml `<Code>` element. This module pulls that local shape name\n"
    " out — namespace prefix, URI suffix, and Smithy `[Charlie,foo]`\n"
    " disambiguation suffix all stripped.\n"
    "\n"
    " Both the retry middleware (`aws/retry`), which inspects the raw\n"
    " `Response` *inside* the retry loop before any typed parsing, and the\n"
    " runtime (`aws/internal/client/runtime`), which builds the typed\n"
    " `ServiceError` *after* the loop, need the same extraction — so it\n"
    " lives here once rather than being duplicated across the two layers.\n"
).

-file("src/aws/internal/error_code.gleam", 57).
?DOC(
    " Strip an error-type wire value down to its local Smithy shape name:\n"
    " drop any `Type:uri` suffix, any `,` disambiguation list, and any\n"
    " `namespace#` prefix. Exposed so callers that already hold a raw\n"
    " header value (not a full response) can normalise it consistently.\n"
).
-spec normalise(binary()) -> binary().
normalise(Raw) ->
    S = case gleam@string:split_once(Raw, <<":"/utf8>>) of
        {ok, {Prefix, _}} ->
            Prefix;

        {error, _} ->
            Raw
    end,
    S@1 = case gleam@string:split_once(S, <<","/utf8>>) of
        {ok, {Prefix@1, _}} ->
            Prefix@1;

        {error, _} ->
            S
    end,
    case gleam@string:split_once(S@1, <<"#"/utf8>>) of
        {ok, {_, Local}} ->
            Local;

        {error, _} ->
            S@1
    end.

-file("src/aws/internal/error_code.gleam", 90).
?DOC(
    " Pull the error code out of a restXml error body. Two shapes appear\n"
    " in the wild — S3-style `<Error><Code>NoSuchBucket</Code>...</Error>`\n"
    " and SQS/SNS-style `<ErrorResponse><Error><Code>X</Code>...</Error>`.\n"
    " In both, the first `<Code>` element holds the error type, so a\n"
    " single text search covers both without dragging in the full XML\n"
    " decoder. The trim + empty-check rejects `<Code/>` / `<Code>   </Code>`\n"
    " so the fallback to \"Unknown\" still fires for malformed bodies.\n"
).
-spec from_xml(binary()) -> {ok, binary()} | {error, nil}.
from_xml(Body) ->
    gleam@result:'try'(
        aws@internal@text_scan:xml_tag_text(Body, <<"Code"/utf8>>),
        fun(Raw) -> case gleam@string:trim(Raw) of
                <<""/utf8>> ->
                    {error, nil};

                Non_empty ->
                    {ok, Non_empty}
            end end
    ).

-file("src/aws/internal/error_code.gleam", 72).
-spec from_body(binary()) -> binary().
from_body(Body) ->
    Found = begin
        _pipe = aws@internal@text_scan:json_string_after_key(
            Body,
            <<"__type"/utf8>>
        ),
        _pipe@1 = gleam@result:lazy_or(
            _pipe,
            fun() ->
                aws@internal@text_scan:json_string_after_key(
                    Body,
                    <<"code"/utf8>>
                )
            end
        ),
        gleam@result:lazy_or(_pipe@1, fun() -> from_xml(Body) end)
    end,
    case Found of
        {ok, V} ->
            normalise(V);

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

-file("src/aws/internal/error_code.gleam", 39).
?DOC(
    " Like `from_headers_and_body` but taking the `x-amzn-errortype`\n"
    " header value as a pre-resolved `Result`. The retry layer holds a\n"
    " `Response` (not a header dict) and reads the header via\n"
    " `response.get_header`, so this entry point lets it reuse the exact\n"
    " same extraction without rebuilding a dict.\n"
).
-spec from_header_value_and_body({ok, binary()} | {error, nil}, bitstring()) -> binary().
from_header_value_and_body(Error_type_header, Body) ->
    case Error_type_header of
        {ok, V} ->
            normalise(V);

        {error, _} ->
            case gleam@bit_array:to_string(Body) of
                {error, _} ->
                    <<"Unknown"/utf8>>;

                {ok, Text} ->
                    from_body(Text)
            end
    end.

-file("src/aws/internal/error_code.gleam", 27).
?DOC(
    " Pull the wire-error-type local name out of an error response's\n"
    " already-lowercased headers + body. Prefers the `x-amzn-errortype`\n"
    " header (restJson1, awsJson*), then falls back to the body's\n"
    " `__type` / `code` (JSON) or `<Code>` (restXml). Returns the *local*\n"
    " shape name with namespace / URI / disambiguation suffixes stripped,\n"
    " or `\"Unknown\"` when nothing matches.\n"
).
-spec from_headers_and_body(gleam@dict:dict(binary(), binary()), bitstring()) -> binary().
from_headers_and_body(Headers, Body) ->
    from_header_value_and_body(
        gleam_stdlib:map_get(Headers, <<"x-amzn-errortype"/utf8>>),
        Body
    ).