-module(aws@internal@codec@xml_decode).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/codec/xml_decode.gleam").
-export([parse/1, find_child/2, find_children/2, text_content/1, attr/2, string_text/1, bool_text/1, int_text/1, float_text/1, smithy_float_text/1, timestamp_text/1, timestamp_text_precise/1, optional_child/3, required_child/3, optional_list/4, required_list/4, optional_flat_list/3, required_flat_list/3, inner_list/3]).
-export_type([element/0, node_/0, raw_node/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(
" XML decoder for restXml / awsQuery / ec2Query responses.\n"
"\n"
" The Erlang side (`aws_ffi:xml_parse/1`) does the heavy lifting via\n"
" xmerl from the OTP standard library. It returns a `Element` tree\n"
" shaped as nested tuples; on the Gleam side we expose that tree and\n"
" a handful of accessor helpers that the generated decoders call\n"
" (`find_child`, `find_children`, `child_text`, ...).\n"
"\n"
" Whitespace-only text nodes between elements are stripped on the\n"
" Erlang side so the generated code can address members by element\n"
" name without thinking about layout. Repeated child elements (used\n"
" for `@xmlFlattened` lists) are surfaced by `find_children`.\n"
).
-type element() :: {element,
binary(),
list({binary(), binary()}),
list(node_())}.
-type node_() :: {element_node, element()} | {text, binary()}.
-type raw_node() :: any().
-file("src/aws/internal/codec/xml_decode.gleam", 60).
-spec tag_of(raw_node()) -> binary().
tag_of(T) ->
erlang:atom_to_binary(erlang:element(1, T)).
-file("src/aws/internal/codec/xml_decode.gleam", 73).
-spec node_from_tuple(raw_node()) -> node_().
node_from_tuple(T) ->
case tag_of(T) of
<<"element"/utf8>> ->
Name = gleam@function:identity(erlang:element(2, T)),
Attrs_raw = gleam@function:identity(erlang:element(3, T)),
Children_raw = gleam@function:identity(erlang:element(4, T)),
Attrs = gleam@list:map(
Attrs_raw,
fun(P) ->
{gleam@function:identity(erlang:element(1, P)),
gleam@function:identity(erlang:element(2, P))}
end
),
Children = gleam@list:map(Children_raw, fun node_from_tuple/1),
{element_node, {element, Name, Attrs, Children}};
<<"text"/utf8>> ->
{text, gleam@function:identity(erlang:element(2, T))};
_ ->
{text, <<""/utf8>>}
end.
-file("src/aws/internal/codec/xml_decode.gleam", 39).
?DOC(
" Parse an XML document into an `Element`. Returns `Error(\"...\")` on\n"
" malformed input — generated decoders propagate this up as a\n"
" `DecodeError`.\n"
).
-spec parse(binary()) -> {ok, element()} | {error, binary()}.
parse(Body) ->
case aws_ffi:xml_parse(Body) of
{ok, T} ->
case node_from_tuple(T) of
{element_node, E} ->
{ok, E};
_ ->
{error, <<"xml: root is not an element"/utf8>>}
end;
{error, _} ->
{error, <<"xml: parse failed"/utf8>>}
end.
-file("src/aws/internal/codec/xml_decode.gleam", 99).
-spec do_find_child(list(node_()), binary()) -> gleam@option:option(element()).
do_find_child(Nodes, Name) ->
case Nodes of
[] ->
none;
[{element_node, E} | Rest] ->
case erlang:element(2, E) =:= Name of
true ->
{some, E};
false ->
do_find_child(Rest, Name)
end;
[_ | Rest@1] ->
do_find_child(Rest@1, Name)
end.
-file("src/aws/internal/codec/xml_decode.gleam", 95).
?DOC(" Find the first child element with the given local name.\n").
-spec find_child(element(), binary()) -> gleam@option:option(element()).
find_child(Parent, Name) ->
do_find_child(erlang:element(4, Parent), Name).
-file("src/aws/internal/codec/xml_decode.gleam", 115).
?DOC(
" Find all child elements with the given local name. Used for both\n"
" `@xmlFlattened` lists (which appear as repeated siblings of the\n"
" parent) and for normal wrapped lists (after stepping into the\n"
" wrapper element).\n"
).
-spec find_children(element(), binary()) -> list(element()).
find_children(Parent, Name) ->
gleam@list:filter_map(erlang:element(4, Parent), fun(N) -> case N of
{element_node, E} ->
case erlang:element(2, E) =:= Name of
true ->
{ok, E};
false ->
{error, nil}
end;
_ ->
{error, nil}
end end).
-file("src/aws/internal/codec/xml_decode.gleam", 130).
?DOC(
" Concatenate all direct text-node children. Used for primitive\n"
" element values like `<Name>foo</Name>`.\n"
).
-spec text_content(element()) -> binary().
text_content(E) ->
gleam@list:fold(erlang:element(4, E), <<""/utf8>>, fun(Acc, N) -> case N of
{text, V} ->
<<Acc/binary, V/binary>>;
_ ->
Acc
end end).
-file("src/aws/internal/codec/xml_decode.gleam", 140).
?DOC(" Lookup an attribute by name on an element.\n").
-spec attr(element(), binary()) -> gleam@option:option(binary()).
attr(E, Name) ->
case gleam@list:find(
erlang:element(3, E),
fun(P) -> erlang:element(1, P) =:= Name end
) of
{ok, {_, V}} ->
{some, V};
{error, _} ->
none
end.
-file("src/aws/internal/codec/xml_decode.gleam", 149).
-spec string_text(element()) -> {ok, binary()} | {error, binary()}.
string_text(E) ->
{ok, text_content(E)}.
-file("src/aws/internal/codec/xml_decode.gleam", 153).
-spec bool_text(element()) -> {ok, boolean()} | {error, binary()}.
bool_text(E) ->
case gleam@string:trim(text_content(E)) of
<<"true"/utf8>> ->
{ok, true};
<<"false"/utf8>> ->
{ok, false};
Other ->
{error, <<"xml: invalid bool: "/utf8, Other/binary>>}
end.
-file("src/aws/internal/codec/xml_decode.gleam", 161).
-spec int_text(element()) -> {ok, integer()} | {error, binary()}.
int_text(E) ->
T = gleam@string:trim(text_content(E)),
case gleam_stdlib:parse_int(T) of
{ok, N} ->
{ok, N};
{error, _} ->
{error, <<"xml: invalid int: "/utf8, T/binary>>}
end.
-file("src/aws/internal/codec/xml_decode.gleam", 169).
-spec float_text(element()) -> {ok, float()} | {error, binary()}.
float_text(E) ->
T = gleam@string:trim(text_content(E)),
case gleam_stdlib:parse_float(T) of
{ok, F} ->
{ok, F};
{error, _} ->
case gleam_stdlib:parse_int(T) of
{ok, N} ->
{ok, erlang:float(N)};
{error, _} ->
{error, <<"xml: invalid float: "/utf8, T/binary>>}
end
end.
-file("src/aws/internal/codec/xml_decode.gleam", 188).
?DOC(
" Like `float_text` but recognises the Smithy IEEE-754 special-\n"
" value tokens (`NaN` / `Infinity` / `-Infinity`) and surfaces\n"
" them as `json_float.SmithyFloat` variants. Used by generated\n"
" Float decoders so the typed output carries the special value\n"
" rather than failing the entire decode.\n"
).
-spec smithy_float_text(element()) -> {ok,
aws@internal@codec@json_float:smithy_float()} |
{error, binary()}.
smithy_float_text(E) ->
T = gleam@string:trim(text_content(E)),
case T of
<<"NaN"/utf8>> ->
{ok, na_n};
<<"Infinity"/utf8>> ->
{ok, pos_infinity};
<<"-Infinity"/utf8>> ->
{ok, neg_infinity};
_ ->
case float_text(E) of
{ok, F} ->
{ok, {float_value, F}};
{error, R} ->
{error, R}
end
end.
-file("src/aws/internal/codec/xml_decode.gleam", 207).
?DOC(
" Decode a Smithy `@timestamp` element. AWS XML APIs serialise these\n"
" as ISO 8601 (e.g. `2024-01-02T03:04:05.000Z`); our type walker\n"
" surfaces timestamps as `Int` (epoch seconds), so we parse the text\n"
" and convert. Falls back to plain integer parsing for the rare case\n"
" where the wire form is already epoch seconds.\n"
).
-spec timestamp_text(element()) -> {ok, integer()} | {error, binary()}.
timestamp_text(E) ->
T = gleam@string:trim(text_content(E)),
_pipe = aws_ffi:parse_iso8601(T),
_pipe@1 = gleam@result:lazy_or(
_pipe,
fun() -> aws_ffi:parse_http_date(T) end
),
_pipe@2 = gleam@result:lazy_or(
_pipe@1,
fun() -> gleam_stdlib:parse_int(T) end
),
gleam@result:map_error(
_pipe@2,
fun(_) -> <<"xml: invalid timestamp: "/utf8, T/binary>> end
).
-file("src/aws/internal/codec/xml_decode.gleam", 223).
?DOC(
" Decode a Smithy `@timestamp` XML element into the precise\n"
" `Timestamp` shape (seconds + nanoseconds). The FFI ISO 8601\n"
" parser is currently whole-second precision so `nanoseconds`\n"
" will be 0 — once the parser learns fractional seconds we\n"
" flip that here without breaking the API.\n"
).
-spec timestamp_text_precise(element()) -> {ok,
aws@internal@codec@json_timestamp:timestamp()} |
{error, binary()}.
timestamp_text_precise(E) ->
case timestamp_text(E) of
{ok, Secs} ->
{ok, {timestamp, Secs, 0}};
{error, Msg} ->
{error, Msg}
end.
-file("src/aws/internal/codec/xml_decode.gleam", 242).
?DOC(
" Decode an optional child element if present, otherwise return None.\n"
" Used in the generated `decode_<struct>_xml_inner` for member fields.\n"
).
-spec optional_child(
element(),
binary(),
fun((element()) -> {ok, NHZ} | {error, binary()})
) -> {ok, gleam@option:option(NHZ)} | {error, binary()}.
optional_child(Parent, Name, Decode) ->
case find_child(Parent, Name) of
none ->
{ok, none};
{some, E} ->
gleam@result:map(Decode(E), fun(Field@0) -> {some, Field@0} end)
end.
-file("src/aws/internal/codec/xml_decode.gleam", 255).
?DOC(
" Decode a required child element, returning an error when the\n"
" element is absent.\n"
).
-spec required_child(
element(),
binary(),
fun((element()) -> {ok, NIF} | {error, binary()})
) -> {ok, NIF} | {error, binary()}.
required_child(Parent, Name, Decode) ->
case find_child(Parent, Name) of
none ->
{error, <<"xml: missing required child: "/utf8, Name/binary>>};
{some, E} ->
Decode(E)
end.
-file("src/aws/internal/codec/xml_decode.gleam", 268).
?DOC(
" Decode a wrapped list: `<Wrapper><member>v</member>...</Wrapper>`.\n"
" `wrapper` is the parent name, `member_name` is the per-entry tag.\n"
).
-spec optional_list(
element(),
binary(),
binary(),
fun((element()) -> {ok, NIK} | {error, binary()})
) -> {ok, gleam@option:option(list(NIK))} | {error, binary()}.
optional_list(Parent, Wrapper, Member_name, Decode) ->
case find_child(Parent, Wrapper) of
none ->
{ok, none};
{some, W} ->
Entries = find_children(W, Member_name),
_pipe = gleam@list:try_map(Entries, Decode),
gleam@result:map(_pipe, fun(Field@0) -> {some, Field@0} end)
end.
-file("src/aws/internal/codec/xml_decode.gleam", 284).
?DOC(" Decode a required wrapped list.\n").
-spec required_list(
element(),
binary(),
binary(),
fun((element()) -> {ok, NIR} | {error, binary()})
) -> {ok, list(NIR)} | {error, binary()}.
required_list(Parent, Wrapper, Member_name, Decode) ->
case find_child(Parent, Wrapper) of
none ->
{error, <<"xml: missing required list: "/utf8, Wrapper/binary>>};
{some, W} ->
Entries = find_children(W, Member_name),
gleam@list:try_map(Entries, Decode)
end.
-file("src/aws/internal/codec/xml_decode.gleam", 302).
?DOC(
" Decode a flattened list: each entry is a direct child of the\n"
" parent (no wrapping element). Returns None when there are zero\n"
" entries, matching the Option(List(a)) shape of normal lists.\n"
).
-spec optional_flat_list(
element(),
binary(),
fun((element()) -> {ok, NIX} | {error, binary()})
) -> {ok, gleam@option:option(list(NIX))} | {error, binary()}.
optional_flat_list(Parent, Name, Decode) ->
case find_children(Parent, Name) of
[] ->
{ok, none};
Entries ->
_pipe = gleam@list:try_map(Entries, Decode),
gleam@result:map(_pipe, fun(Field@0) -> {some, Field@0} end)
end.
-file("src/aws/internal/codec/xml_decode.gleam", 314).
?DOC(" Decode a required flattened list.\n").
-spec required_flat_list(
element(),
binary(),
fun((element()) -> {ok, NJE} | {error, binary()})
) -> {ok, list(NJE)} | {error, binary()}.
required_flat_list(Parent, Name, Decode) ->
case find_children(Parent, Name) of
[] ->
{error, <<"xml: missing required list: "/utf8, Name/binary>>};
Entries ->
gleam@list:try_map(Entries, Decode)
end.
-file("src/aws/internal/codec/xml_decode.gleam", 330).
?DOC(
" Decode the *inner* portion of a list element — used for nested\n"
" lists where the outer caller has already wrapped each entry in\n"
" `<member>...</member>` and we need to extract its children as a\n"
" sub-list. Returns a bare `List(a)` (not optional) since the\n"
" surrounding `optional_list` already gates on presence.\n"
).
-spec inner_list(
element(),
binary(),
fun((element()) -> {ok, NJK} | {error, binary()})
) -> {ok, list(NJK)} | {error, binary()}.
inner_list(Elem, Member_name, Decode) ->
gleam@list:try_map(find_children(Elem, Member_name), Decode).