-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, optional_list/4, optional_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 `foo`.\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 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__xml_inner` for member fields.\n" ). -spec optional_child( element(), binary(), fun((element()) -> {ok, MWU} | {error, binary()}) ) -> {ok, gleam@option:option(MWU)} | {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 wrapped list: `v...`.\n" " `wrapper` is the parent name, `member_name` is the per-entry tag.\n" ). -spec optional_list( element(), binary(), binary(), fun((element()) -> {ok, MXA} | {error, binary()}) ) -> {ok, gleam@option:option(list(MXA))} | {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", 273). ?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, MXH} | {error, binary()}) ) -> {ok, gleam@option:option(list(MXH))} | {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", 289). ?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" " `...` 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, MXO} | {error, binary()}) ) -> {ok, list(MXO)} | {error, binary()}. inner_list(Elem, Member_name, Decode) -> gleam@list:try_map(find_children(Elem, Member_name), Decode).