Skip to main content

aws_smithy_xml/codec/
deserializer.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! XML deserializer implementing the `ShapeDeserializer` trait.
7
8use super::XmlCodecSettings;
9use crate::decode::{self, Document};
10use aws_smithy_schema::serde::{SerdeError, ShapeDeserializer};
11use aws_smithy_schema::Schema;
12use aws_smithy_types::date_time::Format as TimestampFormat;
13use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document as SmithyDocument};
14use std::borrow::Cow;
15use std::sync::Arc;
16
17/// Maximum recursion depth for deserialization. Payloads nested deeper than
18/// this will produce a [`SerdeError`] instead of risking a stack overflow.
19/// Matches the default used by the JSON and CBOR codecs.
20pub(crate) const MAX_DESERIALIZE_DEPTH: u32 = 128;
21
22/// XML deserializer that implements the `ShapeDeserializer` trait.
23///
24/// Wraps the existing `aws_smithy_xml::decode` SAX-like API and provides
25/// schema-driven dispatch for struct members, lists, and maps.
26///
27/// The deserializer holds the input as `&'a [u8]` throughout. For aggregate
28/// reads (`read_struct`, `read_list`, `read_map`) we construct a fresh
29/// `Document` over `input` on demand. For scalar reads we either parse
30/// `input` to extract the root element's text, or — when a parent
31/// aggregate has already extracted the leaf text from its child element —
32/// take the pre-extracted text directly via `text`.
33///
34/// Sibling dispatch within an aggregate read avoids per-iteration
35/// `XmlDeserializer` construction by reusing `&mut self` through
36/// `dispatch_subslice` / `dispatch_text` helpers, which save and restore
37/// the relevant state across the closure call.
38pub struct XmlDeserializer<'a> {
39    /// XML bytes for this deserializer. For the document root and for
40    /// aggregate sub-deserializers this is the slice of the parent input
41    /// covering the relevant element. Ignored when `text` is set.
42    input: &'a [u8],
43    /// Pre-extracted leaf text. When `Some`, scalar reads consume it
44    /// directly without re-parsing `input`; aggregate reads error.
45    text: Option<Cow<'a, str>>,
46    settings: Arc<XmlCodecSettings>,
47    /// Optional schema override consulted by the next aggregate read
48    /// (`read_struct` / `read_list` / `read_map`) when codegen passes a
49    /// shapeless placeholder schema (e.g. `prelude::DOCUMENT`) for an
50    /// inner aggregate. Used to thread the outer aggregate's
51    /// `value_schema` / `member_schema` (with its own
52    /// `with_map_members`/`with_list_member` chain) into nested reads so
53    /// nested element-name overrides (`@xmlName` on inner key/value) can
54    /// be honored.
55    schema_override: Option<&'static Schema>,
56    /// Aggregate nesting depth. Incremented at the top of each
57    /// `read_struct` / `read_list` / `read_map` and decremented before
58    /// they return so sibling reads on the same deserializer don't
59    /// accumulate. Compared against [`XmlCodecSettings::max_depth`] to
60    /// reject deeply-nested payloads before they exhaust the stack.
61    depth: u32,
62}
63
64impl<'a> XmlDeserializer<'a> {
65    /// Creates a new XML deserializer over raw bytes.
66    pub(crate) fn new(input: &'a [u8], settings: Arc<XmlCodecSettings>) -> Self {
67        Self {
68            input,
69            text: None,
70            settings,
71            schema_override: None,
72            depth: 0,
73        }
74    }
75
76    /// Creates a deserializer pre-loaded with leaf text content. Used by
77    /// tests; runtime dispatch uses [`dispatch_text`](Self::dispatch_text)
78    /// to repoint an existing deserializer at leaf text rather than
79    /// constructing a new instance.
80    #[cfg(test)]
81    fn from_text(text: Cow<'a, str>, settings: Arc<XmlCodecSettings>) -> Self {
82        Self {
83            input: b"",
84            text: Some(text),
85            settings,
86            schema_override: None,
87            depth: 0,
88        }
89    }
90
91    /// Increment the recursion-depth counter and return an error if the
92    /// configured maximum would be exceeded. Caller is responsible for
93    /// decrementing on the way out (see [`Self::leave_aggregate`]).
94    ///
95    /// Order matters: the depth bound is checked *before* the increment so
96    /// the error path doesn't leave the counter incremented. Together with
97    /// the IIFE pattern around each aggregate body (which guarantees
98    /// `leave_aggregate` always runs after a successful `enter_aggregate`),
99    /// this keeps `depth` consistent across `?` propagation.
100    fn enter_aggregate(&mut self) -> Result<(), SerdeError> {
101        if self.depth >= self.settings.max_depth() {
102            return Err(SerdeError::custom("maximum nesting depth exceeded"));
103        }
104        self.depth += 1;
105        Ok(())
106    }
107
108    /// Decrement the recursion-depth counter. Pair with each successful
109    /// [`Self::enter_aggregate`] call.
110    fn leave_aggregate(&mut self) {
111        debug_assert!(self.depth > 0, "leave_aggregate without enter_aggregate");
112        self.depth = self.depth.saturating_sub(1);
113    }
114
115    /// Construct a fresh `Document` over `self.input`. Errors if the
116    /// deserializer holds pre-extracted text (an aggregate read was
117    /// expected on this deserializer).
118    fn document(&self) -> Result<Document<'a>, SerdeError> {
119        if self.text.is_some() {
120            return Err(SerdeError::custom("expected XML element, found text"));
121        }
122        Ok(Document::try_from(self.input).unwrap_or_else(|_| Document::new("")))
123    }
124
125    /// Extract the leaf text content. If `text` was pre-set, returns it
126    /// directly; otherwise parses `input`, navigates to the root element,
127    /// and reads its text content.
128    fn take_text(&mut self) -> Result<Cow<'a, str>, SerdeError> {
129        if let Some(t) = self.text.take() {
130            return Ok(t);
131        }
132        let mut doc = Document::try_from(self.input).unwrap_or_else(|_| Document::new(""));
133        let mut root = doc
134            .root_element()
135            .map_err(|e| SerdeError::custom(e.to_string()))?;
136        decode::try_data(&mut root).map_err(|e| SerdeError::custom(e.to_string()))
137    }
138
139    /// Run `f` against `self` after temporarily repointing it at a sub-slice
140    /// of the parent input. State (input, text, schema_override) is
141    /// saved on entry and restored on return so the deserializer can be
142    /// reused for sibling dispatches without per-iteration allocation.
143    fn dispatch_subslice<R>(
144        &mut self,
145        sub: &'a [u8],
146        schema_override: Option<&'static Schema>,
147        f: impl FnOnce(&mut Self) -> R,
148    ) -> R {
149        let saved_input = std::mem::replace(&mut self.input, sub);
150        let saved_text = self.text.take();
151        let saved_override = std::mem::replace(&mut self.schema_override, schema_override);
152        let r = f(self);
153        self.input = saved_input;
154        self.text = saved_text;
155        self.schema_override = saved_override;
156        r
157    }
158
159    /// Run `f` against `self` after temporarily setting pre-extracted leaf
160    /// text. State is restored on return.
161    fn dispatch_text<R>(&mut self, text: Cow<'a, str>, f: impl FnOnce(&mut Self) -> R) -> R {
162        let saved_input = std::mem::replace(&mut self.input, b"");
163        let saved_text = self.text.replace(text);
164        let saved_override = self.schema_override.take();
165        let r = f(self);
166        self.input = saved_input;
167        self.text = saved_text;
168        self.schema_override = saved_override;
169        r
170    }
171
172    /// Resolve a child element name to a member schema by matching against
173    /// @xmlName (if present) or member_name.
174    fn resolve_member<'s>(schema: &'s Schema, element_name: &str) -> Option<&'s Schema> {
175        schema.members().iter().copied().find(|m| {
176            if let Some(xml_name) = m.xml_name() {
177                xml_name.value() == element_name
178            } else {
179                m.member_name() == Some(element_name)
180            }
181        })
182    }
183
184    /// Find the byte slice in `input` that contains the element whose local name
185    /// pointer `el_local` points into `input`. Uses pointer arithmetic to locate
186    /// the `<` before the element name, then scans forward for the matching close
187    /// tag with depth tracking. Returns the sub-slice `<tag...>...</tag>`.
188    ///
189    /// Operates purely on byte slices — `<`, `>`, `/`, and `?` are all single-
190    /// byte ASCII characters, so the byte-level scanning we do here is correct
191    /// regardless of the multi-byte UTF-8 sequences that may appear in element
192    /// content (e.g. attribute values, text nodes containing non-ASCII chars).
193    /// Previous versions converted to `&str` and panicked on
194    /// `start byte index N is not a char boundary` when a byte-level `pos += 1`
195    /// landed inside a multi-byte sequence; sticking to bytes throughout
196    /// avoids the issue.
197    pub(crate) fn find_element_slice(input: &'a [u8], el_local: &str) -> &'a [u8] {
198        // Invariant: `el_local` must be a sub-slice of `input` (typically
199        // returned by xmlparser as a borrow into the underlying bytes).
200        // The pointer-arithmetic below assumes containment; passing a
201        // separately-allocated `String` would compute a meaningless
202        // offset. Caught by `.saturating_sub.min` clamping at runtime
203        // (so we don't UB) but the result is silently wrong. The assert
204        // surfaces the misuse in debug builds.
205        debug_assert!(
206            {
207                let lo = input.as_ptr() as usize;
208                let hi = lo + input.len();
209                let p = el_local.as_ptr() as usize;
210                p >= lo && p + el_local.len() <= hi
211            },
212            "find_element_slice: el_local must point into input"
213        );
214        let name_ptr = el_local.as_ptr() as usize;
215        let input_start = input.as_ptr() as usize;
216        let name_offset = name_ptr.saturating_sub(input_start).min(input.len());
217
218        // The element name is inside the input. Find the `<` immediately
219        // preceding it.
220        let el_start = input[..name_offset]
221            .iter()
222            .rposition(|&b| b == b'<')
223            .unwrap_or(0);
224
225        // Scan forward, byte by byte, tracking nesting of elements with the
226        // same local name. `<`, `>`, `/`, `?` are single-byte ASCII so the
227        // byte-level cursor is always at the start of a UTF-8 char.
228        let tag_name = el_local.as_bytes();
229        let remaining = &input[el_start..];
230        let mut depth = 0i32;
231        let mut pos = 0;
232        while pos < remaining.len() {
233            if remaining[pos..].starts_with(b"</") {
234                // Close tag — check if it matches our tag name.
235                let after_slash = pos + 2;
236                if remaining[after_slash..].starts_with(tag_name) {
237                    let after_name = after_slash + tag_name.len();
238                    if remaining.get(after_name) == Some(&b'>') {
239                        depth -= 1;
240                        if depth == 0 {
241                            let end = el_start + after_name + 1;
242                            return &input[el_start..end];
243                        }
244                    }
245                }
246                pos = after_slash;
247            } else if remaining[pos] == b'<'
248                && remaining.get(pos + 1) != Some(&b'/')
249                && remaining.get(pos + 1) != Some(&b'?')
250            {
251                // Open tag — check if self-closing or matches our name.
252                if let Some(gt) = remaining[pos..].iter().position(|&b| b == b'>') {
253                    let tag_content = &remaining[pos + 1..pos + gt];
254                    let is_self_closing = tag_content.last() == Some(&b'/');
255                    let opens_our_tag = tag_content.starts_with(tag_name)
256                        && tag_content
257                            .get(tag_name.len())
258                            .is_none_or(|&b| b == b' ' || b == b'>' || b == b'/');
259                    if opens_our_tag && is_self_closing && depth == 0 {
260                        // The target element is itself self-closing (e.g.
261                        // `<Foo/>`) — there is no matching close tag, so the
262                        // element slice ends just past this `>`.
263                        let end = el_start + pos + gt + 1;
264                        return &input[el_start..end];
265                    }
266                    if opens_our_tag && !is_self_closing {
267                        depth += 1;
268                    }
269                    pos += gt + 1;
270                } else {
271                    pos += 1;
272                }
273            } else {
274                // Any other byte (text content, attribute byte, multi-byte
275                // UTF-8 lead/continuation, etc.) — advance by one byte. This
276                // is correct because we never `&str`-index into `remaining`,
277                // only byte-slice it, and byte slicing on a `&[u8]` accepts
278                // any offset.
279                pos += 1;
280            }
281        }
282        // Fallback: return from el_start to end
283        &input[el_start..]
284    }
285
286    fn resolve_timestamp_format(&self, schema: &Schema) -> TimestampFormat {
287        schema
288            .timestamp_format()
289            .map(|t| match t.format() {
290                aws_smithy_schema::traits::TimestampFormat::EpochSeconds => {
291                    TimestampFormat::EpochSeconds
292                }
293                // Use the lenient `DateTimeWithOffset` so timezone-suffixed
294                // RFC-3339 strings (e.g. `2019-12-17T00:48:18+01:00`) parse —
295                // matches the Smithy `date-time` protocol-test expectations
296                // and the JSON codec's behavior.
297                aws_smithy_schema::traits::TimestampFormat::DateTime => {
298                    TimestampFormat::DateTimeWithOffset
299                }
300                aws_smithy_schema::traits::TimestampFormat::HttpDate => TimestampFormat::HttpDate,
301            })
302            .unwrap_or_else(|| match self.settings.default_timestamp_format() {
303                TimestampFormat::DateTime => TimestampFormat::DateTimeWithOffset,
304                other => other,
305            })
306    }
307}
308
309/// Locate a depth-2 XML element (a direct child of the document root) whose
310/// local name satisfies `predicate`, returning the byte slice covering it
311/// (`<El>...</El>`, inclusive of tags). The document root itself is also
312/// considered, so an "unwrapped" envelope whose root already matches is found.
313///
314/// Returns `None` if the body is not valid UTF-8, is not parseable as XML, or
315/// contains no matching element — callers decide how to fall back.
316///
317/// Robust to start-tag attributes (e.g. `<Error xmlns="...">`), nested
318/// same-name elements, comments, and CDATA, which a naive substring search
319/// would mishandle. Both the AWS REST XML error path (`name == "Error"`) and
320/// the awsQuery response path (`name.ends_with("Result") || name == "Error"`)
321/// build on this.
322pub fn find_depth2_element_slice_by(
323    body: &[u8],
324    predicate: impl Fn(&str) -> bool,
325) -> Option<&[u8]> {
326    let mut doc = Document::try_from(body).ok()?;
327    let mut root = doc.root_element().ok()?;
328    // Unwrapped envelope: the root element itself matches. Its start/end tags
329    // are already at the body boundaries, so return the whole body.
330    if predicate(root.start_el().local()) {
331        return Some(body);
332    }
333    // Wrapped envelope: scan the root's direct children for a match.
334    while let Some(tag) = root.next_tag() {
335        let local = tag.start_el().local();
336        if predicate(local) {
337            // `local` is a `&str` borrowed from `body`, satisfying the
338            // pointer-containment invariant of `find_element_slice`.
339            return Some(XmlDeserializer::find_element_slice(body, local));
340        }
341    }
342    None
343}
344
345impl ShapeDeserializer for XmlDeserializer<'_> {
346    fn read_struct(
347        &mut self,
348        schema: &Schema,
349        consumer: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
350    ) -> Result<(), SerdeError> {
351        self.enter_aggregate()?;
352        // IIFE: any `?` (or early `return`) inside falls through to
353        // `leave_aggregate` below — preserving the depth counter on the
354        // error path. `return` inside a Rust closure returns from the
355        // closure, not the enclosing function, so the unwrapped-output
356        // early-return below correctly produces `Ok(())` for the IIFE.
357        let result = (|| -> Result<(), SerdeError> {
358            // Build a Document over `self.input` locally. Doing it here (rather
359            // than as part of `XmlDeserializer` state) keeps the iteration
360            // borrow scoped to this stack frame, which lets us mutate `self`
361            // (via `dispatch_*`) for child-consumer dispatches without fighting
362            // a long-lived borrow on `self.state`.
363            let input = self.input;
364            let mut doc = self.document()?;
365            let mut root = doc
366                .root_element()
367                .map_err(|e| SerdeError::custom(e.to_string()))?;
368
369            // Unwrapped XML output (e.g. S3 `GetBucketLocation` whose body is
370            // `<LocationConstraint>...</LocationConstraint>` rather than
371            // `<GetBucketLocationOutput><LocationConstraint>...`). The body's
372            // root element IS the (sole) member element — dispatch it directly
373            // and skip the normal "enter wrapper, iterate children" path. The
374            // schema flag is set by codegen for operations with the
375            // `S3UnwrappedXmlOutputTrait` AWS customization; non-XML codecs
376            // ignore the field, preserving runtime protocol-swap compatibility.
377            if schema.xml_unwrapped_output() {
378                // Capture the local element name and find its byte range
379                // BEFORE dropping `root` / `doc`, because:
380                //   - `find_element_slice`'s pointer-arithmetic invariant
381                //     requires `el_local` to be a sub-slice of `input`;
382                //     `root.start_el().local()` returns exactly that.
383                //   - The owned `String` is only used by `resolve_member`,
384                //     after the parser borrows are released. A previous
385                //     version of this code passed the owned `String` to
386                //     `find_element_slice`, silently producing offset=0;
387                //     correct only by happy accident when the input
388                //     buffer started with the target element.
389                let el_local = root.start_el().local();
390                let sub = Self::find_element_slice(input, el_local);
391                let local = el_local.to_owned();
392                // Release the iterator borrow on `doc` so we can mutate `self`.
393                // `root` is a `ScopedDecoder` (whose `Drop` advances the tokenizer
394                // past the close tag) and is dropped explicitly. `doc` is a
395                // `decode::Document` which has no `Drop` impl; binding to `_`
396                // consumes it without firing clippy's `drop_non_drop` lint.
397                drop(root);
398                let _ = doc;
399                if let Some(member) = Self::resolve_member(schema, &local) {
400                    self.dispatch_subslice(sub, None, |this| consumer(member, this))?;
401                }
402                return Ok(());
403            }
404
405            // Dispatch @xmlAttribute members from the start element's attributes.
406            for member in schema.members() {
407                if member.xml_attribute() {
408                    let attr_name = member
409                        .xml_name()
410                        .map(|t| t.value())
411                        .or(member.member_name())
412                        .unwrap_or("");
413                    if let Some(value) = root.start_el().attr(attr_name) {
414                        let text = Cow::Owned(value.to_owned());
415                        self.dispatch_text(text, |this| consumer(member, this))?;
416                    }
417                }
418            }
419
420            // Track flattened-aggregate members: their wire format is repeated sibling
421            // elements that must be accumulated and dispatched as a single read_list /
422            // read_map call. Map: member_index -> (member_schema, accumulated XML bytes).
423            let mut flattened_groups: std::collections::HashMap<usize, (&Schema, Vec<u8>)> =
424                std::collections::HashMap::new();
425
426            // Dispatch child elements.
427            while let Some(mut child_scope) = root.next_tag() {
428                let local = child_scope.start_el().local().to_owned();
429                let Some(member) = Self::resolve_member(schema, &local) else {
430                    continue;
431                };
432                // For non-flattened aggregate members, the child element IS the
433                // aggregate container (e.g. `<myList><member>...</member></myList>`).
434                // Use a sub-slice into `input` so the consumer's read_list / read_map
435                // / read_struct can build its own Document over the child element.
436                // For flattened aggregate members, accumulate the bytes of each
437                // matching sibling and dispatch them together below.
438                // For scalars (including flattened scalars), extract text inline.
439                let is_aggregate = member.shape_type().is_aggregate();
440                if is_aggregate && !member.xml_flattened() {
441                    let el_local = child_scope.start_el().local();
442                    let sub = Self::find_element_slice(input, el_local);
443                    drop(child_scope);
444                    self.dispatch_subslice(sub, None, |this| consumer(member, this))?;
445                } else if is_aggregate {
446                    // Flattened aggregate: capture this sibling's slice; dispatch
447                    // the merged group below.
448                    let el_local = child_scope.start_el().local();
449                    let sub = Self::find_element_slice(input, el_local);
450                    drop(child_scope);
451                    let idx = member.member_index().unwrap_or(usize::MAX);
452                    let entry = flattened_groups
453                        .entry(idx)
454                        .or_insert_with(|| (member, Vec::new()));
455                    entry.1.extend_from_slice(sub);
456                } else {
457                    let text = decode::try_data(&mut child_scope)
458                        .map_err(|e| SerdeError::custom(e.to_string()))?;
459                    drop(child_scope);
460                    self.dispatch_text(text, |this| consumer(member, this))?;
461                }
462            }
463
464            // Dispatch each accumulated flattened-aggregate group as a single call.
465            // We synthesize a `<__flat>...</__flat>` wrapper so the consumer's
466            // `read_list` / `read_map` sees the collected siblings as
467            // wrapper-children and iterates them normally. The wrapper buffer is
468            // owned locally (lifetime is shorter than `'a`), so we cannot route
469            // it through `dispatch_subslice` — keep a fresh deserializer for
470            // this case only.
471            for (_idx, (member, bytes)) in flattened_groups {
472                let mut wrapped = Vec::with_capacity(bytes.len() + 16);
473                wrapped.extend_from_slice(b"<__flat>");
474                wrapped.extend_from_slice(&bytes);
475                wrapped.extend_from_slice(b"</__flat>");
476                let mut child_deser = XmlDeserializer::new(&wrapped, self.settings.clone());
477                consumer(member, &mut child_deser)?;
478            }
479            Ok(())
480        })();
481        self.leave_aggregate();
482        result
483    }
484
485    fn read_list(
486        &mut self,
487        _schema: &Schema,
488        consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
489    ) -> Result<(), SerdeError> {
490        self.enter_aggregate()?;
491        // IIFE: any `?` inside falls through to `leave_aggregate` below
492        // (see `read_string_list` for rationale).
493        let result = (|| -> Result<(), SerdeError> {
494            let input = self.input;
495            let mut doc = self.document()?;
496            let mut root = doc
497                .root_element()
498                .map_err(|e| SerdeError::custom(e.to_string()))?;
499
500            // Each child tag is a list item. Provide each item to the consumer
501            // by re-pointing `self` at the item's sub-slice via dispatch_subslice.
502            // Scalar consumers will navigate to the text via `take_text()`;
503            // aggregate consumers (read_list / read_struct / read_map) will
504            // descend into the element's content. This keeps the deserializer
505            // compatible with both scalar list elements and nested aggregate
506            // elements (e.g. list-of-lists, list-of-structs) without per-element
507            // type sniffing.
508            while let Some(child_scope) = root.next_tag() {
509                let el_local = child_scope.start_el().local();
510                let sub = Self::find_element_slice(input, el_local);
511                drop(child_scope);
512                self.dispatch_subslice(sub, None, |this| consumer(this))?;
513            }
514            Ok(())
515        })();
516        self.leave_aggregate();
517        result
518    }
519
520    fn read_map(
521        &mut self,
522        schema: &Schema,
523        consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
524    ) -> Result<(), SerdeError> {
525        self.enter_aggregate()?;
526        // IIFE: any `?` inside falls through to `leave_aggregate` below
527        // (see `read_string_list` for rationale).
528        let result = (|| -> Result<(), SerdeError> {
529            // If a parent aggregate read installed a `schema_override` on us, it
530            // takes priority. Codegen generates inner `read_map` calls reusing
531            // the outer `member` schema (closure variable shadowing) — so the
532            // arg here is the outer map's schema, not the inner's. The override
533            // carries the outer's `_VALUE` schema (which chains the inner map's
534            // `_KEY` / `_VALUE`) and is the right one for nested element-name
535            // resolution.
536            let effective_schema: &Schema =
537                self.schema_override.map(|s| s as &Schema).unwrap_or(schema);
538            // Once we've read this override, clear it so it doesn't leak to
539            // sibling reads on the same deserializer.
540            self.schema_override = None;
541            let schema = effective_schema;
542
543            let input = self.input;
544            let mut doc = self.document()?;
545            let mut root = doc
546                .root_element()
547                .map_err(|e| SerdeError::custom(e.to_string()))?;
548
549            // Resolve key/value element names from the schema.
550            let key_name = schema
551                .key()
552                .and_then(|k| k.xml_name().map(|t| t.value()))
553                .unwrap_or("key");
554            let value_name = schema
555                .member()
556                .and_then(|v| v.xml_name().map(|t| t.value()))
557                .unwrap_or("value");
558
559            // Aggregate (struct/list/map) value types need a sub-document deserializer
560            // since their content includes nested elements rather than just text.
561            let value_is_aggregate = schema
562                .member()
563                .map(|v| v.shape_type().is_aggregate())
564                .unwrap_or(false);
565            // For aggregate values, save the value member's `'static` schema so a
566            // nested inner aggregate read (which codegen invokes with
567            // `prelude::DOCUMENT`) can recover its own `_KEY`/`_VALUE`
568            // chain via the `schema_override` parameter on `dispatch_subslice`.
569            let value_schema_static = schema.member_static();
570
571            // Each child tag is an entry (e.g. <entry><key>k</key><value>v</value></entry>).
572            while let Some(mut entry_scope) = root.next_tag() {
573                let mut key: Option<String> = None;
574                // For scalar values we capture the text upfront; for aggregate
575                // values we capture the element sub-slice. At most one is set.
576                let mut value_text: Option<Cow<'_, str>> = None;
577                let mut value_slice: Option<&'_ [u8]> = None;
578                while let Some(mut field_scope) = entry_scope.next_tag() {
579                    let local = field_scope.start_el().local().to_owned();
580                    if local == key_name {
581                        let text = decode::try_data(&mut field_scope)
582                            .map_err(|e| SerdeError::custom(e.to_string()))?;
583                        key = Some(text.into_owned());
584                    } else if local == value_name {
585                        if value_is_aggregate {
586                            let el_local = field_scope.start_el().local();
587                            let sub = Self::find_element_slice(input, el_local);
588                            drop(field_scope);
589                            value_slice = Some(sub);
590                        } else {
591                            let text = decode::try_data(&mut field_scope)
592                                .map_err(|e| SerdeError::custom(e.to_string()))?;
593                            value_text = Some(text);
594                        }
595                    }
596                }
597                drop(entry_scope);
598                if let Some(k) = key {
599                    if let Some(slice) = value_slice {
600                        self.dispatch_subslice(slice, value_schema_static, |this| {
601                            consumer(k, this)
602                        })?;
603                    } else if let Some(t) = value_text {
604                        // Re-borrow t as 'a — the text was extracted from `doc`
605                        // which borrows `self.input: &'a [u8]`, so its lifetime
606                        // is `'a`.
607                        let t: Cow<'_, str> = t;
608                        self.dispatch_text(t.into_owned().into(), |this| consumer(k, this))?;
609                    }
610                }
611            }
612            Ok(())
613        })();
614        self.leave_aggregate();
615        result
616    }
617
618    fn read_boolean(&mut self, _schema: &Schema) -> Result<bool, SerdeError> {
619        let text = self.take_text()?;
620        match text.as_ref() {
621            "true" => Ok(true),
622            "false" => Ok(false),
623            other => Err(SerdeError::custom(format!("invalid boolean: {other}"))),
624        }
625    }
626
627    fn read_byte(&mut self, _schema: &Schema) -> Result<i8, SerdeError> {
628        let text = self.take_text()?;
629        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
630    }
631
632    fn read_short(&mut self, _schema: &Schema) -> Result<i16, SerdeError> {
633        let text = self.take_text()?;
634        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
635    }
636
637    fn read_integer(&mut self, _schema: &Schema) -> Result<i32, SerdeError> {
638        let text = self.take_text()?;
639        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
640    }
641
642    fn read_long(&mut self, _schema: &Schema) -> Result<i64, SerdeError> {
643        let text = self.take_text()?;
644        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
645    }
646
647    fn read_float(&mut self, _schema: &Schema) -> Result<f32, SerdeError> {
648        let text = self.take_text()?;
649        match text.as_ref() {
650            "NaN" => Ok(f32::NAN),
651            "Infinity" => Ok(f32::INFINITY),
652            "-Infinity" => Ok(f32::NEG_INFINITY),
653            _ => text.parse().map_err(|e| SerdeError::custom(format!("{e}"))),
654        }
655    }
656
657    fn read_double(&mut self, _schema: &Schema) -> Result<f64, SerdeError> {
658        let text = self.take_text()?;
659        match text.as_ref() {
660            "NaN" => Ok(f64::NAN),
661            "Infinity" => Ok(f64::INFINITY),
662            "-Infinity" => Ok(f64::NEG_INFINITY),
663            _ => text.parse().map_err(|e| SerdeError::custom(format!("{e}"))),
664        }
665    }
666
667    fn read_big_integer(&mut self, _schema: &Schema) -> Result<BigInteger, SerdeError> {
668        let text = self.take_text()?;
669        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
670    }
671
672    fn read_big_decimal(&mut self, _schema: &Schema) -> Result<BigDecimal, SerdeError> {
673        let text = self.take_text()?;
674        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
675    }
676
677    fn read_string(&mut self, _schema: &Schema) -> Result<String, SerdeError> {
678        let text = self.take_text()?;
679        Ok(text.into_owned())
680    }
681
682    fn read_blob(&mut self, _schema: &Schema) -> Result<Blob, SerdeError> {
683        let text = self.take_text()?;
684        let bytes = aws_smithy_types::base64::decode(text.as_ref())
685            .map_err(|e| SerdeError::custom(format!("{e}")))?;
686        Ok(Blob::new(bytes))
687    }
688
689    fn read_timestamp(&mut self, schema: &Schema) -> Result<DateTime, SerdeError> {
690        let text = self.take_text()?;
691        let format = self.resolve_timestamp_format(schema);
692        DateTime::from_str(text.as_ref(), format).map_err(|e| SerdeError::custom(format!("{e}")))
693    }
694
695    fn read_document(&mut self, _schema: &Schema) -> Result<SmithyDocument, SerdeError> {
696        Err(SerdeError::custom(
697            "document types are not supported by REST XML",
698        ))
699    }
700
701    // -------- Specialized collection overrides --------
702    //
703    // The default trait impls of `read_*_list` / `read_string_string_map`
704    // call `self.read_list` / `self.read_map` with a `&mut dyn FnMut` consumer
705    // that itself calls `&mut dyn ShapeDeserializer::read_X` per element.
706    // For XML this is doubly wasteful: each list element pays
707    //
708    //   1. one `&mut dyn FnMut` indirect call,
709    //   2. one `&mut dyn ShapeDeserializer` virtual call,
710    //   3. and — because the consumer goes through `dispatch_subslice` —
711    //      a fresh `Document::try_from` over the element's sub-slice plus
712    //      a save/restore of (`input`, `text`, `schema_override`).
713    //
714    // The overrides below walk the existing tokenizer once and extract
715    // text inline via `decode::try_data`, eliminating all three costs.
716    // They preserve the default behavior of accepting any child element
717    // name (matching `XmlDeserializer::read_list` / `read_map` which do
718    // not validate element names against the schema's expected member
719    // name — element-name dispatch is the deserializer's responsibility
720    // for structs only).
721    //
722    // Sparse lists are not routed here: `SchemaGenerator` only emits
723    // `read_string_list` / `read_blob_list` / `read_integer_list` /
724    // `read_long_list` / `read_string_string_map` for non-sparse element
725    // shapes (see SchemaGenerator.kt line ~1497).
726
727    fn read_string_list(&mut self, _schema: &Schema) -> Result<Vec<String>, SerdeError> {
728        self.enter_aggregate()?;
729        // IIFE so that any `?` short-circuit still falls through to
730        // `leave_aggregate` below — preserving the depth counter on the
731        // error path. Same pattern in `read_blob_list`, `read_integer_list`,
732        // `read_long_list`, and `read_string_string_map`.
733        let result = (|| -> Result<Vec<String>, SerdeError> {
734            let mut doc = self.document()?;
735            let mut root = doc
736                .root_element()
737                .map_err(|e| SerdeError::custom(e.to_string()))?;
738            let mut out = Vec::new();
739            while let Some(mut child_scope) = root.next_tag() {
740                let text = decode::try_data(&mut child_scope)
741                    .map_err(|e| SerdeError::custom(e.to_string()))?;
742                out.push(text.into_owned());
743            }
744            Ok(out)
745        })();
746        self.leave_aggregate();
747        result
748    }
749
750    fn read_blob_list(&mut self, _schema: &Schema) -> Result<Vec<Blob>, SerdeError> {
751        use aws_smithy_types::base64;
752        self.enter_aggregate()?;
753        let result = (|| -> Result<Vec<Blob>, SerdeError> {
754            let mut doc = self.document()?;
755            let mut root = doc
756                .root_element()
757                .map_err(|e| SerdeError::custom(e.to_string()))?;
758            let mut out = Vec::new();
759            while let Some(mut child_scope) = root.next_tag() {
760                let text = decode::try_data(&mut child_scope)
761                    .map_err(|e| SerdeError::custom(e.to_string()))?;
762                let bytes = base64::decode(text.as_ref())
763                    .map_err(|e| SerdeError::custom(format!("invalid base64: {e}")))?;
764                out.push(Blob::new(bytes));
765            }
766            Ok(out)
767        })();
768        self.leave_aggregate();
769        result
770    }
771
772    fn read_integer_list(&mut self, _schema: &Schema) -> Result<Vec<i32>, SerdeError> {
773        self.enter_aggregate()?;
774        let result = (|| -> Result<Vec<i32>, SerdeError> {
775            let mut doc = self.document()?;
776            let mut root = doc
777                .root_element()
778                .map_err(|e| SerdeError::custom(e.to_string()))?;
779            let mut out = Vec::new();
780            while let Some(mut child_scope) = root.next_tag() {
781                let text = decode::try_data(&mut child_scope)
782                    .map_err(|e| SerdeError::custom(e.to_string()))?;
783                let v: i32 = text
784                    .parse()
785                    .map_err(|e| SerdeError::custom(format!("{e}")))?;
786                out.push(v);
787            }
788            Ok(out)
789        })();
790        self.leave_aggregate();
791        result
792    }
793
794    fn read_long_list(&mut self, _schema: &Schema) -> Result<Vec<i64>, SerdeError> {
795        self.enter_aggregate()?;
796        let result = (|| -> Result<Vec<i64>, SerdeError> {
797            let mut doc = self.document()?;
798            let mut root = doc
799                .root_element()
800                .map_err(|e| SerdeError::custom(e.to_string()))?;
801            let mut out = Vec::new();
802            while let Some(mut child_scope) = root.next_tag() {
803                let text = decode::try_data(&mut child_scope)
804                    .map_err(|e| SerdeError::custom(e.to_string()))?;
805                let v: i64 = text
806                    .parse()
807                    .map_err(|e| SerdeError::custom(format!("{e}")))?;
808                out.push(v);
809            }
810            Ok(out)
811        })();
812        self.leave_aggregate();
813        result
814    }
815
816    fn read_string_string_map(
817        &mut self,
818        schema: &Schema,
819    ) -> Result<std::collections::HashMap<String, String>, SerdeError> {
820        // Mirror the schema_override / key-name / value-name resolution
821        // used by `read_map` so the override is a behavioral drop-in.
822        let effective_schema: &Schema =
823            self.schema_override.map(|s| s as &Schema).unwrap_or(schema);
824        self.schema_override = None;
825        let schema = effective_schema;
826
827        let key_name = schema
828            .key()
829            .and_then(|k| k.xml_name().map(|t| t.value()))
830            .unwrap_or("key");
831        let value_name = schema
832            .member()
833            .and_then(|v| v.xml_name().map(|t| t.value()))
834            .unwrap_or("value");
835
836        self.enter_aggregate()?;
837        let result = (|| -> Result<std::collections::HashMap<String, String>, SerdeError> {
838            let mut doc = self.document()?;
839            let mut root = doc
840                .root_element()
841                .map_err(|e| SerdeError::custom(e.to_string()))?;
842            let mut out = std::collections::HashMap::new();
843            while let Some(mut entry_scope) = root.next_tag() {
844                let mut k: Option<String> = None;
845                let mut v: Option<String> = None;
846                while let Some(mut field_scope) = entry_scope.next_tag() {
847                    let local = field_scope.start_el().local().to_owned();
848                    if local == key_name {
849                        let text = decode::try_data(&mut field_scope)
850                            .map_err(|e| SerdeError::custom(e.to_string()))?;
851                        k = Some(text.into_owned());
852                    } else if local == value_name {
853                        let text = decode::try_data(&mut field_scope)
854                            .map_err(|e| SerdeError::custom(e.to_string()))?;
855                        v = Some(text.into_owned());
856                    }
857                }
858                if let (Some(k), Some(v)) = (k, v) {
859                    out.insert(k, v);
860                }
861            }
862            Ok(out)
863        })();
864        self.leave_aggregate();
865        result
866    }
867
868    fn is_null(&self) -> bool {
869        // XML represents absence by omitting the element entirely.
870        // If we have a deserializer, the element exists, so it's not null.
871        false
872    }
873
874    fn container_size(&self) -> Option<usize> {
875        None
876    }
877}
878
879#[cfg(test)]
880mod tests {
881    use super::*;
882    use aws_smithy_schema::{shape_id, Schema, ShapeType};
883
884    static STRING_MEMBER: Schema =
885        Schema::new_member(shape_id!("test", "S$v"), ShapeType::String, "v", 0);
886
887    #[test]
888    fn read_string_from_text_state() {
889        let settings = Arc::new(XmlCodecSettings::default());
890        let mut deser = XmlDeserializer::from_text(Cow::Borrowed("hello"), settings);
891        let result = deser.read_string(&STRING_MEMBER).unwrap();
892        assert_eq!(result, "hello");
893    }
894
895    #[test]
896    fn read_string_from_doc_text_content() {
897        // Verify a Doc-state deserializer can extract leaf text via the
898        // public `read_string` API (which goes through `take_text` →
899        // lazy Document construction over `self.input`).
900        static V_MEMBER: Schema =
901            Schema::new_member(shape_id!("test", "X$v"), ShapeType::String, "v", 0);
902        let xml = b"<root>world</root>";
903        let settings = Arc::new(XmlCodecSettings::default());
904        let mut deser = XmlDeserializer::new(xml, settings);
905        let result = deser.read_string(&V_MEMBER).unwrap();
906        assert_eq!(result, "world");
907    }
908
909    #[test]
910    fn is_null_always_false() {
911        let settings = Arc::new(XmlCodecSettings::default());
912        let deser = XmlDeserializer::new(b"<r/>", settings);
913        assert!(!deser.is_null());
914    }
915
916    // Struct member dispatch by element name (`@xmlName` and member name).
917
918    static NAME_MEMBER: Schema = Schema::new_member(
919        shape_id!("test", "Person$name"),
920        ShapeType::String,
921        "name",
922        0,
923    );
924    static AGE_MEMBER: Schema =
925        Schema::new_member(shape_id!("test", "Person$age"), ShapeType::String, "age", 1);
926    static RENAMED_MEMBER: Schema = Schema::new_member(
927        shape_id!("test", "Person$nick"),
928        ShapeType::String,
929        "nick",
930        2,
931    )
932    .with_xml_name("Nickname");
933
934    static PERSON_SCHEMA: Schema = Schema::new_struct(
935        shape_id!("test", "Person"),
936        ShapeType::Structure,
937        &[&NAME_MEMBER, &AGE_MEMBER, &RENAMED_MEMBER],
938    );
939
940    #[test]
941    fn read_struct_dispatches_members() {
942        let xml = b"<Person><name>Alice</name><age>30</age></Person>";
943        let settings = Arc::new(XmlCodecSettings::default());
944        let mut deser = XmlDeserializer::new(xml, settings);
945
946        let mut name = String::new();
947        let mut age = String::new();
948        deser
949            .read_struct(&PERSON_SCHEMA, &mut |member, d| {
950                match member.member_name().unwrap() {
951                    "name" => name = d.read_string(member)?,
952                    "age" => age = d.read_string(member)?,
953                    _ => {}
954                }
955                Ok(())
956            })
957            .unwrap();
958
959        assert_eq!(name, "Alice");
960        assert_eq!(age, "30");
961    }
962
963    #[test]
964    fn read_struct_skips_unknown_elements() {
965        let xml = b"<Person><unknown>x</unknown><name>Bob</name></Person>";
966        let settings = Arc::new(XmlCodecSettings::default());
967        let mut deser = XmlDeserializer::new(xml, settings);
968
969        let mut name = String::new();
970        deser
971            .read_struct(&PERSON_SCHEMA, &mut |member, d| {
972                if member.member_name() == Some("name") {
973                    name = d.read_string(member)?;
974                }
975                Ok(())
976            })
977            .unwrap();
978
979        assert_eq!(name, "Bob");
980    }
981
982    #[test]
983    fn read_struct_resolves_xml_name() {
984        let xml = b"<Person><Nickname>Ally</Nickname></Person>";
985        let settings = Arc::new(XmlCodecSettings::default());
986        let mut deser = XmlDeserializer::new(xml, settings);
987
988        let mut nick = String::new();
989        deser
990            .read_struct(&PERSON_SCHEMA, &mut |member, d| {
991                if member.member_name() == Some("nick") {
992                    nick = d.read_string(member)?;
993                }
994                Ok(())
995            })
996            .unwrap();
997
998        assert_eq!(nick, "Ally");
999    }
1000
1001    // `@xmlAttribute` dispatch from the start element's attributes.
1002
1003    static ATTR_MEMBER: Schema =
1004        Schema::new_member(shape_id!("test", "X$id"), ShapeType::String, "id", 0)
1005            .with_xml_attribute();
1006    static ELEM_MEMBER: Schema =
1007        Schema::new_member(shape_id!("test", "X$name"), ShapeType::String, "name", 1);
1008
1009    static X_SCHEMA: Schema = Schema::new_struct(
1010        shape_id!("test", "X"),
1011        ShapeType::Structure,
1012        &[&ATTR_MEMBER, &ELEM_MEMBER],
1013    );
1014
1015    #[test]
1016    fn read_struct_dispatches_attributes() {
1017        let xml = b"<X id=\"42\"><name>hello</name></X>";
1018        let settings = Arc::new(XmlCodecSettings::default());
1019        let mut deser = XmlDeserializer::new(xml, settings);
1020
1021        let mut id = String::new();
1022        let mut name = String::new();
1023        deser
1024            .read_struct(&X_SCHEMA, &mut |member, d| {
1025                match member.member_name().unwrap() {
1026                    "id" => id = d.read_string(member)?,
1027                    "name" => name = d.read_string(member)?,
1028                    _ => {}
1029                }
1030                Ok(())
1031            })
1032            .unwrap();
1033
1034        assert_eq!(id, "42");
1035        assert_eq!(name, "hello");
1036    }
1037
1038    // Wrapped list / map reads with element-name resolution.
1039
1040    #[test]
1041    fn read_list_wrapped() {
1042        let xml = b"<items><member>a</member><member>b</member></items>";
1043        let settings = Arc::new(XmlCodecSettings::default());
1044        let mut deser = XmlDeserializer::new(xml, settings);
1045
1046        static LIST_MEMBER: Schema = Schema::new_member(
1047            shape_id!("test", "L$member"),
1048            ShapeType::String,
1049            "member",
1050            0,
1051        );
1052        static LIST_SCHEMA: Schema = Schema::new_list(shape_id!("test", "L"), &LIST_MEMBER);
1053
1054        let mut items = Vec::new();
1055        deser
1056            .read_list(&LIST_SCHEMA, &mut |d| {
1057                items.push(d.read_string(&LIST_MEMBER)?);
1058                Ok(())
1059            })
1060            .unwrap();
1061
1062        assert_eq!(items, vec!["a", "b"]);
1063    }
1064
1065    #[test]
1066    fn read_map_wrapped() {
1067        let xml = b"<myMap><entry><key>k1</key><value>v1</value></entry><entry><key>k2</key><value>v2</value></entry></myMap>";
1068        let settings = Arc::new(XmlCodecSettings::default());
1069        let mut deser = XmlDeserializer::new(xml, settings);
1070
1071        static MAP_KEY: Schema =
1072            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1073        static MAP_VALUE: Schema =
1074            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 0);
1075        static MAP_SCHEMA: Schema = Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
1076
1077        let mut entries = Vec::new();
1078        deser
1079            .read_map(&MAP_SCHEMA, &mut |k, d| {
1080                entries.push((k, d.read_string(&MAP_VALUE)?));
1081                Ok(())
1082            })
1083            .unwrap();
1084
1085        assert_eq!(
1086            entries,
1087            vec![
1088                ("k1".to_owned(), "v1".to_owned()),
1089                ("k2".to_owned(), "v2".to_owned())
1090            ]
1091        );
1092    }
1093
1094    #[test]
1095    fn read_map_with_renamed_key_value() {
1096        let xml = b"<m><entry><Attribute>a</Attribute><Setting>s</Setting></entry></m>";
1097        let settings = Arc::new(XmlCodecSettings::default());
1098        let mut deser = XmlDeserializer::new(xml, settings);
1099
1100        static MAP_KEY: Schema =
1101            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0)
1102                .with_xml_name("Attribute");
1103        static MAP_VALUE: Schema =
1104            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 0)
1105                .with_xml_name("Setting");
1106        static MAP_SCHEMA: Schema = Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
1107
1108        let mut entries = Vec::new();
1109        deser
1110            .read_map(&MAP_SCHEMA, &mut |k, d| {
1111                entries.push((k, d.read_string(&MAP_VALUE)?));
1112                Ok(())
1113            })
1114            .unwrap();
1115
1116        assert_eq!(entries, vec![("a".to_owned(), "s".to_owned())]);
1117    }
1118
1119    // Flattened collections: repeated sibling elements accumulated into one read.
1120
1121    #[test]
1122    fn read_struct_flattened_list() {
1123        // Flattened list: repeated <item> siblings inside the struct.
1124        let xml = b"<S><name>hi</name><item>a</item><item>b</item></S>";
1125        let settings = Arc::new(XmlCodecSettings::default());
1126        let mut deser = XmlDeserializer::new(xml, settings);
1127
1128        static S_NAME: Schema =
1129            Schema::new_member(shape_id!("test", "S$name"), ShapeType::String, "name", 0);
1130        static S_ITEMS: Schema =
1131            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 1)
1132                .with_xml_flattened()
1133                .with_xml_name("item");
1134        static S_SCHEMA: Schema = Schema::new_struct(
1135            shape_id!("test", "S"),
1136            ShapeType::Structure,
1137            &[&S_NAME, &S_ITEMS],
1138        );
1139
1140        let mut name = String::new();
1141        let mut items = Vec::new();
1142        deser
1143            .read_struct(&S_SCHEMA, &mut |member, d| {
1144                match member.member_name().unwrap() {
1145                    "name" => name = d.read_string(member)?,
1146                    "items" => d.read_list(member, &mut |d| {
1147                        items.push(d.read_string(member)?);
1148                        Ok(())
1149                    })?,
1150                    _ => {}
1151                }
1152                Ok(())
1153            })
1154            .unwrap();
1155
1156        assert_eq!(name, "hi");
1157        assert_eq!(items, vec!["a", "b"]);
1158    }
1159
1160    #[test]
1161    fn read_struct_flattened_list_intermixed() {
1162        // Flattened list elements intermixed with other members.
1163        let xml = b"<S><item>x</item><name>n</name><item>y</item></S>";
1164        let settings = Arc::new(XmlCodecSettings::default());
1165        let mut deser = XmlDeserializer::new(xml, settings);
1166
1167        static S_NAME: Schema =
1168            Schema::new_member(shape_id!("test", "S$name"), ShapeType::String, "name", 0);
1169        static S_ITEMS: Schema =
1170            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 1)
1171                .with_xml_flattened()
1172                .with_xml_name("item");
1173        static S_SCHEMA: Schema = Schema::new_struct(
1174            shape_id!("test", "S"),
1175            ShapeType::Structure,
1176            &[&S_NAME, &S_ITEMS],
1177        );
1178
1179        let mut name = String::new();
1180        let mut items = Vec::new();
1181        deser
1182            .read_struct(&S_SCHEMA, &mut |member, d| {
1183                match member.member_name().unwrap() {
1184                    "name" => name = d.read_string(member)?,
1185                    "items" => d.read_list(member, &mut |d| {
1186                        items.push(d.read_string(member)?);
1187                        Ok(())
1188                    })?,
1189                    _ => {}
1190                }
1191                Ok(())
1192            })
1193            .unwrap();
1194
1195        assert_eq!(name, "n");
1196        assert_eq!(items, vec!["x", "y"]);
1197    }
1198
1199    // Scalar reads (booleans, ints, floats, blob, timestamp) and document rejection.
1200
1201    #[test]
1202    fn read_scalars() {
1203        let settings = Arc::new(XmlCodecSettings::default());
1204
1205        let mut d = XmlDeserializer::from_text(Cow::Borrowed("true"), settings.clone());
1206        assert!(d.read_boolean(&STRING_MEMBER).unwrap());
1207
1208        let mut d = XmlDeserializer::from_text(Cow::Borrowed("-42"), settings.clone());
1209        assert_eq!(d.read_integer(&STRING_MEMBER).unwrap(), -42);
1210
1211        let mut d = XmlDeserializer::from_text(Cow::Borrowed("NaN"), settings.clone());
1212        assert!(d.read_float(&STRING_MEMBER).unwrap().is_nan());
1213
1214        let mut d = XmlDeserializer::from_text(Cow::Borrowed("Infinity"), settings.clone());
1215        assert_eq!(d.read_double(&STRING_MEMBER).unwrap(), f64::INFINITY);
1216
1217        let mut d = XmlDeserializer::from_text(Cow::Borrowed("aGVsbG8="), settings.clone());
1218        assert_eq!(d.read_blob(&STRING_MEMBER).unwrap().as_ref(), b"hello");
1219
1220        let mut d =
1221            XmlDeserializer::from_text(Cow::Borrowed("2023-04-01T12:00:00Z"), settings.clone());
1222        let ts = d.read_timestamp(&STRING_MEMBER).unwrap();
1223        assert_eq!(ts.secs(), 1680350400);
1224    }
1225
1226    #[test]
1227    fn read_document_returns_error() {
1228        let settings = Arc::new(XmlCodecSettings::default());
1229        let mut deser = XmlDeserializer::from_text(Cow::Borrowed("x"), settings);
1230        assert_eq!(
1231            deser.read_document(&STRING_MEMBER).unwrap_err().to_string(),
1232            "document types are not supported by REST XML"
1233        );
1234    }
1235
1236    // Empty body → error. The XML codec is strict here because XML 1.0
1237    // requires every document to have a root element. Consumers (e.g., S3
1238    // HEAD operations) whose output struct has no body-bound members rely
1239    // on `deserialize_with_response` skipping the body deserializer
1240    // entirely (codegen passes `_deserializer`), so they never reach
1241    // `read_struct`. Operations that DO have body-bound members and
1242    // receive an empty body are responding to a malformed wire format —
1243    // the deserializer surfaces that as an error rather than silently
1244    // returning a default-built struct (which the legacy XML parser also
1245    // did not do).
1246    #[test]
1247    fn read_struct_empty_body_errors() {
1248        let settings = Arc::new(XmlCodecSettings::default());
1249        let mut deser = XmlDeserializer::new(b"", settings);
1250        let err = deser
1251            .read_struct(&PERSON_SCHEMA, &mut |_member, _d| Ok(()))
1252            .expect_err("empty body must be rejected by read_struct");
1253        let _ = format!("{err}");
1254    }
1255
1256    #[test]
1257    fn read_list_empty_body_errors() {
1258        let settings = Arc::new(XmlCodecSettings::default());
1259        let mut deser = XmlDeserializer::new(b"", settings);
1260        deser
1261            .read_list(&PERSON_SCHEMA, &mut |_d| Ok(()))
1262            .expect_err("empty body must be rejected by read_list");
1263    }
1264
1265    #[test]
1266    fn read_map_empty_body_errors() {
1267        let settings = Arc::new(XmlCodecSettings::default());
1268        let mut deser = XmlDeserializer::new(b"", settings);
1269        deser
1270            .read_map(&PERSON_SCHEMA, &mut |_k, _d| Ok(()))
1271            .expect_err("empty body must be rejected by read_map");
1272    }
1273
1274    // Recursion-depth guard tests. These pin the read_struct / read_list /
1275    // read_map paths to a small custom `max_depth` so we can exercise the
1276    // overflow path without constructing pathologically large XML.
1277
1278    /// Lower max_depth to `n` for the test deserializer.
1279    fn settings_with_max_depth(n: u32) -> Arc<XmlCodecSettings> {
1280        Arc::new(XmlCodecSettings::builder().max_depth(n).build())
1281    }
1282
1283    /// Build a `<r>(<r>)*N(value)(</r>)*N` chain `depth` levels deep.
1284    fn nested_struct_xml(depth: u32) -> Vec<u8> {
1285        let mut s = String::new();
1286        for _ in 0..depth {
1287            s.push_str("<r>");
1288        }
1289        s.push('v');
1290        for _ in 0..depth {
1291            s.push_str("</r>");
1292        }
1293        s.into_bytes()
1294    }
1295
1296    #[test]
1297    fn read_struct_rejects_overdeep_payloads() {
1298        // Self-referential schema: `R { r: R }`. Each `read_struct` with the
1299        // same schema increments depth and recurses one level via the
1300        // member dispatch.
1301        static R_MEMBER_SELF: Schema =
1302            Schema::new_member(shape_id!("test", "R$r"), ShapeType::Structure, "r", 0);
1303        static R_SCHEMA: Schema = Schema::new_struct(
1304            shape_id!("test", "R"),
1305            ShapeType::Structure,
1306            &[&R_MEMBER_SELF],
1307        );
1308
1309        // Tighter limit so the test is fast.
1310        let max = 4;
1311        let xml = nested_struct_xml(max + 2);
1312        let mut deser = XmlDeserializer::new(&xml, settings_with_max_depth(max));
1313
1314        // Recursive consumer: each entry into `<r>` calls read_struct again.
1315        fn consume(_m: &Schema, d: &mut dyn ShapeDeserializer) -> Result<(), SerdeError> {
1316            d.read_struct(&R_SCHEMA, &mut consume)
1317        }
1318        let err = deser
1319            .read_struct(&R_SCHEMA, &mut consume)
1320            .expect_err("must reject payload exceeding max_depth");
1321        assert!(
1322            format!("{err}").contains("maximum nesting depth exceeded"),
1323            "expected depth-exceeded error, got: {err}"
1324        );
1325    }
1326
1327    #[test]
1328    fn read_struct_accepts_payloads_up_to_max_depth() {
1329        // Inverse of the above: at exactly `max_depth` we should succeed.
1330        // Walking the chain `max_depth` times consumes `max_depth` enter
1331        // calls (the outermost is the test's own call, inner consumer
1332        // recursions add one each).
1333        static R_MEMBER_SELF: Schema =
1334            Schema::new_member(shape_id!("test", "R$r"), ShapeType::Structure, "r", 0);
1335        static R_SCHEMA: Schema = Schema::new_struct(
1336            shape_id!("test", "R"),
1337            ShapeType::Structure,
1338            &[&R_MEMBER_SELF],
1339        );
1340
1341        // 4 nested `<r>` opens; the consumer recurses for each `<r>` it
1342        // encounters as a child member. At depth=max we stop recursing
1343        // (consumer only recurses when it sees an `<r>` child element).
1344        let max = 4;
1345        let xml = nested_struct_xml(max);
1346        let mut deser = XmlDeserializer::new(&xml, settings_with_max_depth(max));
1347
1348        let mut depth_seen = 0u32;
1349        fn consume(
1350            _m: &Schema,
1351            d: &mut dyn ShapeDeserializer,
1352            depth_seen: &mut u32,
1353        ) -> Result<(), SerdeError> {
1354            *depth_seen += 1;
1355            d.read_struct(&R_SCHEMA, &mut |m, d2| consume(m, d2, depth_seen))
1356        }
1357
1358        deser
1359            .read_struct(&R_SCHEMA, &mut |m, d| consume(m, d, &mut depth_seen))
1360            .expect("payload at exactly max_depth must succeed");
1361    }
1362
1363    #[test]
1364    fn read_list_rejects_overdeep_payloads() {
1365        // Nested lists: `<l><l><l>...</l></l></l>` exceeds max_depth.
1366        static L_MEMBER: Schema =
1367            Schema::new_member(shape_id!("test", "L$member"), ShapeType::List, "member", 0);
1368        static L_SCHEMA: Schema = Schema::new_list(shape_id!("test", "L"), &L_MEMBER);
1369
1370        let max = 3;
1371        // 5 levels of `<l>` — exceeds 3.
1372        let xml = b"<l><l><l><l><l/></l></l></l></l>";
1373        let mut deser = XmlDeserializer::new(xml, settings_with_max_depth(max));
1374
1375        fn consume(d: &mut dyn ShapeDeserializer) -> Result<(), SerdeError> {
1376            d.read_list(&L_SCHEMA, &mut consume)
1377        }
1378        let err = deser
1379            .read_list(&L_SCHEMA, &mut consume)
1380            .expect_err("nested-list payload exceeding max_depth must error");
1381        assert!(
1382            format!("{err}").contains("maximum nesting depth exceeded"),
1383            "expected depth-exceeded error, got: {err}"
1384        );
1385    }
1386
1387    #[test]
1388    fn depth_resets_between_sibling_reads() {
1389        // After a successful aggregate read, the depth counter must return
1390        // to its prior value so the next sibling read isn't poisoned.
1391        static R_MEMBER_SELF: Schema =
1392            Schema::new_member(shape_id!("test", "R$r"), ShapeType::Structure, "r", 0);
1393        static R_SCHEMA: Schema = Schema::new_struct(
1394            shape_id!("test", "R"),
1395            ShapeType::Structure,
1396            &[&R_MEMBER_SELF],
1397        );
1398
1399        let max = 4;
1400        let xml = nested_struct_xml(2);
1401        let mut deser = XmlDeserializer::new(&xml, settings_with_max_depth(max));
1402
1403        // First read: 2 levels deep, well under max.
1404        deser
1405            .read_struct(&R_SCHEMA, &mut |_m, d| {
1406                d.read_struct(&R_SCHEMA, &mut |_, _| Ok(()))
1407            })
1408            .expect("first read at depth=2 must succeed");
1409
1410        // Second read on the same deserializer with the same payload:
1411        // would fail if depth had leaked from the first call.
1412        deser
1413            .read_struct(&R_SCHEMA, &mut |_m, d| {
1414                d.read_struct(&R_SCHEMA, &mut |_, _| Ok(()))
1415            })
1416            .expect("sibling read on the same deserializer must succeed");
1417    }
1418
1419    #[test]
1420    fn depth_resets_after_consumer_error() {
1421        // Regression: prior to the IIFE refactor in `read_struct` /
1422        // `read_list` / `read_map` / the 5 collection helpers, a `?`
1423        // propagating out of an aggregate read body skipped the trailing
1424        // `self.leave_aggregate()`, leaking +1 on the depth counter. A
1425        // second read on the same deserializer would then fail with
1426        // "maximum nesting depth exceeded" on a payload well within
1427        // limits.
1428        //
1429        // We force the error via a consumer that always returns Err
1430        // (rather than relying on a malformed XML payload — which would
1431        // also fail on the second read for the same wire-level reason
1432        // and so wouldn't isolate the depth-counter bug).
1433        static L_MEMBER: Schema = Schema::new_member(
1434            shape_id!("test", "L$member"),
1435            ShapeType::String,
1436            "member",
1437            0,
1438        );
1439        static L_SCHEMA: Schema = Schema::new_list(shape_id!("test", "L"), &L_MEMBER);
1440
1441        // max_depth=2 means we can enter at most 2 levels of aggregates
1442        // before erroring. With the leak, after one errored aggregate
1443        // read at depth=1, depth stays at 1, and a second read enters
1444        // at depth=2 — still OK. After two errored reads we'd be at
1445        // depth=2, and a third would push to depth=3 and trip the limit.
1446        let max = 2;
1447        let xml = b"<l><member>a</member><member>b</member></l>";
1448        let mut deser = XmlDeserializer::new(xml, settings_with_max_depth(max));
1449
1450        // Force an error from the consumer on the very first element.
1451        let mut force_err = |_: &mut dyn ShapeDeserializer| -> Result<(), SerdeError> {
1452            Err(SerdeError::custom("forced"))
1453        };
1454        deser
1455            .read_list(&L_SCHEMA, &mut force_err)
1456            .expect_err("forced consumer error must propagate");
1457
1458        // Repeat enough times that, with a leak of +1 per call, the depth
1459        // counter would exceed max_depth. With the fix, every iteration
1460        // ends with depth=0 and this loop is fine.
1461        for i in 0..(max as usize + 5) {
1462            deser
1463                .read_list(&L_SCHEMA, &mut force_err)
1464                .expect_err(&format!("forced consumer error must propagate (iter {i})"));
1465        }
1466
1467        // A successful (no-op) read must still work — proving the counter
1468        // was decremented correctly through all the error iterations.
1469        let mut count = 0usize;
1470        deser
1471            .read_list(&L_SCHEMA, &mut |d| {
1472                count += 1;
1473                d.read_string(&L_MEMBER).map(|_| ())
1474            })
1475            .expect("subsequent successful read must not be poisoned by prior errors");
1476        assert_eq!(count, 2);
1477    }
1478
1479    #[test]
1480    fn read_struct_unwrapped_output_with_prolog() {
1481        // Regression: in the unwrapped-output path of `read_struct`, an
1482        // earlier version called `find_element_slice` with a heap-allocated
1483        // `String` instead of a `&str` borrowing from `input`. The
1484        // pointer-arithmetic invariant broke; only the
1485        // `.saturating_sub.min` clamping prevented UB. The result was
1486        // silently correct only when the target element happened to start
1487        // at offset 0 of the input.
1488        //
1489        // Constructing a payload with an XML prolog (so the element is NOT
1490        // at offset 0) verifies the fixed code passes a real sub-slice of
1491        // `input` to `find_element_slice`. The `debug_assert!` in
1492        // `find_element_slice` would also fire under the old code in
1493        // debug builds.
1494        static MEMBER: Schema = Schema::new_member(
1495            shape_id!("test", "U$location"),
1496            ShapeType::String,
1497            "LocationConstraint",
1498            0,
1499        );
1500        static SCHEMA: Schema =
1501            Schema::new_struct(shape_id!("test", "U"), ShapeType::Structure, &[&MEMBER])
1502                .with_xml_unwrapped_output();
1503
1504        let xml = b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<LocationConstraint>us-west-2</LocationConstraint>";
1505        let mut deser = XmlDeserializer::new(xml, Arc::new(XmlCodecSettings::default()));
1506
1507        let mut got = String::new();
1508        deser
1509            .read_struct(&SCHEMA, &mut |member, d| {
1510                got = d.read_string(member)?;
1511                Ok(())
1512            })
1513            .expect("unwrapped output must deserialize through prolog");
1514        assert_eq!(got, "us-west-2");
1515    }
1516
1517    /// Regression test for a UTF-8 char-boundary panic in `find_element_slice`.
1518    ///
1519    /// Found via `schema_xml_roundtrip` fuzz target on input
1520    /// `StringStringMap([("Б", "")])`. The serialized payload contains a
1521    /// Cyrillic `Б` (UTF-8 bytes `0xD0 0x91`) inside a map-key element. The
1522    /// previous implementation operated on `&str` and advanced its scan
1523    /// cursor by one byte per non-`<` character, which landed mid-char on
1524    /// the second byte of `Б` and panicked with
1525    /// `start byte index N is not a char boundary`. The fix moves all
1526    /// scanning to byte slices since `<`, `>`, `/`, and `?` are single-byte
1527    /// ASCII and the multi-byte content is opaque to the search.
1528    #[test]
1529    fn find_element_slice_handles_multibyte_utf8() {
1530        // Build a struct-with-map XML payload containing Cyrillic text in a
1531        // map-key element. The exact wire form matches what `XmlSerializer`
1532        // emits for `StringStringMap([("Б", "")])` wrapped in
1533        // `WRAPPER_STRING_STRING_MAP_SCHEMA`.
1534        static KEY_MEMBER: Schema =
1535            Schema::new_member(shape_id!("test", "Wrapper$k"), ShapeType::Map, "entries", 0);
1536        static MAP_SCHEMA: Schema = Schema::new_map(
1537            shape_id!("test", "MyMap"),
1538            &aws_smithy_schema::prelude::STRING,
1539            &aws_smithy_schema::prelude::STRING,
1540        );
1541        static WRAPPER: Schema = Schema::new_struct(
1542            shape_id!("test", "Wrapper"),
1543            ShapeType::Structure,
1544            &[&KEY_MEMBER],
1545        );
1546        let _ = MAP_SCHEMA; // referenced for documentation; not directly used
1547
1548        let xml =
1549            "<Wrapper><entries><entry><key>Б</key><value></value></entry></entries></Wrapper>";
1550
1551        let settings = Arc::new(XmlCodecSettings::default());
1552        let mut deser = XmlDeserializer::new(xml.as_bytes(), settings);
1553
1554        let mut entries = Vec::new();
1555        deser
1556            .read_struct(&WRAPPER, &mut |member, d| {
1557                d.read_map(member, &mut |k, d| {
1558                    entries.push((k, d.read_string(&aws_smithy_schema::prelude::STRING)?));
1559                    Ok(())
1560                })
1561            })
1562            .expect("must not panic on multi-byte UTF-8 inside map elements");
1563
1564        assert_eq!(entries, vec![("Б".to_owned(), String::new())]);
1565    }
1566
1567    #[test]
1568    fn find_depth2_by_predicate_wrapped() {
1569        let xml = b"<Resp><FooResult><A>1</A></FooResult><Metadata/></Resp>";
1570        let got = find_depth2_element_slice_by(xml, |n| n.ends_with("Result"));
1571        assert_eq!(got, Some(&b"<FooResult><A>1</A></FooResult>"[..]));
1572    }
1573
1574    #[test]
1575    fn find_depth2_by_predicate_self_closing() {
1576        // Regression: a self-closing target element must return just `<Foo/>`,
1577        // not everything from the element to the end of the document.
1578        let xml = b"<Resp><FooResult/><Metadata><Id>r</Id></Metadata></Resp>";
1579        let got = find_depth2_element_slice_by(xml, |n| n.ends_with("Result"));
1580        assert_eq!(got, Some(&b"<FooResult/>"[..]));
1581    }
1582
1583    #[test]
1584    fn find_depth2_by_predicate_root_match() {
1585        // Unwrapped envelope: the root itself matches — return the whole body.
1586        let xml = b"<Error><Code>Boom</Code></Error>";
1587        let got = find_depth2_element_slice_by(xml, |n| n == "Error");
1588        assert_eq!(got, Some(&xml[..]));
1589    }
1590
1591    #[test]
1592    fn find_depth2_by_predicate_no_match() {
1593        let xml = b"<Resp><Metadata/></Resp>";
1594        assert_eq!(find_depth2_element_slice_by(xml, |n| n == "Error"), None);
1595    }
1596
1597    #[test]
1598    fn find_depth2_by_predicate_invalid_xml() {
1599        assert_eq!(find_depth2_element_slice_by(b"not xml", |_| true), None);
1600    }
1601
1602    // -------- Specialized collection-helper override tests --------
1603    //
1604    // These exercise the inlined `read_*_list` / `read_string_string_map`
1605    // overrides on `XmlDeserializer`, confirming behavioral parity with
1606    // the trait's default-impl path (which goes through `read_list` /
1607    // `read_map` + `&mut dyn ShapeDeserializer`).
1608
1609    #[test]
1610    fn read_string_list_helper() {
1611        let xml = b"<items><member>a</member><member>b</member><member></member></items>";
1612        let settings = Arc::new(XmlCodecSettings::default());
1613        let mut deser = XmlDeserializer::new(xml, settings);
1614
1615        static LIST_MEMBER: Schema = Schema::new_member(
1616            shape_id!("test", "L$member"),
1617            ShapeType::String,
1618            "member",
1619            0,
1620        );
1621        static LIST_SCHEMA: Schema = Schema::new_list(shape_id!("test", "L"), &LIST_MEMBER);
1622
1623        let out = deser.read_string_list(&LIST_SCHEMA).unwrap();
1624        assert_eq!(out, vec!["a".to_owned(), "b".to_owned(), String::new()]);
1625    }
1626
1627    #[test]
1628    fn read_string_list_helper_renamed_member_name() {
1629        // Codegen-emitted call site for a list whose member shape has
1630        // `@xmlName("Item")`. Like `read_list`, the helper does not
1631        // validate child element names against the schema — it accepts
1632        // whatever the wire form provides. This matches the default impl.
1633        let xml = b"<items><Item>x</Item><Item>y</Item></items>";
1634        let settings = Arc::new(XmlCodecSettings::default());
1635        let mut deser = XmlDeserializer::new(xml, settings);
1636
1637        static LIST_MEMBER: Schema = Schema::new_member(
1638            shape_id!("test", "L$member"),
1639            ShapeType::String,
1640            "member",
1641            0,
1642        );
1643        static LIST_SCHEMA: Schema = Schema::new_list(shape_id!("test", "L"), &LIST_MEMBER);
1644
1645        let out = deser.read_string_list(&LIST_SCHEMA).unwrap();
1646        assert_eq!(out, vec!["x".to_owned(), "y".to_owned()]);
1647    }
1648
1649    #[test]
1650    fn read_blob_list_helper() {
1651        // Each element's text is base64-decoded into a Blob.
1652        let xml = b"<blobs><member>aGVsbG8=</member><member>d29ybGQ=</member></blobs>";
1653        let settings = Arc::new(XmlCodecSettings::default());
1654        let mut deser = XmlDeserializer::new(xml, settings);
1655
1656        static LIST_MEMBER: Schema =
1657            Schema::new_member(shape_id!("test", "B$member"), ShapeType::Blob, "member", 0);
1658        static LIST_SCHEMA: Schema = Schema::new_list(shape_id!("test", "B"), &LIST_MEMBER);
1659
1660        let out = deser.read_blob_list(&LIST_SCHEMA).unwrap();
1661        assert_eq!(out.len(), 2);
1662        assert_eq!(out[0].as_ref(), b"hello");
1663        assert_eq!(out[1].as_ref(), b"world");
1664    }
1665
1666    #[test]
1667    fn read_blob_list_helper_rejects_invalid_base64() {
1668        let xml = b"<blobs><member>!!!not-base64!!!</member></blobs>";
1669        let settings = Arc::new(XmlCodecSettings::default());
1670        let mut deser = XmlDeserializer::new(xml, settings);
1671
1672        static LIST_MEMBER: Schema =
1673            Schema::new_member(shape_id!("test", "B$member"), ShapeType::Blob, "member", 0);
1674        static LIST_SCHEMA: Schema = Schema::new_list(shape_id!("test", "B"), &LIST_MEMBER);
1675
1676        let err = deser.read_blob_list(&LIST_SCHEMA).unwrap_err();
1677        assert!(format!("{err}").contains("base64"));
1678    }
1679
1680    #[test]
1681    fn read_integer_list_helper() {
1682        let xml = b"<nums><member>1</member><member>-42</member><member>0</member></nums>";
1683        let settings = Arc::new(XmlCodecSettings::default());
1684        let mut deser = XmlDeserializer::new(xml, settings);
1685
1686        static LIST_MEMBER: Schema = Schema::new_member(
1687            shape_id!("test", "I$member"),
1688            ShapeType::Integer,
1689            "member",
1690            0,
1691        );
1692        static LIST_SCHEMA: Schema = Schema::new_list(shape_id!("test", "I"), &LIST_MEMBER);
1693
1694        let out = deser.read_integer_list(&LIST_SCHEMA).unwrap();
1695        assert_eq!(out, vec![1i32, -42, 0]);
1696    }
1697
1698    #[test]
1699    fn read_long_list_helper() {
1700        let xml = b"<nums><member>9223372036854775807</member><member>-1</member></nums>";
1701        let settings = Arc::new(XmlCodecSettings::default());
1702        let mut deser = XmlDeserializer::new(xml, settings);
1703
1704        static LIST_MEMBER: Schema =
1705            Schema::new_member(shape_id!("test", "Lo$member"), ShapeType::Long, "member", 0);
1706        static LIST_SCHEMA: Schema = Schema::new_list(shape_id!("test", "Lo"), &LIST_MEMBER);
1707
1708        let out = deser.read_long_list(&LIST_SCHEMA).unwrap();
1709        assert_eq!(out, vec![i64::MAX, -1]);
1710    }
1711
1712    #[test]
1713    fn read_string_string_map_helper() {
1714        let xml = b"<m><entry><key>a</key><value>1</value></entry>\
1715                    <entry><key>b</key><value>2</value></entry></m>";
1716        let settings = Arc::new(XmlCodecSettings::default());
1717        let mut deser = XmlDeserializer::new(xml, settings);
1718
1719        static MAP_KEY: Schema =
1720            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1721        static MAP_VALUE: Schema =
1722            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 0);
1723        static MAP_SCHEMA: Schema = Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
1724
1725        let out = deser.read_string_string_map(&MAP_SCHEMA).unwrap();
1726        assert_eq!(out.len(), 2);
1727        assert_eq!(out.get("a").map(String::as_str), Some("1"));
1728        assert_eq!(out.get("b").map(String::as_str), Some("2"));
1729    }
1730
1731    #[test]
1732    fn read_string_string_map_helper_with_renamed_key_value() {
1733        // @xmlName overrides on the map's key / value members — the
1734        // helper resolves these from the schema, mirroring `read_map`.
1735        let xml = b"<m><entry><K>a</K><V>1</V></entry></m>";
1736        let settings = Arc::new(XmlCodecSettings::default());
1737        let mut deser = XmlDeserializer::new(xml, settings);
1738
1739        static MAP_KEY: Schema =
1740            Schema::new_member(shape_id!("test", "M2$key"), ShapeType::String, "key", 0)
1741                .with_xml_name("K");
1742        static MAP_VALUE: Schema =
1743            Schema::new_member(shape_id!("test", "M2$value"), ShapeType::String, "value", 0)
1744                .with_xml_name("V");
1745        static MAP_SCHEMA: Schema = Schema::new_map(shape_id!("test", "M2"), &MAP_KEY, &MAP_VALUE);
1746
1747        let out = deser.read_string_string_map(&MAP_SCHEMA).unwrap();
1748        assert_eq!(out.get("a").map(String::as_str), Some("1"));
1749    }
1750
1751    // Regression tests adapted from review comments on PR #4668 against an
1752    // earlier schema-XML deserializer that reconstructed sub-trees with
1753    // `try_data` + `format!("<{}>{}</{}>", ...)`. That pattern broke (a) on
1754    // structs with element children >1 level deep (because `try_data` errors
1755    // on a non-text token) and (b) on text containing `&` or `<` (because
1756    // unescape ran before re-emitting into fabricated tags, producing
1757    // invalid XML on re-parse). Our deserializer propagates raw byte slices
1758    // for aggregate sub-trees via `find_element_slice` and `dispatch_subslice`,
1759    // so neither bug should reproduce — these tests lock that in.
1760    #[test]
1761    fn nested_struct_three_levels_deep() {
1762        static LEAF: Schema =
1763            Schema::new_member(shape_id!("t", "Inner"), ShapeType::String, "Leaf", 0);
1764        static INNER_SCHEMA: Schema =
1765            Schema::new_struct(shape_id!("t", "Inner"), ShapeType::Structure, &[&LEAF]);
1766        static INNER_MEMBER: Schema =
1767            Schema::new_member(shape_id!("t", "Middle"), ShapeType::Structure, "Inner", 0);
1768        static MIDDLE_SCHEMA: Schema = Schema::new_struct(
1769            shape_id!("t", "Middle"),
1770            ShapeType::Structure,
1771            &[&INNER_MEMBER],
1772        );
1773        static MIDDLE_MEMBER: Schema =
1774            Schema::new_member(shape_id!("t", "Outer"), ShapeType::Structure, "Middle", 0);
1775        static OUTER_SCHEMA: Schema = Schema::new_struct(
1776            shape_id!("t", "Outer"),
1777            ShapeType::Structure,
1778            &[&MIDDLE_MEMBER],
1779        );
1780        static OUTER_MEMBER: Schema =
1781            Schema::new_member(shape_id!("t", "Root"), ShapeType::Structure, "Outer", 0);
1782        static ROOT: Schema = Schema::new_struct(
1783            shape_id!("t", "Root"),
1784            ShapeType::Structure,
1785            &[&OUTER_MEMBER],
1786        );
1787
1788        let xml = b"<Root><Outer><Middle><Inner><Leaf>value</Leaf></Inner></Middle></Outer></Root>";
1789        let settings = Arc::new(XmlCodecSettings::default());
1790        let mut deser = XmlDeserializer::new(xml, settings);
1791        let mut leaf = String::new();
1792        deser
1793            .read_struct(&ROOT, &mut |outer_m, d_outer| {
1794                assert_eq!(outer_m.member_name(), Some("Outer"));
1795                d_outer.read_struct(&OUTER_SCHEMA, &mut |middle_m, d_middle| {
1796                    assert_eq!(middle_m.member_name(), Some("Middle"));
1797                    d_middle.read_struct(&MIDDLE_SCHEMA, &mut |inner_m, d_inner| {
1798                        assert_eq!(inner_m.member_name(), Some("Inner"));
1799                        d_inner.read_struct(&INNER_SCHEMA, &mut |leaf_m, d_leaf| {
1800                            if leaf_m.member_name() == Some("Leaf") {
1801                                leaf = d_leaf.read_string(leaf_m)?;
1802                            }
1803                            Ok(())
1804                        })
1805                    })
1806                })
1807            })
1808            .expect("3-level nested struct should round-trip");
1809        assert_eq!(leaf, "value");
1810    }
1811
1812    #[test]
1813    fn struct_member_with_escaped_text_round_trips() {
1814        static VALUE: Schema =
1815            Schema::new_member(shape_id!("t", "Body"), ShapeType::String, "value", 0);
1816        static BODY_SCHEMA: Schema =
1817            Schema::new_struct(shape_id!("t", "Body"), ShapeType::Structure, &[&VALUE]);
1818        static PAYLOAD_MEMBER: Schema = Schema::new_member(
1819            shape_id!("t", "Envelope"),
1820            ShapeType::Structure,
1821            "payload",
1822            0,
1823        );
1824        static ENVELOPE_SCHEMA: Schema = Schema::new_struct(
1825            shape_id!("t", "Envelope"),
1826            ShapeType::Structure,
1827            &[&PAYLOAD_MEMBER],
1828        );
1829
1830        // Server response with an XML-escaped `&` in the leaf value.
1831        // After `unescape`, the original value is `foo&bar`. The PR's
1832        // deserializer would re-emit the unescaped text into reconstructed
1833        // markup, producing invalid XML and an error or wrong value.
1834        let xml = b"<Envelope><payload><value>foo&amp;bar</value></payload></Envelope>";
1835        let settings = Arc::new(XmlCodecSettings::default());
1836        let mut deser = XmlDeserializer::new(xml, settings);
1837        let mut value_str = String::new();
1838        deser
1839            .read_struct(&ENVELOPE_SCHEMA, &mut |payload_m, d_payload| {
1840                let _ = payload_m;
1841                d_payload.read_struct(&BODY_SCHEMA, &mut |inner_m, d_inner| {
1842                    if inner_m.member_name() == Some("value") {
1843                        value_str = d_inner.read_string(inner_m)?;
1844                    }
1845                    Ok(())
1846                })
1847            })
1848            .expect("escaped text inside a nested struct should round-trip");
1849        assert_eq!(value_str, "foo&bar");
1850    }
1851
1852    #[test]
1853    fn read_map_preserves_empty_string_key() {
1854        // The PR's deserializer had a guard that dropped entries with empty
1855        // keys. Empty string is a valid map key per Smithy semantics.
1856        static KEY: Schema =
1857            Schema::new_member(shape_id!("t", "M$key"), ShapeType::String, "key", 0);
1858        static VALUE: Schema =
1859            Schema::new_member(shape_id!("t", "M$value"), ShapeType::String, "value", 1);
1860        static MAP: Schema = Schema::new_map(shape_id!("t", "M"), &KEY, &VALUE);
1861
1862        let xml = b"<Root><entry><key></key><value>v1</value></entry></Root>";
1863        let settings = Arc::new(XmlCodecSettings::default());
1864        let mut deser = XmlDeserializer::new(xml, settings);
1865
1866        let mut got: std::collections::HashMap<String, String> = Default::default();
1867        deser
1868            .read_map(&MAP, &mut |k, d| {
1869                let v = d.read_string(&VALUE)?;
1870                got.insert(k, v);
1871                Ok(())
1872            })
1873            .expect("empty-key entry should be preserved");
1874        assert_eq!(got.get("").map(String::as_str), Some("v1"));
1875    }
1876}