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};
14
15use aws_smithy_types::Document as SmithyDocument;
16use std::borrow::Cow;
17use std::sync::Arc;
18
19/// Maximum recursion depth for deserialization. Payloads nested deeper than
20/// this will produce a [`SerdeError`] instead of risking a stack overflow.
21/// Matches the default used by the JSON and CBOR codecs.
22pub(crate) const MAX_DESERIALIZE_DEPTH: u32 = 128;
23
24/// XML deserializer that implements the `ShapeDeserializer` trait.
25///
26/// Wraps the existing `aws_smithy_xml::decode` SAX-like API and provides
27/// schema-driven dispatch for struct members, lists, and maps.
28///
29/// The deserializer holds the input as `&'a [u8]` throughout. For aggregate
30/// reads (`read_struct`, `read_list`, `read_map`) we construct a fresh
31/// `Document` over `input` on demand. For scalar reads we either parse
32/// `input` to extract the root element's text, or — when a parent
33/// aggregate has already extracted the leaf text from its child element —
34/// take the pre-extracted text directly via `text`.
35///
36/// Sibling dispatch within an aggregate read avoids per-iteration
37/// `XmlDeserializer` construction by reusing `&mut self` through
38/// `dispatch_subslice` / `dispatch_text` helpers, which save and restore
39/// the relevant state across the closure call.
40pub struct XmlDeserializer<'a> {
41    /// XML bytes for this deserializer. For the document root and for
42    /// aggregate sub-deserializers this is the slice of the parent input
43    /// covering the relevant element. Ignored when `text` is set.
44    input: &'a [u8],
45    /// Pre-extracted leaf text, including any extraction error. Scalar reads
46    /// consume this result; a consumer that skips the value also skips errors
47    /// about its leaf shape. Aggregate reads error when this is present.
48    text: Option<Result<Cow<'a, str>, SerdeError>>,
49    settings: Arc<XmlCodecSettings>,
50    /// Aggregate nesting depth. Incremented at the top of each
51    /// `read_struct` / `read_list` / `read_map` and decremented before
52    /// they return so sibling reads on the same deserializer don't
53    /// accumulate. Compared against [`XmlCodecSettings::max_depth`] to
54    /// reject deeply-nested payloads before they exhaust the stack.
55    depth: u32,
56}
57
58impl<'a> XmlDeserializer<'a> {
59    /// Creates a new XML deserializer over raw bytes.
60    pub(crate) fn new(input: &'a [u8], settings: Arc<XmlCodecSettings>) -> Self {
61        Self {
62            input,
63            text: None,
64            settings,
65            depth: 0,
66        }
67    }
68
69    /// Spawn a deserializer for owned sub-content that cannot be reached via
70    /// [`dispatch_subslice`](Self::dispatch_subslice) — e.g. the synthesized
71    /// `<__flat>` wrapper around a flattened-aggregate group, whose buffer is
72    /// owned locally and so has a shorter lifetime than `'a`.
73    ///
74    /// Inherits the parent's [`depth`](Self::depth) so the recursion-depth
75    /// guard stays continuous across the boundary, exactly as it does for the
76    /// non-flattened path (which reuses `self` via `dispatch_subslice`).
77    /// Using [`new`](Self::new) here instead would reset `depth` to 0 and let
78    /// a shape that recurses through a flattened member (e.g.
79    /// `structure Node { @xmlFlattened kids: NodeList }`) nest without bound,
80    /// overflowing the stack.
81    fn new_child<'b>(&self, input: &'b [u8]) -> XmlDeserializer<'b> {
82        XmlDeserializer {
83            input,
84            text: None,
85            settings: self.settings.clone(),
86            depth: self.depth,
87        }
88    }
89
90    /// Creates a deserializer pre-loaded with leaf text content. Used by
91    /// tests; runtime dispatch uses [`dispatch_text`](Self::dispatch_text)
92    /// to repoint an existing deserializer at leaf text rather than
93    /// constructing a new instance.
94    #[cfg(test)]
95    fn from_text(text: Cow<'a, str>, settings: Arc<XmlCodecSettings>) -> Self {
96        Self {
97            input: b"",
98            text: Some(Ok(text)),
99            settings,
100            depth: 0,
101        }
102    }
103
104    /// Increment the recursion-depth counter and return an error if the
105    /// configured maximum would be exceeded. Caller is responsible for
106    /// decrementing on the way out (see [`Self::leave_aggregate`]).
107    ///
108    /// Order matters: the depth bound is checked *before* the increment so
109    /// the error path doesn't leave the counter incremented. Together with
110    /// the IIFE pattern around each aggregate body (which guarantees
111    /// `leave_aggregate` always runs after a successful `enter_aggregate`),
112    /// this keeps `depth` consistent across `?` propagation.
113    fn enter_aggregate(&mut self) -> Result<(), SerdeError> {
114        if self.depth >= self.settings.max_depth() {
115            return Err(SerdeError::custom("maximum nesting depth exceeded"));
116        }
117        self.depth += 1;
118        Ok(())
119    }
120
121    /// Decrement the recursion-depth counter. Pair with each successful
122    /// [`Self::enter_aggregate`] call.
123    fn leave_aggregate(&mut self) {
124        debug_assert!(self.depth > 0, "leave_aggregate without enter_aggregate");
125        self.depth = self.depth.saturating_sub(1);
126    }
127
128    /// Construct a fresh `Document` over `self.input`. Errors if the
129    /// deserializer holds pre-extracted text (an aggregate read was
130    /// expected on this deserializer).
131    fn document(&self) -> Result<Document<'a>, SerdeError> {
132        if self.text.is_some() {
133            return Err(SerdeError::custom("expected XML element, found text"));
134        }
135        let mut doc = Document::try_from(self.input).unwrap_or_else(|_| Document::new(""));
136        if self.settings.validate_document && self.depth == 1 {
137            doc.check_well_formedness();
138        }
139        Ok(doc)
140    }
141
142    /// Extract the leaf text content. If `text` was pre-set, returns it
143    /// directly; otherwise parses `input`, navigates to the root element,
144    /// and reads its text content.
145    fn take_text(&mut self) -> Result<Cow<'a, str>, SerdeError> {
146        if let Some(t) = self.text.take() {
147            return t;
148        }
149        let mut doc = Document::try_from(self.input).unwrap_or_else(|_| Document::new(""));
150        let top_level_strict = self.settings.validate_document && self.depth == 0;
151        if top_level_strict {
152            doc.check_well_formedness();
153        }
154        let mut root = doc
155            .root_element()
156            .map_err(|e| SerdeError::custom(e.to_string()))?;
157        let text = decode::try_data(&mut root).map_err(|e| SerdeError::custom(e.to_string()))?;
158        drop(root);
159        if top_level_strict {
160            Self::finish_well_formed(&mut doc)?;
161        }
162        Ok(text)
163    }
164
165    /// Run `f` against `self` after temporarily repointing it at a sub-slice
166    /// of the parent input. State (input, text) is saved on entry and
167    /// restored on return so the deserializer can be reused for sibling
168    /// dispatches without per-iteration allocation.
169    fn dispatch_subslice<R>(&mut self, sub: &'a [u8], f: impl FnOnce(&mut Self) -> R) -> R {
170        let saved_input = std::mem::replace(&mut self.input, sub);
171        let saved_text = self.text.take();
172        let r = f(self);
173        self.input = saved_input;
174        self.text = saved_text;
175        r
176    }
177
178    /// Run `f` against `self` after temporarily setting a leaf-text result.
179    /// Extraction errors surface only when the consumer reads the value.
180    /// State is restored on return.
181    fn dispatch_text<R>(
182        &mut self,
183        text: Result<Cow<'a, str>, SerdeError>,
184        f: impl FnOnce(&mut Self) -> R,
185    ) -> R {
186        let saved_input = std::mem::replace(&mut self.input, b"");
187        let saved_text = self.text.replace(text);
188        let r = f(self);
189        self.input = saved_input;
190        self.text = saved_text;
191        r
192    }
193
194    /// Resolve a child element name to a member schema by matching against
195    /// @xmlName (if present) or member_name. Returns the first matching member, so
196    /// a name shared by several members always resolves to the same one.
197    fn resolve_member<'s>(schema: &'s Schema<'s>, element_name: &str) -> Option<&'s Schema<'s>> {
198        schema.members().iter().copied().find(|m| {
199            if let Some(xml_name) = m.xml_name() {
200                xml_name.value() == element_name
201            } else {
202                m.member_name() == Some(element_name)
203            }
204        })
205    }
206
207    /// The element name the items of a wrapped list must carry when the codec
208    /// checks collection element names (`check_names`, the
209    /// `strict_collection_element_names` setting): `@xmlName` on the list's
210    /// member, else the member's name (`member`).
211    ///
212    /// `None` means every child element is an item. That is the case when the
213    /// setting is off; for a flattened list, whose siblings `read_struct` has
214    /// already selected by name before handing them over; and for a schema
215    /// that does not describe the list's member, such as the
216    /// `prelude::DOCUMENT` placeholder codegen falls back to when it has no
217    /// sub-schema for a nested aggregate, where the name is not known.
218    fn list_item_name<'s>(check_names: bool, schema: &'s Schema<'_>) -> Option<&'s str> {
219        if !check_names || schema.xml_flattened() {
220            return None;
221        }
222        let member = schema.member()?;
223        Some(
224            member
225                .xml_name()
226                .map(|t| t.value())
227                .or(member.member_name())
228                .unwrap_or("member"),
229        )
230    }
231
232    /// Whether `el` is an item of a list whose items are named `item_name`
233    /// (see [`list_item_name`](Self::list_item_name)). Other elements are
234    /// skipped, as unknown structure members are.
235    fn is_list_item(item_name: Option<&str>, el: &decode::StartEl<'_>) -> bool {
236        item_name.is_none_or(|name| el.matches(name))
237    }
238
239    /// Whether `el` is an entry of the map `schema` describes. With
240    /// `check_names` (the `strict_collection_element_names` setting) only the
241    /// `entry` children of a wrapped map are; without it every child is. The
242    /// siblings of a flattened map were selected by name in `read_struct`, so
243    /// each of them is an entry.
244    fn is_map_entry(check_names: bool, schema: &Schema<'_>, el: &decode::StartEl<'_>) -> bool {
245        !check_names || schema.xml_flattened() || el.matches("entry")
246    }
247
248    /// Byte offset of the `<` that opens the element whose local name
249    /// `el_local` borrows from `input` (xmlparser hands out names as borrows
250    /// into the document). Only the element's own `prefix:` sits between that
251    /// `<` and the name, so a short backwards search finds it.
252    ///
253    /// The matching end offset is *not* computed here: callers take it from
254    /// [`ScopedDecoder::end_offset`](decode::ScopedDecoder::end_offset), which
255    /// runs the tokenizer through the matching close tag. Taking both ends from the
256    /// tokenizer means comments, CDATA, processing instructions and `>` in
257    /// attribute values are handled exactly as the parser sees them, and each
258    /// input byte belongs to at most one sibling slice. An earlier byte
259    /// scanner disagreed with the tokenizer on such input, fell back to "rest
260    /// of document" for every sibling, and made flattened lists quadratic in
261    /// memory and CPU.
262    fn element_start(input: &[u8], el_local: &str) -> usize {
263        let input_start = input.as_ptr() as usize;
264        let name_ptr = el_local.as_ptr() as usize;
265        debug_assert!(
266            name_ptr >= input_start && name_ptr + el_local.len() <= input_start + input.len(),
267            "element_start: el_local must point into input"
268        );
269        let name_offset = name_ptr.saturating_sub(input_start).min(input.len());
270        input[..name_offset]
271            .iter()
272            .rposition(|&b| b == b'<')
273            .unwrap_or(0)
274    }
275
276    /// Surface any well-formedness violation recorded by a document in
277    /// checking mode (see [`Document::check_well_formedness`]).
278    fn finish_well_formed(doc: &mut Document<'_>) -> Result<(), SerdeError> {
279        doc.finish_well_formed()
280            .map_err(|e| SerdeError::invalid_input(format!("ill-formed XML: {e}")))
281    }
282
283    /// Only the outer aggregate owns the document boundary. Its tokenizer also
284    /// drains skipped children, so nested reads do not need separate validation.
285    fn finish_document(&self, doc: &mut Document<'_>) -> Result<(), SerdeError> {
286        if self.settings.validate_document && self.depth == 1 {
287            Self::finish_well_formed(doc)?;
288        }
289        Ok(())
290    }
291
292    fn resolve_timestamp_format(&self, schema: &Schema<'_>) -> Result<TimestampFormat, SerdeError> {
293        let Some(t) = schema.timestamp_format() else {
294            return Ok(match self.settings.default_timestamp_format() {
295                TimestampFormat::DateTime => TimestampFormat::DateTimeWithOffset,
296                other => other,
297            });
298        };
299        Ok(match t.format() {
300            aws_smithy_schema::traits::TimestampFormat::EpochSeconds => {
301                TimestampFormat::EpochSeconds
302            }
303            // Use the lenient `DateTimeWithOffset` so timezone-suffixed
304            // RFC-3339 strings (e.g. `2019-12-17T00:48:18+01:00`) parse —
305            // matches the Smithy `date-time` protocol-test expectations
306            // and the JSON codec's behavior.
307            aws_smithy_schema::traits::TimestampFormat::DateTime => {
308                TimestampFormat::DateTimeWithOffset
309            }
310            aws_smithy_schema::traits::TimestampFormat::HttpDate => TimestampFormat::HttpDate,
311            other => {
312                return Err(SerdeError::unsupported(format!(
313                    "unsupported timestamp format {other:?}"
314                )))
315            }
316        })
317    }
318}
319
320/// Locate a depth-2 XML element (a direct child of the document root) whose
321/// local name satisfies `predicate`, returning the byte slice covering it
322/// (`<El>...</El>`, inclusive of tags). The document root itself is also
323/// considered, so an "unwrapped" envelope whose root already matches is found.
324///
325/// Returns `None` if the body is not valid UTF-8, is not parseable as XML, or
326/// contains no matching element — callers decide how to fall back.
327///
328/// Robust to start-tag attributes (e.g. `<Error xmlns="...">`), nested
329/// same-name elements, comments, and CDATA, which a naive substring search
330/// would mishandle. Both the AWS REST XML error path (`name == "Error"`) and
331/// the awsQuery response path (`name.ends_with("Result") || name == "Error"`)
332/// build on this.
333pub fn find_depth2_element_slice_by(
334    body: &[u8],
335    predicate: impl Fn(&str) -> bool,
336) -> Option<&[u8]> {
337    let mut doc = Document::try_from(body).ok()?;
338    let mut root = doc.root_element().ok()?;
339    // Unwrapped envelope: the root element itself matches. Its start/end tags
340    // are already at the body boundaries, so return the whole body.
341    if predicate(root.start_el().local()) {
342        return Some(body);
343    }
344    // Wrapped envelope: scan the root's direct children for a match.
345    while let Some(tag) = root.next_tag() {
346        let local = tag.start_el().local();
347        if predicate(local) {
348            // `local` is a `&str` borrowed from `body`, satisfying the
349            // pointer-containment invariant of `element_start`.
350            let start = XmlDeserializer::element_start(body, local);
351            return Some(&body[start..tag.end_offset()]);
352        }
353    }
354    None
355}
356
357impl ShapeDeserializer for XmlDeserializer<'_> {
358    fn read_struct(
359        &mut self,
360        schema: &Schema<'_>,
361        consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
362    ) -> Result<(), SerdeError> {
363        self.enter_aggregate()?;
364        // IIFE: any `?` (or early `return`) inside falls through to
365        // `leave_aggregate` below — preserving the depth counter on the
366        // error path. `return` inside a Rust closure returns from the
367        // closure, not the enclosing function, so the unwrapped-output
368        // early-return below correctly produces `Ok(())` for the IIFE.
369        let result = (|| -> Result<(), SerdeError> {
370            // Build a Document over `self.input` locally. Doing it here (rather
371            // than as part of `XmlDeserializer` state) keeps the iteration
372            // borrow scoped to this stack frame, which lets us mutate `self`
373            // (via `dispatch_*`) for child-consumer dispatches without fighting
374            // a long-lived borrow on `self.state`.
375            let input = self.input;
376            // The top-level struct of a strict (server) read owns the whole
377            // body: it rejects ill-formed XML rather than recovering from it,
378            // so nothing in front of the service can read the bytes
379            // differently from the handler. Every byte of the body passes
380            // through this `doc`'s tokenizer (child elements are drained
381            // through it), so checking here covers nested content too.
382            let mut doc = self.document()?;
383            let mut root = doc
384                .root_element()
385                .map_err(|e| SerdeError::custom(e.to_string()))?;
386
387            if self.settings.validate_root_name && self.depth == 1 {
388                let expected = schema
389                    .xml_name()
390                    .map(|t| t.value())
391                    .or_else(|| schema.original_name())
392                    .or_else(|| schema.member_name())
393                    .unwrap_or_else(|| schema.shape_id().shape_name());
394                if !root.start_el().matches(expected) {
395                    return Err(SerdeError::invalid_input(format!(
396                        "expected XML root {expected}"
397                    )));
398                }
399            }
400
401            // Unwrapped XML output (e.g. S3 `GetBucketLocation` whose body is
402            // `<LocationConstraint>...</LocationConstraint>` rather than
403            // `<GetBucketLocationOutput><LocationConstraint>...`). The body's
404            // root element IS the (sole) member element — dispatch it directly
405            // and skip the normal "enter wrapper, iterate children" path. The
406            // schema flag is set by codegen for operations with the
407            // `S3UnwrappedXmlOutputTrait` AWS customization; non-XML codecs
408            // ignore the field, preserving runtime protocol-swap compatibility.
409            if schema.xml_unwrapped_output() {
410                // Capture the element's start offset BEFORE consuming `root`:
411                // `element_start`'s pointer-arithmetic invariant requires
412                // `el_local` to be a sub-slice of `input`, which
413                // `root.start_el().local()` is. A previous version passed an
414                // owned `String` here, silently producing offset=0; correct
415                // only by happy accident when the input buffer started with
416                // the target element.
417                let el_local = root.start_el().local();
418                let start = Self::element_start(input, el_local);
419                let local = el_local.to_owned();
420                // Consuming `root` advances the tokenizer past the close tag,
421                // which gives the end offset and releases the iterator borrow
422                // on `doc` so we can mutate `self`.
423                let sub = &input[start..root.end_offset()];
424                self.finish_document(&mut doc)?;
425                if let Some(member) = Self::resolve_member(schema, &local) {
426                    self.dispatch_subslice(sub, |this| consumer(member, this))?;
427                }
428                return Ok(());
429            }
430
431            // Dispatch @xmlAttribute members from the start element's attributes.
432            for member in schema.members() {
433                if member.xml_attribute() {
434                    let attr_name = member
435                        .xml_name()
436                        .map(|t| t.value())
437                        .or(member.member_name())
438                        .unwrap_or("");
439                    if let Some(value) = root.start_el().attr(attr_name) {
440                        let text = Cow::Owned(value.to_owned());
441                        self.dispatch_text(Ok(text), |this| consumer(member, this))?;
442                    }
443                }
444            }
445
446            // Track flattened-aggregate members: their wire format is repeated sibling
447            // elements that must be accumulated and dispatched as a single read_list /
448            // read_map call. Map: member_index -> (member_schema, accumulated XML bytes).
449            let mut flattened_groups: std::collections::HashMap<usize, (&Schema<'_>, Vec<u8>)> =
450                std::collections::HashMap::new();
451
452            // Dispatch child elements.
453            while let Some(mut child_scope) = root.next_tag() {
454                let Some(member) = Self::resolve_member(schema, child_scope.start_el().local())
455                else {
456                    continue;
457                };
458                // For non-flattened aggregate members, the child element IS the
459                // aggregate container (e.g. `<myList><member>...</member></myList>`).
460                // Use a sub-slice into `input` so the consumer's read_list / read_map
461                // / read_struct can build its own Document over the child element.
462                // For flattened aggregate members, accumulate the bytes of each
463                // matching sibling and dispatch them together below.
464                // For scalars (including flattened scalars), extract text inline.
465                let is_aggregate = member.shape_type().is_aggregate();
466                if is_aggregate && !member.xml_flattened() {
467                    let start = Self::element_start(input, child_scope.start_el().local());
468                    let sub = &input[start..child_scope.end_offset()];
469                    self.dispatch_subslice(sub, |this| consumer(member, this))?;
470                } else if is_aggregate {
471                    // Flattened aggregate: capture this sibling's slice; dispatch
472                    // the merged group below.
473                    let start = Self::element_start(input, child_scope.start_el().local());
474                    let sub = &input[start..child_scope.end_offset()];
475                    let idx = member.member_index().unwrap_or(usize::MAX);
476                    let entry = flattened_groups
477                        .entry(idx)
478                        .or_insert_with(|| (member, Vec::new()));
479                    entry.1.extend_from_slice(sub);
480                } else {
481                    // Keep extraction errors with the value: a transport adapter may
482                    // discard this member without reading its modeled scalar type.
483                    let text = decode::try_data(&mut child_scope)
484                        .map_err(|e| SerdeError::custom(e.to_string()));
485                    drop(child_scope);
486                    self.dispatch_text(text, |this| consumer(member, this))?;
487                }
488            }
489            drop(root);
490            self.finish_document(&mut doc)?;
491
492            // Dispatch each accumulated flattened-aggregate group as a single call.
493            // We synthesize a `<__flat>...</__flat>` wrapper so the consumer's
494            // `read_list` / `read_map` sees the collected siblings as
495            // wrapper-children and iterates them normally. The wrapper buffer is
496            // owned locally (lifetime is shorter than `'a`), so we cannot route
497            // it through `dispatch_subslice`; `new_child` instead spawns a
498            // deserializer that inherits the current depth, keeping the
499            // recursion-depth guard continuous across the flattened boundary.
500            for (_idx, (member, bytes)) in flattened_groups {
501                let mut wrapped = Vec::with_capacity(bytes.len() + 16);
502                wrapped.extend_from_slice(b"<__flat>");
503                wrapped.extend_from_slice(&bytes);
504                wrapped.extend_from_slice(b"</__flat>");
505                let mut child_deser = self.new_child(&wrapped);
506                consumer(member, &mut child_deser)?;
507            }
508            Ok(())
509        })();
510        self.leave_aggregate();
511        result
512    }
513
514    fn read_list(
515        &mut self,
516        schema: &Schema<'_>,
517        consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
518    ) -> Result<(), SerdeError> {
519        let item_name = Self::list_item_name(self.settings.strict_collection_element_names, schema);
520        self.enter_aggregate()?;
521        // IIFE: any `?` inside falls through to `leave_aggregate` below
522        // (see `read_string_list` for rationale).
523        let result = (|| -> Result<(), SerdeError> {
524            let input = self.input;
525            let mut doc = self.document()?;
526            let mut root = doc
527                .root_element()
528                .map_err(|e| SerdeError::custom(e.to_string()))?;
529
530            // Each child tag is a list item (with `strict_collection_element_names`,
531            // each one named as an item). Provide each item to the consumer
532            // by re-pointing `self` at the item's sub-slice via dispatch_subslice.
533            // Scalar consumers will navigate to the text via `take_text()`;
534            // aggregate consumers (read_list / read_struct / read_map) will
535            // descend into the element's content. This keeps the deserializer
536            // compatible with both scalar list elements and nested aggregate
537            // elements (e.g. list-of-lists, list-of-structs) without per-element
538            // type sniffing.
539            while let Some(child_scope) = root.next_tag() {
540                if !Self::is_list_item(item_name, child_scope.start_el()) {
541                    continue;
542                }
543                let start = Self::element_start(input, child_scope.start_el().local());
544                let sub = &input[start..child_scope.end_offset()];
545                self.dispatch_subslice(sub, |this| consumer(this))?;
546            }
547            drop(root);
548            self.finish_document(&mut doc)?;
549            Ok(())
550        })();
551        self.leave_aggregate();
552        result
553    }
554
555    fn read_map(
556        &mut self,
557        schema: &Schema<'_>,
558        consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
559    ) -> Result<(), SerdeError> {
560        self.enter_aggregate()?;
561        // IIFE: any `?` inside falls through to `leave_aggregate` below
562        // (see `read_string_list` for rationale).
563        let result = (|| -> Result<(), SerdeError> {
564            let input = self.input;
565            let mut doc = self.document()?;
566            let mut root = doc
567                .root_element()
568                .map_err(|e| SerdeError::custom(e.to_string()))?;
569
570            // Resolve key/value element names from the schema.
571            let key_name = schema
572                .key()
573                .and_then(|k| k.xml_name().map(|t| t.value()))
574                .unwrap_or("key");
575            let value_name = schema
576                .member()
577                .and_then(|v| v.xml_name().map(|t| t.value()))
578                .unwrap_or("value");
579
580            // Aggregate (struct/list/map) value types need a sub-document deserializer
581            // since their content includes nested elements rather than just text.
582            let value_is_aggregate = schema
583                .member()
584                .map(|v| v.shape_type().is_aggregate())
585                .unwrap_or(false);
586
587            // Each child tag is an entry (e.g. <entry><key>k</key><value>v</value></entry>);
588            // with `strict_collection_element_names`, only the `entry` children of a
589            // wrapped map are.
590            while let Some(mut entry_scope) = root.next_tag() {
591                if !Self::is_map_entry(
592                    self.settings.strict_collection_element_names,
593                    schema,
594                    entry_scope.start_el(),
595                ) {
596                    continue;
597                }
598                let mut key: Option<String> = None;
599                // For scalar values we capture the text upfront; for aggregate
600                // values we capture the element sub-slice. At most one is set.
601                let mut value_text: Option<Cow<'_, str>> = None;
602                let mut value_slice: Option<&'_ [u8]> = None;
603                while let Some(mut field_scope) = entry_scope.next_tag() {
604                    let local = field_scope.start_el().local();
605                    let (is_key, is_value) = (local == key_name, local == value_name);
606                    if is_key {
607                        let text = decode::try_data(&mut field_scope)
608                            .map_err(|e| SerdeError::custom(e.to_string()))?;
609                        key = Some(text.into_owned());
610                    } else if is_value {
611                        if value_is_aggregate {
612                            let start = Self::element_start(input, field_scope.start_el().local());
613                            let sub = &input[start..field_scope.end_offset()];
614                            value_slice = Some(sub);
615                        } else {
616                            let text = decode::try_data(&mut field_scope)
617                                .map_err(|e| SerdeError::custom(e.to_string()))?;
618                            value_text = Some(text);
619                        }
620                    }
621                }
622                drop(entry_scope);
623                if let Some(k) = key {
624                    if let Some(slice) = value_slice {
625                        self.dispatch_subslice(slice, |this| consumer(k, this))?;
626                    } else if let Some(t) = value_text {
627                        // Re-borrow t as 'a — the text was extracted from `doc`
628                        // which borrows `self.input: &'a [u8]`, so its lifetime
629                        // is `'a`.
630                        let t: Cow<'_, str> = t;
631                        self.dispatch_text(Ok(t.into_owned().into()), |this| consumer(k, this))?;
632                    }
633                }
634            }
635            drop(root);
636            self.finish_document(&mut doc)?;
637            Ok(())
638        })();
639        self.leave_aggregate();
640        result
641    }
642
643    fn read_boolean(&mut self, _schema: &Schema<'_>) -> Result<bool, SerdeError> {
644        let text = self.take_text()?;
645        match text.as_ref() {
646            "true" => Ok(true),
647            "false" => Ok(false),
648            other => Err(SerdeError::custom(format!("invalid boolean: {other}"))),
649        }
650    }
651
652    fn read_byte(&mut self, _schema: &Schema<'_>) -> Result<i8, SerdeError> {
653        let text = self.take_text()?;
654        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
655    }
656
657    fn read_short(&mut self, _schema: &Schema<'_>) -> Result<i16, SerdeError> {
658        let text = self.take_text()?;
659        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
660    }
661
662    fn read_integer(&mut self, _schema: &Schema<'_>) -> Result<i32, SerdeError> {
663        let text = self.take_text()?;
664        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
665    }
666
667    fn read_long(&mut self, _schema: &Schema<'_>) -> Result<i64, SerdeError> {
668        let text = self.take_text()?;
669        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
670    }
671
672    fn read_float(&mut self, _schema: &Schema<'_>) -> Result<f32, SerdeError> {
673        let text = self.take_text()?;
674        match text.as_ref() {
675            "NaN" => Ok(f32::NAN),
676            "Infinity" => Ok(f32::INFINITY),
677            "-Infinity" => Ok(f32::NEG_INFINITY),
678            _ => text.parse().map_err(|e| SerdeError::custom(format!("{e}"))),
679        }
680    }
681
682    fn read_double(&mut self, _schema: &Schema<'_>) -> Result<f64, SerdeError> {
683        let text = self.take_text()?;
684        match text.as_ref() {
685            "NaN" => Ok(f64::NAN),
686            "Infinity" => Ok(f64::INFINITY),
687            "-Infinity" => Ok(f64::NEG_INFINITY),
688            _ => text.parse().map_err(|e| SerdeError::custom(format!("{e}"))),
689        }
690    }
691
692    fn read_big_integer(&mut self, _schema: &Schema<'_>) -> Result<BigInteger, SerdeError> {
693        let text = self.take_text()?;
694        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
695    }
696
697    fn read_big_decimal(&mut self, _schema: &Schema<'_>) -> Result<BigDecimal, SerdeError> {
698        let text = self.take_text()?;
699        text.parse().map_err(|e| SerdeError::custom(format!("{e}")))
700    }
701
702    fn read_string(&mut self, _schema: &Schema<'_>) -> Result<String, SerdeError> {
703        let text = self.take_text()?;
704        Ok(text.into_owned())
705    }
706
707    fn read_blob(&mut self, _schema: &Schema<'_>) -> Result<Blob, SerdeError> {
708        let text = self.take_text()?;
709        let bytes = aws_smithy_types::base64::decode(text.as_ref())
710            .map_err(|e| SerdeError::custom(format!("{e}")))?;
711        Ok(Blob::new(bytes))
712    }
713
714    fn read_timestamp(&mut self, schema: &Schema<'_>) -> Result<DateTime, SerdeError> {
715        let text = self.take_text()?;
716        let format = self.resolve_timestamp_format(schema)?;
717        DateTime::from_str(text.as_ref(), format).map_err(|e| SerdeError::custom(format!("{e}")))
718    }
719
720    fn read_document(&mut self, _schema: &Schema<'_>) -> Result<SmithyDocument, SerdeError> {
721        Err(SerdeError::custom(
722            "document types are not supported by REST XML",
723        ))
724    }
725
726    // -------- Specialized collection overrides --------
727    //
728    // The default trait impls of `read_*_list` / `read_string_string_map`
729    // call `self.read_list` / `self.read_map` with a `&mut dyn FnMut` consumer
730    // that itself calls `&mut dyn ShapeDeserializer::read_X` per element.
731    // For XML this is doubly wasteful: each list element pays
732    //
733    //   1. one `&mut dyn FnMut` indirect call,
734    //   2. one `&mut dyn ShapeDeserializer` virtual call,
735    //   3. and — because the consumer goes through `dispatch_subslice` —
736    //      a fresh `Document::try_from` over the element's sub-slice plus
737    //      a save/restore of (`input`, `text`).
738    //
739    // The overrides below walk the existing tokenizer once and extract
740    // text inline via `decode::try_data`, eliminating all three costs.
741    // Like `XmlDeserializer::read_list` / `read_map`, they take every child
742    // element as an item or entry unless `strict_collection_element_names`
743    // is set, in which case they take only the children named as the schema
744    // says items and entries are and skip the others.
745    //
746    // Sparse lists are not routed here: `SchemaGenerator` only emits
747    // `read_string_list` / `read_blob_list` / `read_integer_list` /
748    // `read_long_list` / `read_string_string_map` for non-sparse element
749    // shapes (see SchemaGenerator.kt line ~1497).
750
751    fn read_string_list(&mut self, schema: &Schema<'_>) -> Result<Vec<String>, SerdeError> {
752        let item_name = Self::list_item_name(self.settings.strict_collection_element_names, schema);
753        self.enter_aggregate()?;
754        // IIFE so that any `?` short-circuit still falls through to
755        // `leave_aggregate` below — preserving the depth counter on the
756        // error path. Same pattern in `read_blob_list`, `read_integer_list`,
757        // `read_long_list`, and `read_string_string_map`.
758        let result = (|| -> Result<Vec<String>, SerdeError> {
759            let mut doc = self.document()?;
760            let mut root = doc
761                .root_element()
762                .map_err(|e| SerdeError::custom(e.to_string()))?;
763            let mut out = Vec::new();
764            while let Some(mut child_scope) = root.next_tag() {
765                if !Self::is_list_item(item_name, child_scope.start_el()) {
766                    continue;
767                }
768                let text = decode::try_data(&mut child_scope)
769                    .map_err(|e| SerdeError::custom(e.to_string()))?;
770                out.push(text.into_owned());
771            }
772            drop(root);
773            self.finish_document(&mut doc)?;
774            Ok(out)
775        })();
776        self.leave_aggregate();
777        result
778    }
779
780    fn read_blob_list(&mut self, schema: &Schema<'_>) -> Result<Vec<Blob>, SerdeError> {
781        use aws_smithy_types::base64;
782        let item_name = Self::list_item_name(self.settings.strict_collection_element_names, schema);
783        self.enter_aggregate()?;
784        let result = (|| -> Result<Vec<Blob>, SerdeError> {
785            let mut doc = self.document()?;
786            let mut root = doc
787                .root_element()
788                .map_err(|e| SerdeError::custom(e.to_string()))?;
789            let mut out = Vec::new();
790            while let Some(mut child_scope) = root.next_tag() {
791                if !Self::is_list_item(item_name, child_scope.start_el()) {
792                    continue;
793                }
794                let text = decode::try_data(&mut child_scope)
795                    .map_err(|e| SerdeError::custom(e.to_string()))?;
796                let bytes = base64::decode(text.as_ref())
797                    .map_err(|e| SerdeError::custom(format!("invalid base64: {e}")))?;
798                out.push(Blob::new(bytes));
799            }
800            drop(root);
801            self.finish_document(&mut doc)?;
802            Ok(out)
803        })();
804        self.leave_aggregate();
805        result
806    }
807
808    fn read_integer_list(&mut self, schema: &Schema<'_>) -> Result<Vec<i32>, SerdeError> {
809        let item_name = Self::list_item_name(self.settings.strict_collection_element_names, schema);
810        self.enter_aggregate()?;
811        let result = (|| -> Result<Vec<i32>, SerdeError> {
812            let mut doc = self.document()?;
813            let mut root = doc
814                .root_element()
815                .map_err(|e| SerdeError::custom(e.to_string()))?;
816            let mut out = Vec::new();
817            while let Some(mut child_scope) = root.next_tag() {
818                if !Self::is_list_item(item_name, child_scope.start_el()) {
819                    continue;
820                }
821                let text = decode::try_data(&mut child_scope)
822                    .map_err(|e| SerdeError::custom(e.to_string()))?;
823                let v: i32 = text
824                    .parse()
825                    .map_err(|e| SerdeError::custom(format!("{e}")))?;
826                out.push(v);
827            }
828            drop(root);
829            self.finish_document(&mut doc)?;
830            Ok(out)
831        })();
832        self.leave_aggregate();
833        result
834    }
835
836    fn read_long_list(&mut self, schema: &Schema<'_>) -> Result<Vec<i64>, SerdeError> {
837        let item_name = Self::list_item_name(self.settings.strict_collection_element_names, schema);
838        self.enter_aggregate()?;
839        let result = (|| -> Result<Vec<i64>, SerdeError> {
840            let mut doc = self.document()?;
841            let mut root = doc
842                .root_element()
843                .map_err(|e| SerdeError::custom(e.to_string()))?;
844            let mut out = Vec::new();
845            while let Some(mut child_scope) = root.next_tag() {
846                if !Self::is_list_item(item_name, child_scope.start_el()) {
847                    continue;
848                }
849                let text = decode::try_data(&mut child_scope)
850                    .map_err(|e| SerdeError::custom(e.to_string()))?;
851                let v: i64 = text
852                    .parse()
853                    .map_err(|e| SerdeError::custom(format!("{e}")))?;
854                out.push(v);
855            }
856            drop(root);
857            self.finish_document(&mut doc)?;
858            Ok(out)
859        })();
860        self.leave_aggregate();
861        result
862    }
863
864    fn read_string_string_map(
865        &mut self,
866        schema: &Schema<'_>,
867    ) -> Result<std::collections::HashMap<String, String>, SerdeError> {
868        let key_name = schema
869            .key()
870            .and_then(|k| k.xml_name().map(|t| t.value()))
871            .unwrap_or("key");
872        let value_name = schema
873            .member()
874            .and_then(|v| v.xml_name().map(|t| t.value()))
875            .unwrap_or("value");
876
877        self.enter_aggregate()?;
878        let result = (|| -> Result<std::collections::HashMap<String, String>, SerdeError> {
879            let mut doc = self.document()?;
880            let mut root = doc
881                .root_element()
882                .map_err(|e| SerdeError::custom(e.to_string()))?;
883            let mut out = std::collections::HashMap::new();
884            while let Some(mut entry_scope) = root.next_tag() {
885                if !Self::is_map_entry(
886                    self.settings.strict_collection_element_names,
887                    schema,
888                    entry_scope.start_el(),
889                ) {
890                    continue;
891                }
892                let mut k: Option<String> = None;
893                let mut v: Option<String> = None;
894                while let Some(mut field_scope) = entry_scope.next_tag() {
895                    let local = field_scope.start_el().local();
896                    let (is_key, is_value) = (local == key_name, local == value_name);
897                    if is_key {
898                        let text = decode::try_data(&mut field_scope)
899                            .map_err(|e| SerdeError::custom(e.to_string()))?;
900                        k = Some(text.into_owned());
901                    } else if is_value {
902                        let text = decode::try_data(&mut field_scope)
903                            .map_err(|e| SerdeError::custom(e.to_string()))?;
904                        v = Some(text.into_owned());
905                    }
906                }
907                if let (Some(k), Some(v)) = (k, v) {
908                    out.insert(k, v);
909                }
910            }
911            drop(root);
912            self.finish_document(&mut doc)?;
913            Ok(out)
914        })();
915        self.leave_aggregate();
916        result
917    }
918
919    fn is_null(&self) -> bool {
920        // XML represents absence by omitting the element entirely.
921        // If we have a deserializer, the element exists, so it's not null.
922        false
923    }
924
925    /// Nothing to advance. `read_struct` delimits each child before dispatching it —
926    /// either as an owned sub-slice or as pre-extracted text — and the child's
927    /// `ScopedDecoder` is dropped, which moves the parent's tokenizer past the closing
928    /// tag regardless of what the consumer did. Advancing again here would consume a
929    /// sibling element.
930    fn skip_value(&mut self) -> Result<(), SerdeError> {
931        Ok(())
932    }
933
934    fn container_size(&self) -> Option<usize> {
935        None
936    }
937}
938
939#[cfg(test)]
940mod tests {
941    use super::*;
942    use aws_smithy_schema::{shape_id, Schema, ShapeType};
943
944    static STRING_MEMBER: Schema<'static> =
945        Schema::new_member(shape_id!("test", "S$v"), ShapeType::String, "v", 0);
946
947    #[test]
948    fn read_string_from_text_state() {
949        let settings = Arc::new(XmlCodecSettings::default());
950        let mut deser = XmlDeserializer::from_text(Cow::Borrowed("hello"), settings);
951        let result = deser.read_string(&STRING_MEMBER).unwrap();
952        assert_eq!(result, "hello");
953    }
954
955    #[test]
956    fn read_string_from_doc_text_content() {
957        // Verify a Doc-state deserializer can extract leaf text via the
958        // public `read_string` API (which goes through `take_text` →
959        // lazy Document construction over `self.input`).
960        static V_MEMBER: Schema<'static> =
961            Schema::new_member(shape_id!("test", "X$v"), ShapeType::String, "v", 0);
962        let xml = b"<root>world</root>";
963        let settings = Arc::new(XmlCodecSettings::default());
964        let mut deser = XmlDeserializer::new(xml, settings);
965        let result = deser.read_string(&V_MEMBER).unwrap();
966        assert_eq!(result, "world");
967    }
968
969    #[test]
970    fn is_null_always_false() {
971        let settings = Arc::new(XmlCodecSettings::default());
972        let deser = XmlDeserializer::new(b"<r/>", settings);
973        assert!(!deser.is_null());
974    }
975
976    // Struct member dispatch by element name (`@xmlName` and member name).
977
978    static NAME_MEMBER: Schema<'static> = Schema::new_member(
979        shape_id!("test", "Person$name"),
980        ShapeType::String,
981        "name",
982        0,
983    );
984    static AGE_MEMBER: Schema<'static> =
985        Schema::new_member(shape_id!("test", "Person$age"), ShapeType::String, "age", 1);
986    static RENAMED_MEMBER: Schema<'static> = Schema::new_member(
987        shape_id!("test", "Person$nick"),
988        ShapeType::String,
989        "nick",
990        2,
991    )
992    .with_xml_name("Nickname");
993
994    static PERSON_SCHEMA: Schema<'static> = Schema::new_struct(
995        shape_id!("test", "Person"),
996        ShapeType::Structure,
997        &[&NAME_MEMBER, &AGE_MEMBER, &RENAMED_MEMBER],
998    );
999
1000    #[test]
1001    fn read_struct_dispatches_members() {
1002        let xml = b"<Person><name>Alice</name><age>30</age></Person>";
1003        let settings = Arc::new(XmlCodecSettings::default());
1004        let mut deser = XmlDeserializer::new(xml, settings);
1005
1006        let mut name = String::new();
1007        let mut age = String::new();
1008        deser
1009            .read_struct(&PERSON_SCHEMA, &mut |member, d| {
1010                match member.member_name().unwrap() {
1011                    "name" => name = d.read_string(member)?,
1012                    "age" => age = d.read_string(member)?,
1013                    _ => {}
1014                }
1015                Ok(())
1016            })
1017            .unwrap();
1018
1019        assert_eq!(name, "Alice");
1020        assert_eq!(age, "30");
1021    }
1022
1023    #[test]
1024    fn read_struct_skips_unknown_elements() {
1025        let xml = b"<Person><unknown>x</unknown><name>Bob</name></Person>";
1026        let settings = Arc::new(XmlCodecSettings::default());
1027        let mut deser = XmlDeserializer::new(xml, settings);
1028
1029        let mut name = String::new();
1030        deser
1031            .read_struct(&PERSON_SCHEMA, &mut |member, d| {
1032                if member.member_name() == Some("name") {
1033                    name = d.read_string(member)?;
1034                }
1035                Ok(())
1036            })
1037            .unwrap();
1038
1039        assert_eq!(name, "Bob");
1040    }
1041
1042    /// A member's own name can collide with another member's `@xmlName`. Every
1043    /// occurrence of the shared element resolves to the first matching member, wherever
1044    /// it appears and however often it repeats.
1045    #[test]
1046    fn colliding_element_names_resolve_to_the_first_member() {
1047        static STATUS: Schema<'static> = Schema::new_member(
1048            shape_id!("ns", "S", "status"),
1049            ShapeType::String,
1050            "status",
1051            0,
1052        );
1053        static STATUS_TEXT: Schema<'static> = Schema::new_member(
1054            shape_id!("ns", "S", "statusText"),
1055            ShapeType::String,
1056            "statusText",
1057            1,
1058        )
1059        .with_xml_name("status");
1060        static OTHER: Schema<'static> =
1061            Schema::new_member(shape_id!("ns", "S", "other"), ShapeType::String, "other", 2);
1062        static S: Schema<'static> = Schema::new_struct(
1063            shape_id!("ns", "S"),
1064            ShapeType::Structure,
1065            &[&STATUS, &STATUS_TEXT, &OTHER],
1066        );
1067
1068        fn read(xml: &[u8]) -> Vec<(usize, String)> {
1069            let mut deser = XmlDeserializer::new(xml, Arc::new(XmlCodecSettings::default()));
1070            let mut seen = Vec::new();
1071            deser
1072                .read_struct(&S, &mut |member, d| {
1073                    seen.push((member.member_index().unwrap(), d.read_string(member)?));
1074                    Ok(())
1075                })
1076                .unwrap();
1077            seen
1078        }
1079
1080        assert_eq!(
1081            read(b"<S><status>a</status><status>b</status></S>"),
1082            [(0, "a".to_owned()), (0, "b".to_owned())]
1083        );
1084        assert_eq!(
1085            read(b"<S><other>x</other><status>a</status></S>"),
1086            [(2, "x".to_owned()), (0, "a".to_owned())]
1087        );
1088    }
1089
1090    #[test]
1091    fn strict_read_rejects_ill_formed_xml() {
1092        static P_STRING: Schema<'static> =
1093            Schema::new_member(shape_id!("test", "P$s"), ShapeType::String, "s", 0);
1094        static P_LIST: Schema<'static> =
1095            Schema::new_member(shape_id!("test", "P$list"), ShapeType::List, "list", 1);
1096        static P_FLAT: Schema<'static> =
1097            Schema::new_member(shape_id!("test", "P$flat"), ShapeType::List, "flat", 2)
1098                .with_xml_flattened();
1099        static P_MAP: Schema<'static> =
1100            Schema::new_member(shape_id!("test", "P$map"), ShapeType::Map, "map", 3);
1101        static P_SCHEMA: Schema<'static> = Schema::new_struct(
1102            shape_id!("test", "P"),
1103            ShapeType::Structure,
1104            &[&P_STRING, &P_LIST, &P_FLAT, &P_MAP],
1105        );
1106        static MAP_KEY: Schema<'static> =
1107            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1108        static MAP_VALUE: Schema<'static> =
1109            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1);
1110        static MAP_SCHEMA: Schema<'static> =
1111            Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
1112
1113        fn read(input: &str, strict: bool) -> Result<(), SerdeError> {
1114            let settings = Arc::new(
1115                XmlCodecSettings::builder()
1116                    .enforce_strictness(strict)
1117                    .build(),
1118            );
1119            XmlDeserializer::new(input.as_bytes(), settings).read_struct(&P_SCHEMA, &mut |m, d| {
1120                match m.member_name().unwrap() {
1121                    "s" => d.read_string(m).map(|_| ()),
1122                    "list" | "flat" => d.read_list(m, &mut |d| d.read_string(m).map(|_| ())),
1123                    _ => d.read_map(&MAP_SCHEMA, &mut |_, d| d.read_string(m).map(|_| ())),
1124                }
1125            })
1126        }
1127
1128        for ok in [
1129            "<P><s>x</s><list><member>a</member></list><flat>b</flat><flat>c</flat></P>",
1130            "<?xml version=\"1.0\"?><!-- c --><P><s><![CDATA[<x>]]></s></P>\n<!-- trailing -->",
1131            r#"<a:P xmlns:a="u"><a:s>x</a:s><list ><member/></list ></a:P>"#,
1132            "<P><unknown><deep/></unknown><map><entry><key>k</key><value>v</value></entry></map></P>",
1133            "<P/>",
1134        ] {
1135            read(ok, true).unwrap_or_else(|e| panic!("well-formed {ok:?} rejected: {e}"));
1136        }
1137
1138        for bad in [
1139            // proof 55: mismatched end tags
1140            "<P><map><gntry><key>k</key><value>v</value></entry></map><s>s</s></P>",
1141            "<P><list><member>a</member></l><member>b</member></list></P>",
1142            // mismatched end tag inside a flattened member / an unknown member
1143            "<P><flat>a</flatx><flat>b</flat></P>",
1144            "<P><unknown><deep></unknown></P>",
1145            // prefix must match too
1146            r#"<P xmlns:a="u" xmlns:b="v"><a:s>x</b:s></P>"#,
1147            r#"<P xmlns:a="u"><s>x</a:s></P>"#,
1148            r#"<P xmlns:a="u"><a:s>x</s></P>"#,
1149            // unclosed root, unclosed child
1150            "<P><s>x</s>",
1151            "<P><s>x</P>",
1152            // stray close tag after the root, second root
1153            "<P><s>x</s></P></P>",
1154            "<P></P><P></P>",
1155        ] {
1156            assert!(read(bad, true).is_err(), "ill-formed {bad:?} accepted");
1157            // The lenient (client) read still recovers as before.
1158            let _ = read(bad, false);
1159        }
1160        // Lenient reads keep recovering rather than rejecting.
1161        read("<P><s>x</s>", false).expect("lenient read accepts an unclosed root");
1162    }
1163
1164    #[test]
1165    fn strict_roots_only_validate_document_boundary() {
1166        static CHILD: Schema<'static> = Schema::new_struct(
1167            aws_smithy_schema::shape_id!("test", "Child"),
1168            aws_smithy_schema::ShapeType::Structure,
1169            &[],
1170        );
1171        static RENAMED: Schema<'static> = Schema::new_member(
1172            aws_smithy_schema::shape_id!("test", "Root", "child"),
1173            aws_smithy_schema::ShapeType::Structure,
1174            "child",
1175            0,
1176        )
1177        .with_xml_name("Renamed");
1178        static ROOT: Schema<'static> = Schema::new_struct(
1179            aws_smithy_schema::shape_id!("test", "Synthetic"),
1180            aws_smithy_schema::ShapeType::Structure,
1181            &[&RENAMED],
1182        )
1183        .with_original_name("Original")
1184        .with_xml_name("WireRoot");
1185        for strict in [false, true] {
1186            let settings = Arc::new(
1187                XmlCodecSettings::builder()
1188                    .enforce_strictness(strict)
1189                    .build(),
1190            );
1191            for (input, matches) in [
1192                (b"<WireRoot><Renamed/></WireRoot>".as_slice(), true),
1193                (b"<Wrong><Renamed/></Wrong>", false),
1194            ] {
1195                let mut visited = false;
1196                let result = XmlDeserializer::new(input, settings.clone()).read_struct(
1197                    &ROOT,
1198                    &mut |_, d| {
1199                        visited = true;
1200                        d.read_struct(&CHILD, &mut |_, _| Ok(()))
1201                    },
1202                );
1203                assert_eq!(result.is_ok(), !strict || matches);
1204                assert_eq!(visited, !strict || matches);
1205            }
1206        }
1207    }
1208
1209    #[test]
1210    fn read_struct_resolves_xml_name() {
1211        let xml = b"<Person><Nickname>Ally</Nickname></Person>";
1212        let settings = Arc::new(XmlCodecSettings::default());
1213        let mut deser = XmlDeserializer::new(xml, settings);
1214
1215        let mut nick = String::new();
1216        deser
1217            .read_struct(&PERSON_SCHEMA, &mut |member, d| {
1218                if member.member_name() == Some("nick") {
1219                    nick = d.read_string(member)?;
1220                }
1221                Ok(())
1222            })
1223            .unwrap();
1224
1225        assert_eq!(nick, "Ally");
1226    }
1227
1228    // `@xmlAttribute` dispatch from the start element's attributes.
1229
1230    static ATTR_MEMBER: Schema<'static> =
1231        Schema::new_member(shape_id!("test", "X$id"), ShapeType::String, "id", 0)
1232            .with_xml_attribute();
1233    static ELEM_MEMBER: Schema<'static> =
1234        Schema::new_member(shape_id!("test", "X$name"), ShapeType::String, "name", 1);
1235
1236    static X_SCHEMA: Schema<'static> = Schema::new_struct(
1237        shape_id!("test", "X"),
1238        ShapeType::Structure,
1239        &[&ATTR_MEMBER, &ELEM_MEMBER],
1240    );
1241
1242    #[test]
1243    fn read_struct_dispatches_attributes() {
1244        let xml = b"<X id=\"42\"><name>hello</name></X>";
1245        let settings = Arc::new(XmlCodecSettings::default());
1246        let mut deser = XmlDeserializer::new(xml, settings);
1247
1248        let mut id = String::new();
1249        let mut name = String::new();
1250        deser
1251            .read_struct(&X_SCHEMA, &mut |member, d| {
1252                match member.member_name().unwrap() {
1253                    "id" => id = d.read_string(member)?,
1254                    "name" => name = d.read_string(member)?,
1255                    _ => {}
1256                }
1257                Ok(())
1258            })
1259            .unwrap();
1260
1261        assert_eq!(id, "42");
1262        assert_eq!(name, "hello");
1263    }
1264
1265    // Wrapped list / map reads with element-name resolution.
1266
1267    #[test]
1268    fn read_list_wrapped() {
1269        let xml = b"<items><member>a</member><member>b</member></items>";
1270        let settings = Arc::new(XmlCodecSettings::default());
1271        let mut deser = XmlDeserializer::new(xml, settings);
1272
1273        static LIST_MEMBER: Schema<'static> = Schema::new_member(
1274            shape_id!("test", "L$member"),
1275            ShapeType::String,
1276            "member",
1277            0,
1278        );
1279        static LIST_SCHEMA: Schema<'static> =
1280            Schema::new_list(shape_id!("test", "L"), &LIST_MEMBER);
1281
1282        let mut items = Vec::new();
1283        deser
1284            .read_list(&LIST_SCHEMA, &mut |d| {
1285                items.push(d.read_string(&LIST_MEMBER)?);
1286                Ok(())
1287            })
1288            .unwrap();
1289
1290        assert_eq!(items, vec!["a", "b"]);
1291    }
1292
1293    #[test]
1294    fn read_map_wrapped() {
1295        let xml = b"<myMap><entry><key>k1</key><value>v1</value></entry><entry><key>k2</key><value>v2</value></entry></myMap>";
1296        let settings = Arc::new(XmlCodecSettings::default());
1297        let mut deser = XmlDeserializer::new(xml, settings);
1298
1299        static MAP_KEY: Schema<'static> =
1300            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1301        static MAP_VALUE: Schema<'static> =
1302            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 0);
1303        static MAP_SCHEMA: Schema<'static> =
1304            Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
1305
1306        let mut entries = Vec::new();
1307        deser
1308            .read_map(&MAP_SCHEMA, &mut |k, d| {
1309                entries.push((k, d.read_string(&MAP_VALUE)?));
1310                Ok(())
1311            })
1312            .unwrap();
1313
1314        assert_eq!(
1315            entries,
1316            vec![
1317                ("k1".to_owned(), "v1".to_owned()),
1318                ("k2".to_owned(), "v2".to_owned())
1319            ]
1320        );
1321    }
1322
1323    #[test]
1324    fn read_map_with_renamed_key_value() {
1325        let xml = b"<m><entry><Attribute>a</Attribute><Setting>s</Setting></entry></m>";
1326        let settings = Arc::new(XmlCodecSettings::default());
1327        let mut deser = XmlDeserializer::new(xml, settings);
1328
1329        static MAP_KEY: Schema<'static> =
1330            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0)
1331                .with_xml_name("Attribute");
1332        static MAP_VALUE: Schema<'static> =
1333            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 0)
1334                .with_xml_name("Setting");
1335        static MAP_SCHEMA: Schema<'static> =
1336            Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
1337
1338        let mut entries = Vec::new();
1339        deser
1340            .read_map(&MAP_SCHEMA, &mut |k, d| {
1341                entries.push((k, d.read_string(&MAP_VALUE)?));
1342                Ok(())
1343            })
1344            .unwrap();
1345
1346        assert_eq!(entries, vec![("a".to_owned(), "s".to_owned())]);
1347    }
1348
1349    // Flattened collections: repeated sibling elements accumulated into one read.
1350
1351    #[test]
1352    fn read_struct_flattened_list() {
1353        // Flattened list: repeated <item> siblings inside the struct.
1354        let xml = b"<S><name>hi</name><item>a</item><item>b</item></S>";
1355        let settings = Arc::new(XmlCodecSettings::default());
1356        let mut deser = XmlDeserializer::new(xml, settings);
1357
1358        static S_NAME: Schema<'static> =
1359            Schema::new_member(shape_id!("test", "S$name"), ShapeType::String, "name", 0);
1360        static S_ITEMS: Schema<'static> =
1361            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 1)
1362                .with_xml_flattened()
1363                .with_xml_name("item");
1364        static S_SCHEMA: Schema<'static> = Schema::new_struct(
1365            shape_id!("test", "S"),
1366            ShapeType::Structure,
1367            &[&S_NAME, &S_ITEMS],
1368        );
1369
1370        let mut name = String::new();
1371        let mut items = Vec::new();
1372        deser
1373            .read_struct(&S_SCHEMA, &mut |member, d| {
1374                match member.member_name().unwrap() {
1375                    "name" => name = d.read_string(member)?,
1376                    "items" => d.read_list(member, &mut |d| {
1377                        items.push(d.read_string(member)?);
1378                        Ok(())
1379                    })?,
1380                    _ => {}
1381                }
1382                Ok(())
1383            })
1384            .unwrap();
1385
1386        assert_eq!(name, "hi");
1387        assert_eq!(items, vec!["a", "b"]);
1388    }
1389
1390    /// Regression (proofs 51/52): markup-like text in comments, CDATA,
1391    /// processing instructions and attribute values inside a flattened list,
1392    /// a list of structs and a map with struct values must not change what is
1393    /// decoded. Values follow `try_data`: the first text node wins, and CDATA
1394    /// is skipped (matching the legacy decoder).
1395    #[test]
1396    fn read_struct_ignores_markup_in_comments_cdata_pi_attrs() {
1397        static S_ITEMS: Schema<'static> =
1398            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 0)
1399                .with_xml_flattened()
1400                .with_xml_name("item");
1401        static S_PEOPLE: Schema<'static> =
1402            Schema::new_member(shape_id!("test", "S$people"), ShapeType::List, "people", 1);
1403        static S_MAP: Schema<'static> =
1404            Schema::new_member(shape_id!("test", "S$m"), ShapeType::Map, "m", 2);
1405        static S_SCHEMA: Schema<'static> = Schema::new_struct(
1406            shape_id!("test", "S"),
1407            ShapeType::Structure,
1408            &[&S_ITEMS, &S_PEOPLE, &S_MAP],
1409        );
1410        static PEOPLE_MEMBER: Schema<'static> = Schema::new_member(
1411            shape_id!("test", "People$member"),
1412            ShapeType::Structure,
1413            "member",
1414            0,
1415        );
1416        static PEOPLE_SCHEMA: Schema<'static> =
1417            Schema::new_list(shape_id!("test", "People"), &PEOPLE_MEMBER);
1418        static MAP_KEY: Schema<'static> =
1419            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1420        static MAP_VALUE: Schema<'static> = Schema::new_member(
1421            shape_id!("test", "M$value"),
1422            ShapeType::Structure,
1423            "value",
1424            1,
1425        );
1426        static MAP_SCHEMA: Schema<'static> =
1427            Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
1428
1429        let xml = concat!(
1430            "<S>",
1431            "<item><!--><item>-->a</item>",
1432            "<item><![CDATA[><item>]]></item>",
1433            "<item><?pi <item>?>c</item>",
1434            r#"<item x="/>">d</item>"#,
1435            "<people>",
1436            "<member><!--><member>--><name>p1</name></member>",
1437            r#"<member x="/>"><![CDATA[<member>]]><name>p2</name></member>"#,
1438            "</people>",
1439            "<m><entry><key>k</key><value><!--><value>--><name>v</name></value></entry></m>",
1440            "</S>",
1441        );
1442        let mut deser = XmlDeserializer::new(xml.as_bytes(), Arc::new(XmlCodecSettings::default()));
1443
1444        fn read_name(d: &mut dyn ShapeDeserializer) -> Result<String, SerdeError> {
1445            let mut name = String::new();
1446            d.read_struct(&PERSON_SCHEMA, &mut |member, d| {
1447                if member.member_name() == Some("name") {
1448                    name = d.read_string(member)?;
1449                }
1450                Ok(())
1451            })?;
1452            Ok(name)
1453        }
1454
1455        let (mut items, mut people, mut map) = (Vec::new(), Vec::new(), Vec::new());
1456        deser
1457            .read_struct(&S_SCHEMA, &mut |member, d| {
1458                match member.member_name().unwrap() {
1459                    "items" => d.read_list(member, &mut |d| {
1460                        items.push(d.read_string(member)?);
1461                        Ok(())
1462                    })?,
1463                    "people" => d.read_list(&PEOPLE_SCHEMA, &mut |d| {
1464                        people.push(read_name(d)?);
1465                        Ok(())
1466                    })?,
1467                    "m" => d.read_map(&MAP_SCHEMA, &mut |k, d| {
1468                        map.push((k, read_name(d)?));
1469                        Ok(())
1470                    })?,
1471                    _ => {}
1472                }
1473                Ok(())
1474            })
1475            .unwrap();
1476
1477        assert_eq!(items, vec!["a", "", "c", "d"]);
1478        assert_eq!(people, vec!["p1", "p2"]);
1479        assert_eq!(map, vec![("k".to_owned(), "v".to_owned())]);
1480    }
1481
1482    #[test]
1483    fn read_struct_flattened_list_intermixed() {
1484        // Flattened list elements intermixed with other members.
1485        let xml = b"<S><item>x</item><name>n</name><item>y</item></S>";
1486        let settings = Arc::new(XmlCodecSettings::default());
1487        let mut deser = XmlDeserializer::new(xml, settings);
1488
1489        static S_NAME: Schema<'static> =
1490            Schema::new_member(shape_id!("test", "S$name"), ShapeType::String, "name", 0);
1491        static S_ITEMS: Schema<'static> =
1492            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 1)
1493                .with_xml_flattened()
1494                .with_xml_name("item");
1495        static S_SCHEMA: Schema<'static> = Schema::new_struct(
1496            shape_id!("test", "S"),
1497            ShapeType::Structure,
1498            &[&S_NAME, &S_ITEMS],
1499        );
1500
1501        let mut name = String::new();
1502        let mut items = Vec::new();
1503        deser
1504            .read_struct(&S_SCHEMA, &mut |member, d| {
1505                match member.member_name().unwrap() {
1506                    "name" => name = d.read_string(member)?,
1507                    "items" => d.read_list(member, &mut |d| {
1508                        items.push(d.read_string(member)?);
1509                        Ok(())
1510                    })?,
1511                    _ => {}
1512                }
1513                Ok(())
1514            })
1515            .unwrap();
1516
1517        assert_eq!(name, "n");
1518        assert_eq!(items, vec!["x", "y"]);
1519    }
1520
1521    #[test]
1522    fn read_struct_flattened_map() {
1523        // Flattened map: each `<attr>` sibling is an entry, whatever its name;
1524        // an `<entry>` sibling is not a member of the structure and is skipped.
1525        let xml = b"<S><attr><key>a</key><value>1</value></attr><name>n</name>\
1526                    <entry><key>x</key><value>9</value></entry>\
1527                    <attr><key>b</key><value>2</value></attr></S>";
1528
1529        static MAP_KEY: Schema<'static> =
1530            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1531        static MAP_VALUE: Schema<'static> =
1532            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1);
1533        static S_NAME: Schema<'static> =
1534            Schema::new_member(shape_id!("test", "S$name"), ShapeType::String, "name", 0);
1535        static S_ATTRS: Schema<'static> =
1536            Schema::new_member(shape_id!("test", "S$attrs"), ShapeType::Map, "attrs", 1)
1537                .with_map_members(&MAP_KEY, &MAP_VALUE)
1538                .with_xml_flattened()
1539                .with_xml_name("attr");
1540        static S_SCHEMA: Schema<'static> = Schema::new_struct(
1541            shape_id!("test", "S"),
1542            ShapeType::Structure,
1543            &[&S_NAME, &S_ATTRS],
1544        );
1545
1546        for (helper, check_names) in [(false, false), (true, false), (false, true), (true, true)] {
1547            let settings = XmlCodecSettings::builder()
1548                .strict_collection_element_names(check_names)
1549                .build();
1550            let mut deser = XmlDeserializer::new(xml, Arc::new(settings));
1551            let mut attrs = Vec::new();
1552            deser
1553                .read_struct(&S_SCHEMA, &mut |member, d| {
1554                    if member.member_name() == Some("attrs") {
1555                        if helper {
1556                            attrs.extend(d.read_string_string_map(member)?);
1557                        } else {
1558                            d.read_map(member, &mut |k, d| {
1559                                attrs.push((k, d.read_string(&MAP_VALUE)?));
1560                                Ok(())
1561                            })?;
1562                        }
1563                    }
1564                    Ok(())
1565                })
1566                .unwrap();
1567            attrs.sort();
1568            assert_eq!(
1569                attrs,
1570                vec![
1571                    ("a".to_owned(), "1".to_owned()),
1572                    ("b".to_owned(), "2".to_owned())
1573                ]
1574            );
1575        }
1576    }
1577
1578    // Scalar reads (booleans, ints, floats, blob, timestamp) and document rejection.
1579
1580    #[test]
1581    fn read_scalars() {
1582        let settings = Arc::new(XmlCodecSettings::default());
1583
1584        let mut d = XmlDeserializer::from_text(Cow::Borrowed("true"), settings.clone());
1585        assert!(d.read_boolean(&STRING_MEMBER).unwrap());
1586
1587        let mut d = XmlDeserializer::from_text(Cow::Borrowed("-42"), settings.clone());
1588        assert_eq!(d.read_integer(&STRING_MEMBER).unwrap(), -42);
1589
1590        let mut d = XmlDeserializer::from_text(Cow::Borrowed("NaN"), settings.clone());
1591        assert!(d.read_float(&STRING_MEMBER).unwrap().is_nan());
1592
1593        let mut d = XmlDeserializer::from_text(Cow::Borrowed("Infinity"), settings.clone());
1594        assert_eq!(d.read_double(&STRING_MEMBER).unwrap(), f64::INFINITY);
1595
1596        let mut d = XmlDeserializer::from_text(Cow::Borrowed("aGVsbG8="), settings.clone());
1597        assert_eq!(d.read_blob(&STRING_MEMBER).unwrap().as_ref(), b"hello");
1598
1599        let mut d =
1600            XmlDeserializer::from_text(Cow::Borrowed("2023-04-01T12:00:00Z"), settings.clone());
1601        let ts = d.read_timestamp(&STRING_MEMBER).unwrap();
1602        assert_eq!(ts.secs(), 1680350400);
1603    }
1604
1605    #[test]
1606    fn read_document_returns_error() {
1607        let settings = Arc::new(XmlCodecSettings::default());
1608        let mut deser = XmlDeserializer::from_text(Cow::Borrowed("x"), settings);
1609        assert_eq!(
1610            deser.read_document(&STRING_MEMBER).unwrap_err().to_string(),
1611            "document types are not supported by REST XML"
1612        );
1613    }
1614
1615    // Empty body → error. The XML codec is strict here because XML 1.0
1616    // requires every document to have a root element. Consumers (e.g., S3
1617    // HEAD operations) whose output struct has no body-bound members rely
1618    // on the HTTP response composite skipping the body deserializer
1619    // entirely, so they never reach `read_struct`. Operations that DO have body-bound members and
1620    // receive an empty body are responding to a malformed wire format —
1621    // the deserializer surfaces that as an error rather than silently
1622    // returning a default-built struct (which the legacy XML parser also
1623    // did not do).
1624    #[test]
1625    fn read_struct_empty_body_errors() {
1626        let settings = Arc::new(XmlCodecSettings::default());
1627        let mut deser = XmlDeserializer::new(b"", settings);
1628        let err = deser
1629            .read_struct(&PERSON_SCHEMA, &mut |_member, _d| Ok(()))
1630            .expect_err("empty body must be rejected by read_struct");
1631        let _ = format!("{err}");
1632    }
1633
1634    #[test]
1635    fn read_list_empty_body_errors() {
1636        let settings = Arc::new(XmlCodecSettings::default());
1637        let mut deser = XmlDeserializer::new(b"", settings);
1638        deser
1639            .read_list(&PERSON_SCHEMA, &mut |_d| Ok(()))
1640            .expect_err("empty body must be rejected by read_list");
1641    }
1642
1643    #[test]
1644    fn read_map_empty_body_errors() {
1645        let settings = Arc::new(XmlCodecSettings::default());
1646        let mut deser = XmlDeserializer::new(b"", settings);
1647        deser
1648            .read_map(&PERSON_SCHEMA, &mut |_k, _d| Ok(()))
1649            .expect_err("empty body must be rejected by read_map");
1650    }
1651
1652    // Recursion-depth guard tests. These pin the read_struct / read_list /
1653    // read_map paths to a small custom `max_depth` so we can exercise the
1654    // overflow path without constructing pathologically large XML.
1655
1656    /// Lower max_depth to `n` for the test deserializer.
1657    fn settings_with_max_depth(n: u32) -> Arc<XmlCodecSettings> {
1658        Arc::new(XmlCodecSettings::builder().max_depth(n).build())
1659    }
1660
1661    /// Build a `<r>(<r>)*N(value)(</r>)*N` chain `depth` levels deep.
1662    fn nested_struct_xml(depth: u32) -> Vec<u8> {
1663        let mut s = String::new();
1664        for _ in 0..depth {
1665            s.push_str("<r>");
1666        }
1667        s.push('v');
1668        for _ in 0..depth {
1669            s.push_str("</r>");
1670        }
1671        s.into_bytes()
1672    }
1673
1674    #[test]
1675    fn read_struct_rejects_overdeep_payloads() {
1676        // Self-referential schema: `R { r: R }`. Each `read_struct` with the
1677        // same schema increments depth and recurses one level via the
1678        // member dispatch.
1679        static R_MEMBER_SELF: Schema<'static> =
1680            Schema::new_member(shape_id!("test", "R$r"), ShapeType::Structure, "r", 0);
1681        static R_SCHEMA: Schema<'static> = Schema::new_struct(
1682            shape_id!("test", "R"),
1683            ShapeType::Structure,
1684            &[&R_MEMBER_SELF],
1685        );
1686
1687        // Tighter limit so the test is fast.
1688        let max = 4;
1689        let xml = nested_struct_xml(max + 2);
1690        let mut deser = XmlDeserializer::new(&xml, settings_with_max_depth(max));
1691
1692        // Recursive consumer: each entry into `<r>` calls read_struct again.
1693        fn consume(_m: &Schema<'_>, d: &mut dyn ShapeDeserializer) -> Result<(), SerdeError> {
1694            d.read_struct(&R_SCHEMA, &mut consume)
1695        }
1696        let err = deser
1697            .read_struct(&R_SCHEMA, &mut consume)
1698            .expect_err("must reject payload exceeding max_depth");
1699        assert!(
1700            format!("{err}").contains("maximum nesting depth exceeded"),
1701            "expected depth-exceeded error, got: {err}"
1702        );
1703    }
1704
1705    #[test]
1706    fn read_struct_accepts_payloads_up_to_max_depth() {
1707        // Inverse of the above: at exactly `max_depth` we should succeed.
1708        // Walking the chain `max_depth` times consumes `max_depth` enter
1709        // calls (the outermost is the test's own call, inner consumer
1710        // recursions add one each).
1711        static R_MEMBER_SELF: Schema<'static> =
1712            Schema::new_member(shape_id!("test", "R$r"), ShapeType::Structure, "r", 0);
1713        static R_SCHEMA: Schema<'static> = Schema::new_struct(
1714            shape_id!("test", "R"),
1715            ShapeType::Structure,
1716            &[&R_MEMBER_SELF],
1717        );
1718
1719        // 4 nested `<r>` opens; the consumer recurses for each `<r>` it
1720        // encounters as a child member. At depth=max we stop recursing
1721        // (consumer only recurses when it sees an `<r>` child element).
1722        let max = 4;
1723        let xml = nested_struct_xml(max);
1724        let mut deser = XmlDeserializer::new(&xml, settings_with_max_depth(max));
1725
1726        let mut depth_seen = 0u32;
1727        fn consume(
1728            _m: &Schema<'_>,
1729            d: &mut dyn ShapeDeserializer,
1730            depth_seen: &mut u32,
1731        ) -> Result<(), SerdeError> {
1732            *depth_seen += 1;
1733            d.read_struct(&R_SCHEMA, &mut |m, d2| consume(m, d2, depth_seen))
1734        }
1735
1736        deser
1737            .read_struct(&R_SCHEMA, &mut |m, d| consume(m, d, &mut depth_seen))
1738            .expect("payload at exactly max_depth must succeed");
1739    }
1740
1741    #[test]
1742    fn read_list_rejects_overdeep_payloads() {
1743        // Nested lists: `<l><l><l>...</l></l></l>` exceeds max_depth.
1744        static L_MEMBER: Schema<'static> =
1745            Schema::new_member(shape_id!("test", "L$member"), ShapeType::List, "member", 0);
1746        static L_SCHEMA: Schema<'static> = Schema::new_list(shape_id!("test", "L"), &L_MEMBER);
1747
1748        let max = 3;
1749        // 5 levels of `<l>` — exceeds 3.
1750        let xml = b"<l><l><l><l><l/></l></l></l></l>";
1751        let mut deser = XmlDeserializer::new(xml, settings_with_max_depth(max));
1752
1753        fn consume(d: &mut dyn ShapeDeserializer) -> Result<(), SerdeError> {
1754            d.read_list(&L_SCHEMA, &mut consume)
1755        }
1756        let err = deser
1757            .read_list(&L_SCHEMA, &mut consume)
1758            .expect_err("nested-list payload exceeding max_depth must error");
1759        assert!(
1760            format!("{err}").contains("maximum nesting depth exceeded"),
1761            "expected depth-exceeded error, got: {err}"
1762        );
1763    }
1764
1765    #[test]
1766    fn depth_resets_between_sibling_reads() {
1767        // After a successful aggregate read, the depth counter must return
1768        // to its prior value so the next sibling read isn't poisoned.
1769        static R_MEMBER_SELF: Schema<'static> =
1770            Schema::new_member(shape_id!("test", "R$r"), ShapeType::Structure, "r", 0);
1771        static R_SCHEMA: Schema<'static> = Schema::new_struct(
1772            shape_id!("test", "R"),
1773            ShapeType::Structure,
1774            &[&R_MEMBER_SELF],
1775        );
1776
1777        let max = 4;
1778        let xml = nested_struct_xml(2);
1779        let mut deser = XmlDeserializer::new(&xml, settings_with_max_depth(max));
1780
1781        // First read: 2 levels deep, well under max.
1782        deser
1783            .read_struct(&R_SCHEMA, &mut |_m, d| {
1784                d.read_struct(&R_SCHEMA, &mut |_, _| Ok(()))
1785            })
1786            .expect("first read at depth=2 must succeed");
1787
1788        // Second read on the same deserializer with the same payload:
1789        // would fail if depth had leaked from the first call.
1790        deser
1791            .read_struct(&R_SCHEMA, &mut |_m, d| {
1792                d.read_struct(&R_SCHEMA, &mut |_, _| Ok(()))
1793            })
1794            .expect("sibling read on the same deserializer must succeed");
1795    }
1796
1797    #[test]
1798    fn read_struct_rejects_overdeep_payloads_through_flattened_member() {
1799        // Regression: a shape that recurses through an @xmlFlattened list
1800        // member — `structure Node { @xmlFlattened kids: NodeList }` where
1801        // `NodeList` is a list of `Node` — must still be depth-limited.
1802        //
1803        // Flattened groups are dispatched through a freshly spawned
1804        // deserializer (the synthesized `<__flat>` wrapper is owned locally
1805        // and can't route through `dispatch_subslice`). That child must
1806        // inherit the parent's depth via `new_child`; if it instead started
1807        // at 0 (as a plain `XmlDeserializer::new` would), the counter would
1808        // reset on every flattened hop and this recursion would nest without
1809        // bound, overflowing the worker stack instead of returning an error.
1810        static NODE_KIDS: Schema<'static> =
1811            Schema::new_member(shape_id!("test", "Node$kids"), ShapeType::List, "kids", 0)
1812                .with_xml_flattened()
1813                .with_xml_name("kids");
1814        static NODE_SCHEMA: Schema<'static> = Schema::new_struct(
1815            shape_id!("test", "Node"),
1816            ShapeType::Structure,
1817            &[&NODE_KIDS],
1818        );
1819
1820        // Deeply nested flattened `<kids>` chain, far beyond `max`. Each
1821        // `Node` level costs two enter_aggregate calls (its `read_struct`
1822        // plus the flattened list's `read_list`), so `max = 4` rejects within
1823        // a handful of levels; 50 levels guarantees we cross it.
1824        let max = 4;
1825        let depth = 50usize;
1826        let mut xml = Vec::new();
1827        xml.extend_from_slice(b"<Node>");
1828        for _ in 0..depth {
1829            xml.extend_from_slice(b"<kids>");
1830        }
1831        for _ in 0..depth {
1832            xml.extend_from_slice(b"</kids>");
1833        }
1834        xml.extend_from_slice(b"</Node>");
1835
1836        let mut deser = XmlDeserializer::new(&xml, settings_with_max_depth(max));
1837
1838        // Each `<kids>` list item is itself a `Node`, so the list consumer
1839        // recurses back into `read_struct`.
1840        fn consume(_m: &Schema<'_>, d: &mut dyn ShapeDeserializer) -> Result<(), SerdeError> {
1841            d.read_struct(&NODE_SCHEMA, &mut |member, d| {
1842                d.read_list(member, &mut |d| consume(&NODE_SCHEMA, d))
1843            })
1844        }
1845
1846        let err = consume(&NODE_SCHEMA, &mut deser)
1847            .expect_err("recursion through a flattened member must be depth-limited");
1848        assert!(
1849            format!("{err}").contains("maximum nesting depth exceeded"),
1850            "expected depth-exceeded error, got: {err}"
1851        );
1852    }
1853
1854    #[test]
1855    fn depth_resets_after_consumer_error() {
1856        // Regression: prior to the IIFE refactor in `read_struct` /
1857        // `read_list` / `read_map` / the 5 collection helpers, a `?`
1858        // propagating out of an aggregate read body skipped the trailing
1859        // `self.leave_aggregate()`, leaking +1 on the depth counter. A
1860        // second read on the same deserializer would then fail with
1861        // "maximum nesting depth exceeded" on a payload well within
1862        // limits.
1863        //
1864        // We force the error via a consumer that always returns Err
1865        // (rather than relying on a malformed XML payload — which would
1866        // also fail on the second read for the same wire-level reason
1867        // and so wouldn't isolate the depth-counter bug).
1868        static L_MEMBER: Schema<'static> = Schema::new_member(
1869            shape_id!("test", "L$member"),
1870            ShapeType::String,
1871            "member",
1872            0,
1873        );
1874        static L_SCHEMA: Schema<'static> = Schema::new_list(shape_id!("test", "L"), &L_MEMBER);
1875
1876        // max_depth=2 means we can enter at most 2 levels of aggregates
1877        // before erroring. With the leak, after one errored aggregate
1878        // read at depth=1, depth stays at 1, and a second read enters
1879        // at depth=2 — still OK. After two errored reads we'd be at
1880        // depth=2, and a third would push to depth=3 and trip the limit.
1881        let max = 2;
1882        let xml = b"<l><member>a</member><member>b</member></l>";
1883        let mut deser = XmlDeserializer::new(xml, settings_with_max_depth(max));
1884
1885        // Force an error from the consumer on the very first element.
1886        let mut force_err = |_: &mut dyn ShapeDeserializer| -> Result<(), SerdeError> {
1887            Err(SerdeError::custom("forced"))
1888        };
1889        deser
1890            .read_list(&L_SCHEMA, &mut force_err)
1891            .expect_err("forced consumer error must propagate");
1892
1893        // Repeat enough times that, with a leak of +1 per call, the depth
1894        // counter would exceed max_depth. With the fix, every iteration
1895        // ends with depth=0 and this loop is fine.
1896        for i in 0..(max as usize + 5) {
1897            deser
1898                .read_list(&L_SCHEMA, &mut force_err)
1899                .expect_err(&format!("forced consumer error must propagate (iter {i})"));
1900        }
1901
1902        // A successful (no-op) read must still work — proving the counter
1903        // was decremented correctly through all the error iterations.
1904        let mut count = 0usize;
1905        deser
1906            .read_list(&L_SCHEMA, &mut |d| {
1907                count += 1;
1908                d.read_string(&L_MEMBER).map(|_| ())
1909            })
1910            .expect("subsequent successful read must not be poisoned by prior errors");
1911        assert_eq!(count, 2);
1912    }
1913
1914    #[test]
1915    fn read_struct_unwrapped_output_with_prolog() {
1916        // Regression: in the unwrapped-output path of `read_struct`, an
1917        // earlier version called the element-slicing helper with a heap-allocated
1918        // `String` instead of a `&str` borrowing from `input`. The
1919        // pointer-arithmetic invariant broke; only the
1920        // `.saturating_sub.min` clamping prevented UB. The result was
1921        // silently correct only when the target element happened to start
1922        // at offset 0 of the input.
1923        //
1924        // Constructing a payload with an XML prolog (so the element is NOT
1925        // at offset 0) verifies the fixed code passes a real sub-slice of
1926        // `input` to `element_start`. The `debug_assert!` in
1927        // `element_start` would also fire under the old code in
1928        // debug builds.
1929        static MEMBER: Schema<'static> = Schema::new_member(
1930            shape_id!("test", "U$location"),
1931            ShapeType::String,
1932            "LocationConstraint",
1933            0,
1934        );
1935        static SCHEMA: Schema<'static> =
1936            Schema::new_struct(shape_id!("test", "U"), ShapeType::Structure, &[&MEMBER])
1937                .with_xml_unwrapped_output();
1938
1939        let xml = b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<LocationConstraint>us-west-2</LocationConstraint>";
1940        let mut deser = XmlDeserializer::new(xml, Arc::new(XmlCodecSettings::default()));
1941
1942        let mut got = String::new();
1943        deser
1944            .read_struct(&SCHEMA, &mut |member, d| {
1945                got = d.read_string(member)?;
1946                Ok(())
1947            })
1948            .expect("unwrapped output must deserialize through prolog");
1949        assert_eq!(got, "us-west-2");
1950    }
1951
1952    /// Regression test for a UTF-8 char-boundary panic in an earlier byte
1953    /// scanner that located element boundaries.
1954    ///
1955    /// Found via `schema_xml_roundtrip` fuzz target on input
1956    /// `StringStringMap([("Б", "")])`. The serialized payload contains a
1957    /// Cyrillic `Б` (UTF-8 bytes `0xD0 0x91`) inside a map-key element. The
1958    /// previous implementation operated on `&str` and advanced its scan
1959    /// cursor by one byte per non-`<` character, which landed mid-char on
1960    /// the second byte of `Б` and panicked with
1961    /// `start byte index N is not a char boundary`. Element boundaries now
1962    /// come from the tokenizer's offsets, which always land on a `<` or just
1963    /// past a `>`.
1964    #[test]
1965    fn element_slicing_handles_multibyte_utf8() {
1966        // Build a struct-with-map XML payload containing Cyrillic text in a
1967        // map-key element. The exact wire form matches what `XmlSerializer`
1968        // emits for `StringStringMap([("Б", "")])` wrapped in
1969        // `WRAPPER_STRING_STRING_MAP_SCHEMA`.
1970        static KEY_MEMBER: Schema<'static> =
1971            Schema::new_member(shape_id!("test", "Wrapper$k"), ShapeType::Map, "entries", 0);
1972        static MAP_SCHEMA: Schema<'static> = Schema::new_map(
1973            shape_id!("test", "MyMap"),
1974            &aws_smithy_schema::prelude::STRING,
1975            &aws_smithy_schema::prelude::STRING,
1976        );
1977        static WRAPPER: Schema<'static> = Schema::new_struct(
1978            shape_id!("test", "Wrapper"),
1979            ShapeType::Structure,
1980            &[&KEY_MEMBER],
1981        );
1982        let _ = MAP_SCHEMA; // referenced for documentation; not directly used
1983
1984        let xml =
1985            "<Wrapper><entries><entry><key>Б</key><value></value></entry></entries></Wrapper>";
1986
1987        let settings = Arc::new(XmlCodecSettings::default());
1988        let mut deser = XmlDeserializer::new(xml.as_bytes(), settings);
1989
1990        let mut entries = Vec::new();
1991        deser
1992            .read_struct(&WRAPPER, &mut |member, d| {
1993                d.read_map(member, &mut |k, d| {
1994                    entries.push((k, d.read_string(&aws_smithy_schema::prelude::STRING)?));
1995                    Ok(())
1996                })
1997            })
1998            .expect("must not panic on multi-byte UTF-8 inside map elements");
1999
2000        assert_eq!(entries, vec![("Б".to_owned(), String::new())]);
2001    }
2002
2003    /// Slice each child of the root element the way the aggregate readers do:
2004    /// start from `element_start`, end at the child scope's `end_offset`.
2005    fn child_slices(input: &[u8]) -> Vec<&[u8]> {
2006        let mut doc = Document::try_from(input).unwrap();
2007        let mut root = doc.root_element().unwrap();
2008        let mut out = Vec::new();
2009        while let Some(child) = root.next_tag() {
2010            let start = XmlDeserializer::element_start(input, child.start_el().local());
2011            out.push(&input[start..child.end_offset()]);
2012        }
2013        out
2014    }
2015
2016    /// Element slices must match the element exactly for prefixed names,
2017    /// whitespace in tags, and mismatched prefixes on the close tag.
2018    #[test]
2019    fn element_slices_prefixed_and_whitespace() {
2020        let input = br#"<Root><a:flatList xmlns:a="u">x</a:flatList><a:flatList xmlns:a="u">y</a:flatList></Root>"#;
2021        assert_eq!(
2022            child_slices(input),
2023            vec![
2024                &br#"<a:flatList xmlns:a="u">x</a:flatList>"#[..],
2025                &br#"<a:flatList xmlns:a="u">y</a:flatList>"#[..],
2026            ],
2027        );
2028        let ws = b"<Root><flatList >x</flatList ><flatList>y</flatList></Root>";
2029        assert_eq!(
2030            child_slices(ws),
2031            vec![
2032                &b"<flatList >x</flatList >"[..],
2033                &b"<flatList>y</flatList>"[..]
2034            ],
2035        );
2036        let empty = b"<Root><a/><b x=\"1\" /></Root>";
2037        assert_eq!(
2038            child_slices(empty),
2039            vec![&b"<a/>"[..], &b"<b x=\"1\" />"[..]]
2040        );
2041    }
2042
2043    /// Regression (proofs 51/52): markup-like text inside comments, CDATA,
2044    /// processing instructions and attribute values must not shift element
2045    /// boundaries. The old byte scanner saw `<flatList>` inside `<!-- -->` as
2046    /// an open tag, never found the matching close, and returned the rest of
2047    /// the document for every sibling (quadratic memory and CPU).
2048    #[test]
2049    fn element_slices_ignore_markup_in_comments_cdata_pi_attrs() {
2050        for item in [
2051            &b"<flatList><!--><flatList>--></flatList>"[..],
2052            b"<flatList><![CDATA[><flatList>]]></flatList>",
2053            b"<flatList><?pi <flatList>?>x</flatList>",
2054            b"<flatList a=\"/>\">x</flatList>",
2055            b"<flatList><!-- </flatList> -->x</flatList>",
2056        ] {
2057            let mut input = b"<Root>".to_vec();
2058            for _ in 0..3 {
2059                input.extend_from_slice(item);
2060            }
2061            input.extend_from_slice(b"</Root>");
2062            assert_eq!(
2063                child_slices(&input),
2064                vec![item; 3],
2065                "{}",
2066                String::from_utf8_lossy(item)
2067            );
2068        }
2069    }
2070
2071    #[test]
2072    fn find_depth2_by_predicate_wrapped() {
2073        let xml = b"<Resp><FooResult><A>1</A></FooResult><Metadata/></Resp>";
2074        let got = find_depth2_element_slice_by(xml, |n| n.ends_with("Result"));
2075        assert_eq!(got, Some(&b"<FooResult><A>1</A></FooResult>"[..]));
2076    }
2077
2078    #[test]
2079    fn find_depth2_by_predicate_self_closing() {
2080        // Regression: a self-closing target element must return just `<Foo/>`,
2081        // not everything from the element to the end of the document.
2082        let xml = b"<Resp><FooResult/><Metadata><Id>r</Id></Metadata></Resp>";
2083        let got = find_depth2_element_slice_by(xml, |n| n.ends_with("Result"));
2084        assert_eq!(got, Some(&b"<FooResult/>"[..]));
2085    }
2086
2087    #[test]
2088    fn find_depth2_by_predicate_root_match() {
2089        // Unwrapped envelope: the root itself matches — return the whole body.
2090        let xml = b"<Error><Code>Boom</Code></Error>";
2091        let got = find_depth2_element_slice_by(xml, |n| n == "Error");
2092        assert_eq!(got, Some(&xml[..]));
2093    }
2094
2095    #[test]
2096    fn find_depth2_by_predicate_no_match() {
2097        let xml = b"<Resp><Metadata/></Resp>";
2098        assert_eq!(find_depth2_element_slice_by(xml, |n| n == "Error"), None);
2099    }
2100
2101    #[test]
2102    fn find_depth2_by_predicate_invalid_xml() {
2103        assert_eq!(find_depth2_element_slice_by(b"not xml", |_| true), None);
2104    }
2105
2106    // -------- Specialized collection-helper override tests --------
2107    //
2108    // These exercise the inlined `read_*_list` / `read_string_string_map`
2109    // overrides on `XmlDeserializer`, confirming behavioral parity with
2110    // the trait's default-impl path (which goes through `read_list` /
2111    // `read_map` + `&mut dyn ShapeDeserializer`).
2112
2113    #[test]
2114    fn read_string_list_helper() {
2115        let xml = b"<items><member>a</member><member>b</member><member></member></items>";
2116        let settings = Arc::new(XmlCodecSettings::default());
2117        let mut deser = XmlDeserializer::new(xml, settings);
2118
2119        static LIST_MEMBER: Schema<'static> = Schema::new_member(
2120            shape_id!("test", "L$member"),
2121            ShapeType::String,
2122            "member",
2123            0,
2124        );
2125        static LIST_SCHEMA: Schema<'static> =
2126            Schema::new_list(shape_id!("test", "L"), &LIST_MEMBER);
2127
2128        let out = deser.read_string_list(&LIST_SCHEMA).unwrap();
2129        assert_eq!(out, vec!["a".to_owned(), "b".to_owned(), String::new()]);
2130    }
2131
2132    #[test]
2133    fn read_string_list_helper_renamed_member_name() {
2134        // Codegen-emitted call site for a list whose member shape has
2135        // `@xmlName("Item")`. Like `read_list`, the helper does not
2136        // validate child element names against the schema — it accepts
2137        // whatever the wire form provides. This matches the default impl.
2138        let xml = b"<items><Item>x</Item><Item>y</Item></items>";
2139        let settings = Arc::new(XmlCodecSettings::default());
2140        let mut deser = XmlDeserializer::new(xml, settings);
2141
2142        static LIST_MEMBER: Schema<'static> = Schema::new_member(
2143            shape_id!("test", "L$member"),
2144            ShapeType::String,
2145            "member",
2146            0,
2147        );
2148        static LIST_SCHEMA: Schema<'static> =
2149            Schema::new_list(shape_id!("test", "L"), &LIST_MEMBER);
2150
2151        let out = deser.read_string_list(&LIST_SCHEMA).unwrap();
2152        assert_eq!(out, vec!["x".to_owned(), "y".to_owned()]);
2153    }
2154
2155    /// Settings with `strict_collection_element_names` set to `check_names`.
2156    /// Without it they still enforce strictness, as a server does: strictness
2157    /// alone does not make a read check collection element names.
2158    fn element_name_settings(check_names: bool) -> Arc<XmlCodecSettings> {
2159        Arc::new(
2160            XmlCodecSettings::builder()
2161                .enforce_strictness(!check_names)
2162                .strict_collection_element_names(check_names)
2163                .build(),
2164        )
2165    }
2166
2167    /// With `strict_collection_element_names`, a wrapped list takes only the
2168    /// children named as its member, as the legacy generated parsers do.
2169    /// Without it every child is an item, whether or not strictness is
2170    /// enforced.
2171    #[test]
2172    fn element_name_check_skips_list_children_not_named_as_the_member() {
2173        static STRING_MEMBER: Schema<'static> = Schema::new_member(
2174            shape_id!("test", "L$member"),
2175            ShapeType::String,
2176            "member",
2177            0,
2178        );
2179        static STRING_LIST: Schema<'static> =
2180            Schema::new_list(shape_id!("test", "L"), &STRING_MEMBER);
2181        // A structure member targeting the list, as codegen emits it.
2182        static STRUCT_MEMBER: Schema<'static> =
2183            Schema::new_member(shape_id!("test", "S$tags"), ShapeType::List, "tags", 0)
2184                .with_list_member(&STRING_MEMBER);
2185        static RENAMED_MEMBER: Schema<'static> = Schema::new_member(
2186            shape_id!("test", "R$member"),
2187            ShapeType::String,
2188            "member",
2189            0,
2190        )
2191        .with_xml_name("Item");
2192        static RENAMED_LIST: Schema<'static> =
2193            Schema::new_list(shape_id!("test", "R"), &RENAMED_MEMBER);
2194        static BLOB_MEMBER: Schema<'static> =
2195            Schema::new_member(shape_id!("test", "B$member"), ShapeType::Blob, "member", 0);
2196        static BLOB_LIST: Schema<'static> = Schema::new_list(shape_id!("test", "B"), &BLOB_MEMBER);
2197        static INT_MEMBER: Schema<'static> = Schema::new_member(
2198            shape_id!("test", "I$member"),
2199            ShapeType::Integer,
2200            "member",
2201            0,
2202        );
2203        static INT_LIST: Schema<'static> = Schema::new_list(shape_id!("test", "I"), &INT_MEMBER);
2204        static LONG_MEMBER: Schema<'static> =
2205            Schema::new_member(shape_id!("test", "Lo$member"), ShapeType::Long, "member", 0);
2206        static LONG_LIST: Schema<'static> = Schema::new_list(shape_id!("test", "Lo"), &LONG_MEMBER);
2207
2208        fn deser(xml: &'static str, check_names: bool) -> XmlDeserializer<'static> {
2209            XmlDeserializer::new(xml.as_bytes(), element_name_settings(check_names))
2210        }
2211        fn read_list(xml: &'static str, check_names: bool, schema: &Schema<'_>) -> Vec<String> {
2212            let mut items = Vec::new();
2213            deser(xml, check_names)
2214                .read_list(schema, &mut |d| {
2215                    items.push(d.read_string(&STRING_MEMBER)?);
2216                    Ok(())
2217                })
2218                .unwrap();
2219            items
2220        }
2221
2222        let strings =
2223            "<tags><item>a</item><member>b</member><Member>c</Member><p:member>d</p:member></tags>";
2224        for schema in [&STRING_LIST, &STRUCT_MEMBER] {
2225            assert_eq!(
2226                deser(strings, true).read_string_list(schema).unwrap(),
2227                vec!["b", "d"]
2228            );
2229            assert_eq!(read_list(strings, true, schema), vec!["b", "d"]);
2230            assert_eq!(
2231                deser(strings, false).read_string_list(schema).unwrap(),
2232                vec!["a", "b", "c", "d"]
2233            );
2234            assert_eq!(read_list(strings, false, schema), vec!["a", "b", "c", "d"]);
2235        }
2236
2237        let renamed = "<l><Item>x</Item><member>skipped</member><Item>y</Item></l>";
2238        assert_eq!(
2239            deser(renamed, true)
2240                .read_string_list(&RENAMED_LIST)
2241                .unwrap(),
2242            vec!["x", "y"]
2243        );
2244        assert_eq!(
2245            deser(renamed, false)
2246                .read_string_list(&RENAMED_LIST)
2247                .unwrap(),
2248            vec!["x", "skipped", "y"]
2249        );
2250
2251        // A skipped child is not decoded, so its content cannot fail the read.
2252        let blobs = deser("<l><x>!!!</x><member>aGVsbG8=</member></l>", true)
2253            .read_blob_list(&BLOB_LIST)
2254            .unwrap();
2255        assert_eq!(blobs, vec![Blob::new("hello")]);
2256        assert_eq!(
2257            deser("<l><x>nan</x><member>7</member></l>", true)
2258                .read_integer_list(&INT_LIST)
2259                .unwrap(),
2260            vec![7]
2261        );
2262        assert_eq!(
2263            deser("<l><x><deep/></x><member>-9</member></l>", true)
2264                .read_long_list(&LONG_LIST)
2265                .unwrap(),
2266            vec![-9]
2267        );
2268    }
2269
2270    /// A schema that does not describe the list's member (the placeholder
2271    /// codegen falls back to when it has no sub-schema for a nested aggregate)
2272    /// gives no item name to check.
2273    #[test]
2274    fn element_name_check_without_a_member_schema_reads_every_child() {
2275        let xml = b"<l><a>1</a><b>2</b></l>";
2276        let mut deser = XmlDeserializer::new(xml, element_name_settings(true));
2277        let mut items = Vec::new();
2278        deser
2279            .read_list(&aws_smithy_schema::prelude::DOCUMENT, &mut |d| {
2280                items.push(d.read_string(&aws_smithy_schema::prelude::STRING)?);
2281                Ok(())
2282            })
2283            .unwrap();
2284        assert_eq!(items, vec!["1", "2"]);
2285    }
2286
2287    /// With `strict_collection_element_names`, a wrapped map takes only its
2288    /// `entry` children. Without it every child is an entry, whether or not
2289    /// strictness is enforced.
2290    #[test]
2291    fn element_name_check_skips_map_children_not_named_entry() {
2292        static MAP_KEY: Schema<'static> =
2293            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
2294        static MAP_VALUE: Schema<'static> =
2295            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1);
2296        static MAP_SCHEMA: Schema<'static> =
2297            Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
2298
2299        let xml = "<m><item><key>x</key><value>1</value></item>\
2300                   <entry><key>a</key><value>2</value></entry></m>";
2301
2302        for (settings, expected) in [
2303            (element_name_settings(true), vec![("a", "2")]),
2304            (element_name_settings(false), vec![("a", "2"), ("x", "1")]),
2305            (
2306                Arc::new(XmlCodecSettings::default()),
2307                vec![("a", "2"), ("x", "1")],
2308            ),
2309        ] {
2310            let expected: Vec<(String, String)> = expected
2311                .into_iter()
2312                .map(|(k, v)| (k.to_owned(), v.to_owned()))
2313                .collect();
2314
2315            let mut out: Vec<_> = XmlDeserializer::new(xml.as_bytes(), settings.clone())
2316                .read_string_string_map(&MAP_SCHEMA)
2317                .unwrap()
2318                .into_iter()
2319                .collect();
2320            out.sort();
2321            assert_eq!(out, expected);
2322
2323            let mut entries = Vec::new();
2324            XmlDeserializer::new(xml.as_bytes(), settings)
2325                .read_map(&MAP_SCHEMA, &mut |k, d| {
2326                    entries.push((k, d.read_string(&MAP_VALUE)?));
2327                    Ok(())
2328                })
2329                .unwrap();
2330            entries.sort();
2331            assert_eq!(entries, expected);
2332        }
2333    }
2334
2335    /// The element-name check applies at every nesting level: each nested
2336    /// list or map is read against the sub-schema that names its own items.
2337    #[test]
2338    fn element_name_check_applies_to_nested_collections() {
2339        // `grid: list<list<String>>`, the inner member renamed to `Cell`.
2340        static CELL: Schema<'static> = Schema::new_member(
2341            shape_id!("test", "Row$member"),
2342            ShapeType::String,
2343            "member",
2344            0,
2345        )
2346        .with_xml_name("Cell");
2347        static ROW: Schema<'static> = Schema::new_member(
2348            shape_id!("test", "Grid$member"),
2349            ShapeType::List,
2350            "member",
2351            0,
2352        )
2353        .with_list_member(&CELL);
2354        static GRID: Schema<'static> =
2355            Schema::new_member(shape_id!("test", "S$grid"), ShapeType::List, "grid", 0)
2356                .with_list_member(&ROW);
2357        // `attrs: map<String, list<String>>`.
2358        static ATTR_KEY: Schema<'static> =
2359            Schema::new_member(shape_id!("test", "Attrs$key"), ShapeType::String, "key", 0);
2360        static ATTR_ITEM: Schema<'static> = Schema::new_member(
2361            shape_id!("test", "Values$member"),
2362            ShapeType::String,
2363            "member",
2364            0,
2365        );
2366        static ATTR_VALUE: Schema<'static> = Schema::new_member(
2367            shape_id!("test", "Attrs$value"),
2368            ShapeType::List,
2369            "value",
2370            1,
2371        )
2372        .with_list_member(&ATTR_ITEM);
2373        static ATTRS: Schema<'static> =
2374            Schema::new_member(shape_id!("test", "S$attrs"), ShapeType::Map, "attrs", 1)
2375                .with_map_members(&ATTR_KEY, &ATTR_VALUE);
2376
2377        fn grid(check_names: bool) -> Vec<Vec<String>> {
2378            let xml = b"<grid>\
2379                <member><Cell>1</Cell><member>x</member></member>\
2380                <row><Cell>9</Cell></row>\
2381                <member><Cell>2</Cell></member>\
2382                </grid>";
2383            let mut rows = Vec::new();
2384            XmlDeserializer::new(xml, element_name_settings(check_names))
2385                .read_list(&GRID, &mut |d| {
2386                    let mut row = Vec::new();
2387                    d.read_list(&ROW, &mut |d| {
2388                        row.push(d.read_string(&CELL)?);
2389                        Ok(())
2390                    })?;
2391                    rows.push(row);
2392                    Ok(())
2393                })
2394                .unwrap();
2395            rows
2396        }
2397        assert_eq!(grid(true), vec![vec!["1"], vec!["2"]]);
2398        assert_eq!(grid(false), vec![vec!["1", "x"], vec!["9"], vec!["2"]]);
2399
2400        fn attrs(check_names: bool) -> Vec<(String, Vec<String>)> {
2401            let xml = b"<attrs>\
2402                <entry><key>k</key><value><member>a</member><item>b</item></value></entry>\
2403                <other><key>z</key><value><member>q</member></value></other>\
2404                </attrs>";
2405            let mut entries = Vec::new();
2406            XmlDeserializer::new(xml, element_name_settings(check_names))
2407                .read_map(&ATTRS, &mut |key, d| {
2408                    entries.push((key, d.read_string_list(&ATTR_VALUE)?));
2409                    Ok(())
2410                })
2411                .unwrap();
2412            entries.sort();
2413            entries
2414        }
2415        assert_eq!(attrs(true), vec![("k".to_owned(), vec!["a".to_owned()])]);
2416        assert_eq!(
2417            attrs(false),
2418            vec![
2419                ("k".to_owned(), vec!["a".to_owned(), "b".to_owned()]),
2420                ("z".to_owned(), vec!["q".to_owned()]),
2421            ]
2422        );
2423    }
2424
2425    /// A recursive structure (`Node { name, children: list<Node> }`) is read
2426    /// against its own schema at every depth, so the check applies at each
2427    /// level of the tree.
2428    #[test]
2429    fn element_name_check_applies_to_recursive_structures() {
2430        static NODE_NAME: Schema<'static> =
2431            Schema::new_member(shape_id!("test", "Node$name"), ShapeType::String, "name", 0);
2432        static NODE_LIST_MEMBER: Schema<'static> = Schema::new_member(
2433            shape_id!("test", "NodeList$member"),
2434            ShapeType::Structure,
2435            "member",
2436            0,
2437        );
2438        static NODE_CHILDREN: Schema<'static> = Schema::new_member(
2439            shape_id!("test", "Node$children"),
2440            ShapeType::List,
2441            "children",
2442            1,
2443        )
2444        .with_list_member(&NODE_LIST_MEMBER);
2445        static NODE: Schema<'static> = Schema::new_struct(
2446            shape_id!("test", "Node"),
2447            ShapeType::Structure,
2448            &[&NODE_NAME, &NODE_CHILDREN],
2449        );
2450
2451        #[derive(Debug, PartialEq)]
2452        struct Node(String, Vec<Node>);
2453
2454        fn read_node(d: &mut dyn ShapeDeserializer) -> Result<Node, SerdeError> {
2455            let mut node = Node(String::new(), Vec::new());
2456            d.read_struct(&NODE, &mut |member, d| {
2457                match member.member_name() {
2458                    Some("name") => node.0 = d.read_string(member)?,
2459                    Some("children") => d.read_list(member, &mut |d| {
2460                        node.1.push(read_node(d)?);
2461                        Ok(())
2462                    })?,
2463                    _ => {}
2464                }
2465                Ok(())
2466            })?;
2467            Ok(node)
2468        }
2469        fn leaf(name: &str) -> Node {
2470            Node(name.to_owned(), Vec::new())
2471        }
2472
2473        let xml = b"<Node><name>root</name><children>\
2474            <member><name>a</name><children>\
2475                <bogus><name>x</name></bogus>\
2476                <member><name>a1</name></member>\
2477            </children></member>\
2478            <item><name>skipped</name></item>\
2479            <member><name>b</name></member>\
2480            </children></Node>";
2481        let read = |check_names: bool| {
2482            read_node(&mut XmlDeserializer::new(
2483                xml,
2484                element_name_settings(check_names),
2485            ))
2486            .unwrap()
2487        };
2488        assert_eq!(
2489            read(true),
2490            Node(
2491                "root".to_owned(),
2492                vec![Node("a".to_owned(), vec![leaf("a1")]), leaf("b")]
2493            )
2494        );
2495        assert_eq!(
2496            read(false),
2497            Node(
2498                "root".to_owned(),
2499                vec![
2500                    Node("a".to_owned(), vec![leaf("x"), leaf("a1")]),
2501                    leaf("skipped"),
2502                    leaf("b")
2503                ]
2504            )
2505        );
2506    }
2507
2508    #[test]
2509    fn read_blob_list_helper() {
2510        // Each element's text is base64-decoded into a Blob.
2511        let xml = b"<blobs><member>aGVsbG8=</member><member>d29ybGQ=</member></blobs>";
2512        let settings = Arc::new(XmlCodecSettings::default());
2513        let mut deser = XmlDeserializer::new(xml, settings);
2514
2515        static LIST_MEMBER: Schema<'static> =
2516            Schema::new_member(shape_id!("test", "B$member"), ShapeType::Blob, "member", 0);
2517        static LIST_SCHEMA: Schema<'static> =
2518            Schema::new_list(shape_id!("test", "B"), &LIST_MEMBER);
2519
2520        let out = deser.read_blob_list(&LIST_SCHEMA).unwrap();
2521        assert_eq!(out.len(), 2);
2522        assert_eq!(out[0].as_ref(), b"hello");
2523        assert_eq!(out[1].as_ref(), b"world");
2524    }
2525
2526    #[test]
2527    fn read_blob_list_helper_rejects_invalid_base64() {
2528        let xml = b"<blobs><member>!!!not-base64!!!</member></blobs>";
2529        let settings = Arc::new(XmlCodecSettings::default());
2530        let mut deser = XmlDeserializer::new(xml, settings);
2531
2532        static LIST_MEMBER: Schema<'static> =
2533            Schema::new_member(shape_id!("test", "B$member"), ShapeType::Blob, "member", 0);
2534        static LIST_SCHEMA: Schema<'static> =
2535            Schema::new_list(shape_id!("test", "B"), &LIST_MEMBER);
2536
2537        let err = deser.read_blob_list(&LIST_SCHEMA).unwrap_err();
2538        assert!(format!("{err}").contains("base64"));
2539    }
2540
2541    #[test]
2542    fn read_integer_list_helper() {
2543        let xml = b"<nums><member>1</member><member>-42</member><member>0</member></nums>";
2544        let settings = Arc::new(XmlCodecSettings::default());
2545        let mut deser = XmlDeserializer::new(xml, settings);
2546
2547        static LIST_MEMBER: Schema<'static> = Schema::new_member(
2548            shape_id!("test", "I$member"),
2549            ShapeType::Integer,
2550            "member",
2551            0,
2552        );
2553        static LIST_SCHEMA: Schema<'static> =
2554            Schema::new_list(shape_id!("test", "I"), &LIST_MEMBER);
2555
2556        let out = deser.read_integer_list(&LIST_SCHEMA).unwrap();
2557        assert_eq!(out, vec![1i32, -42, 0]);
2558    }
2559
2560    #[test]
2561    fn read_long_list_helper() {
2562        let xml = b"<nums><member>9223372036854775807</member><member>-1</member></nums>";
2563        let settings = Arc::new(XmlCodecSettings::default());
2564        let mut deser = XmlDeserializer::new(xml, settings);
2565
2566        static LIST_MEMBER: Schema<'static> =
2567            Schema::new_member(shape_id!("test", "Lo$member"), ShapeType::Long, "member", 0);
2568        static LIST_SCHEMA: Schema<'static> =
2569            Schema::new_list(shape_id!("test", "Lo"), &LIST_MEMBER);
2570
2571        let out = deser.read_long_list(&LIST_SCHEMA).unwrap();
2572        assert_eq!(out, vec![i64::MAX, -1]);
2573    }
2574
2575    #[test]
2576    fn read_string_string_map_helper() {
2577        let xml = b"<m><entry><key>a</key><value>1</value></entry>\
2578                    <entry><key>b</key><value>2</value></entry></m>";
2579        let settings = Arc::new(XmlCodecSettings::default());
2580        let mut deser = XmlDeserializer::new(xml, settings);
2581
2582        static MAP_KEY: Schema<'static> =
2583            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
2584        static MAP_VALUE: Schema<'static> =
2585            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 0);
2586        static MAP_SCHEMA: Schema<'static> =
2587            Schema::new_map(shape_id!("test", "M"), &MAP_KEY, &MAP_VALUE);
2588
2589        let out = deser.read_string_string_map(&MAP_SCHEMA).unwrap();
2590        assert_eq!(out.len(), 2);
2591        assert_eq!(out.get("a").map(String::as_str), Some("1"));
2592        assert_eq!(out.get("b").map(String::as_str), Some("2"));
2593    }
2594
2595    #[test]
2596    fn read_string_string_map_helper_with_renamed_key_value() {
2597        // @xmlName overrides on the map's key / value members — the
2598        // helper resolves these from the schema, mirroring `read_map`.
2599        let xml = b"<m><entry><K>a</K><V>1</V></entry></m>";
2600        let settings = Arc::new(XmlCodecSettings::default());
2601        let mut deser = XmlDeserializer::new(xml, settings);
2602
2603        static MAP_KEY: Schema<'static> =
2604            Schema::new_member(shape_id!("test", "M2$key"), ShapeType::String, "key", 0)
2605                .with_xml_name("K");
2606        static MAP_VALUE: Schema<'static> =
2607            Schema::new_member(shape_id!("test", "M2$value"), ShapeType::String, "value", 0)
2608                .with_xml_name("V");
2609        static MAP_SCHEMA: Schema<'static> =
2610            Schema::new_map(shape_id!("test", "M2"), &MAP_KEY, &MAP_VALUE);
2611
2612        let out = deser.read_string_string_map(&MAP_SCHEMA).unwrap();
2613        assert_eq!(out.get("a").map(String::as_str), Some("1"));
2614    }
2615
2616    // Regression tests adapted from review comments on PR #4668 against an
2617    // earlier schema-XML deserializer that reconstructed sub-trees with
2618    // `try_data` + `format!("<{}>{}</{}>", ...)`. That pattern broke (a) on
2619    // structs with element children >1 level deep (because `try_data` errors
2620    // on a non-text token) and (b) on text containing `&` or `<` (because
2621    // unescape ran before re-emitting into fabricated tags, producing
2622    // invalid XML on re-parse). Our deserializer propagates raw byte slices
2623    // for aggregate sub-trees via `element_start` and `dispatch_subslice`,
2624    // so neither bug should reproduce — these tests lock that in.
2625    #[test]
2626    fn nested_struct_three_levels_deep() {
2627        static LEAF: Schema<'static> =
2628            Schema::new_member(shape_id!("t", "Inner"), ShapeType::String, "Leaf", 0);
2629        static INNER_SCHEMA: Schema<'static> =
2630            Schema::new_struct(shape_id!("t", "Inner"), ShapeType::Structure, &[&LEAF]);
2631        static INNER_MEMBER: Schema<'static> =
2632            Schema::new_member(shape_id!("t", "Middle"), ShapeType::Structure, "Inner", 0);
2633        static MIDDLE_SCHEMA: Schema<'static> = Schema::new_struct(
2634            shape_id!("t", "Middle"),
2635            ShapeType::Structure,
2636            &[&INNER_MEMBER],
2637        );
2638        static MIDDLE_MEMBER: Schema<'static> =
2639            Schema::new_member(shape_id!("t", "Outer"), ShapeType::Structure, "Middle", 0);
2640        static OUTER_SCHEMA: Schema<'static> = Schema::new_struct(
2641            shape_id!("t", "Outer"),
2642            ShapeType::Structure,
2643            &[&MIDDLE_MEMBER],
2644        );
2645        static OUTER_MEMBER: Schema<'static> =
2646            Schema::new_member(shape_id!("t", "Root"), ShapeType::Structure, "Outer", 0);
2647        static ROOT: Schema<'static> = Schema::new_struct(
2648            shape_id!("t", "Root"),
2649            ShapeType::Structure,
2650            &[&OUTER_MEMBER],
2651        );
2652
2653        let xml = b"<Root><Outer><Middle><Inner><Leaf>value</Leaf></Inner></Middle></Outer></Root>";
2654        let settings = Arc::new(XmlCodecSettings::default());
2655        let mut deser = XmlDeserializer::new(xml, settings);
2656        let mut leaf = String::new();
2657        deser
2658            .read_struct(&ROOT, &mut |outer_m, d_outer| {
2659                assert_eq!(outer_m.member_name(), Some("Outer"));
2660                d_outer.read_struct(&OUTER_SCHEMA, &mut |middle_m, d_middle| {
2661                    assert_eq!(middle_m.member_name(), Some("Middle"));
2662                    d_middle.read_struct(&MIDDLE_SCHEMA, &mut |inner_m, d_inner| {
2663                        assert_eq!(inner_m.member_name(), Some("Inner"));
2664                        d_inner.read_struct(&INNER_SCHEMA, &mut |leaf_m, d_leaf| {
2665                            if leaf_m.member_name() == Some("Leaf") {
2666                                leaf = d_leaf.read_string(leaf_m)?;
2667                            }
2668                            Ok(())
2669                        })
2670                    })
2671                })
2672            })
2673            .expect("3-level nested struct should round-trip");
2674        assert_eq!(leaf, "value");
2675    }
2676
2677    #[test]
2678    fn struct_member_with_escaped_text_round_trips() {
2679        static VALUE: Schema<'static> =
2680            Schema::new_member(shape_id!("t", "Body"), ShapeType::String, "value", 0);
2681        static BODY_SCHEMA: Schema<'static> =
2682            Schema::new_struct(shape_id!("t", "Body"), ShapeType::Structure, &[&VALUE]);
2683        static PAYLOAD_MEMBER: Schema<'static> = Schema::new_member(
2684            shape_id!("t", "Envelope"),
2685            ShapeType::Structure,
2686            "payload",
2687            0,
2688        );
2689        static ENVELOPE_SCHEMA: Schema<'static> = Schema::new_struct(
2690            shape_id!("t", "Envelope"),
2691            ShapeType::Structure,
2692            &[&PAYLOAD_MEMBER],
2693        );
2694
2695        // Server response with an XML-escaped `&` in the leaf value.
2696        // After `unescape`, the original value is `foo&bar`. The PR's
2697        // deserializer would re-emit the unescaped text into reconstructed
2698        // markup, producing invalid XML and an error or wrong value.
2699        let xml = b"<Envelope><payload><value>foo&amp;bar</value></payload></Envelope>";
2700        let settings = Arc::new(XmlCodecSettings::default());
2701        let mut deser = XmlDeserializer::new(xml, settings);
2702        let mut value_str = String::new();
2703        deser
2704            .read_struct(&ENVELOPE_SCHEMA, &mut |payload_m, d_payload| {
2705                let _ = payload_m;
2706                d_payload.read_struct(&BODY_SCHEMA, &mut |inner_m, d_inner| {
2707                    if inner_m.member_name() == Some("value") {
2708                        value_str = d_inner.read_string(inner_m)?;
2709                    }
2710                    Ok(())
2711                })
2712            })
2713            .expect("escaped text inside a nested struct should round-trip");
2714        assert_eq!(value_str, "foo&bar");
2715    }
2716
2717    #[test]
2718    fn read_map_preserves_empty_string_key() {
2719        // The PR's deserializer had a guard that dropped entries with empty
2720        // keys. Empty string is a valid map key per Smithy semantics.
2721        static KEY: Schema<'static> =
2722            Schema::new_member(shape_id!("t", "M$key"), ShapeType::String, "key", 0);
2723        static VALUE: Schema<'static> =
2724            Schema::new_member(shape_id!("t", "M$value"), ShapeType::String, "value", 1);
2725        static MAP: Schema<'static> = Schema::new_map(shape_id!("t", "M"), &KEY, &VALUE);
2726
2727        let xml = b"<Root><entry><key></key><value>v1</value></entry></Root>";
2728        let settings = Arc::new(XmlCodecSettings::default());
2729        let mut deser = XmlDeserializer::new(xml, settings);
2730
2731        let mut got: std::collections::HashMap<String, String> = Default::default();
2732        deser
2733            .read_map(&MAP, &mut |k, d| {
2734                let v = d.read_string(&VALUE)?;
2735                got.insert(k, v);
2736                Ok(())
2737            })
2738            .expect("empty-key entry should be preserved");
2739        assert_eq!(got.get("").map(String::as_str), Some("v1"));
2740    }
2741
2742    /// Outer map whose value member targets a map with `@xmlName` on
2743    /// the inner key/value. Mirrors the wire format produced by the
2744    /// serializer's `map_value_is_inner_map_with_renamed_inner_key_value`
2745    /// test and verifies the nested read uses the inner map's renamed
2746    /// element names.
2747    #[test]
2748    fn read_map_value_is_inner_map_with_renamed_inner_key_value() {
2749        let xml = b"<outerMap><entry><key>ok</key><value><entry><InnerKey>ik</InnerKey><InnerVal>iv</InnerVal></entry></value></entry></outerMap>";
2750        let settings = Arc::new(XmlCodecSettings::default());
2751        let mut deser = XmlDeserializer::new(xml, settings);
2752
2753        static INNER_KEY: Schema<'static> = Schema::new_member(
2754            shape_id!("test", "InnerMap$key"),
2755            ShapeType::String,
2756            "key",
2757            0,
2758        )
2759        .with_xml_name("InnerKey");
2760        static INNER_VAL: Schema<'static> = Schema::new_member(
2761            shape_id!("test", "InnerMap$value"),
2762            ShapeType::String,
2763            "value",
2764            1,
2765        )
2766        .with_xml_name("InnerVal");
2767
2768        // The outer map's value member, with its target's _KEY/_VALUE
2769        // chain attached. This is what codegen now emits at the inner
2770        // `read_map` call site.
2771        static OUTER_VALUE: Schema<'static> = Schema::new_member(
2772            shape_id!("test", "OuterMap$value"),
2773            ShapeType::Map,
2774            "value",
2775            1,
2776        )
2777        .with_map_members(&INNER_KEY, &INNER_VAL);
2778
2779        static OUTER_KEY: Schema<'static> = Schema::new_member(
2780            shape_id!("test", "OuterMap$key"),
2781            ShapeType::String,
2782            "key",
2783            0,
2784        );
2785        static OUTER_MAP_SCHEMA: Schema<'static> =
2786            Schema::new_map(shape_id!("test", "OuterMap"), &OUTER_KEY, &OUTER_VALUE);
2787
2788        let mut got: Vec<(String, Vec<(String, String)>)> = Vec::new();
2789        deser
2790            .read_map(&OUTER_MAP_SCHEMA, &mut |outer_key, d| {
2791                let mut inner: Vec<(String, String)> = Vec::new();
2792                d.read_map(&OUTER_VALUE, &mut |inner_key, d2| {
2793                    let inner_val = d2.read_string(&INNER_VAL)?;
2794                    inner.push((inner_key, inner_val));
2795                    Ok(())
2796                })?;
2797                got.push((outer_key, inner));
2798                Ok(())
2799            })
2800            .expect("nested map deserialization should succeed");
2801
2802        assert_eq!(
2803            got,
2804            vec![("ok".to_owned(), vec![("ik".to_owned(), "iv".to_owned())])]
2805        );
2806    }
2807
2808    /// List whose member targets a map with `@xmlName` on inner
2809    /// key/value. Mirrors the serializer's
2810    /// `list_member_is_inner_map_with_renamed_inner_key_value` wire
2811    /// format.
2812    #[test]
2813    fn read_list_member_is_inner_map_with_renamed_inner_key_value() {
2814        let xml = b"<items><member><entry><Attr>k1</Attr><Set>v1</Set></entry></member></items>";
2815        let settings = Arc::new(XmlCodecSettings::default());
2816        let mut deser = XmlDeserializer::new(xml, settings);
2817
2818        static INNER_KEY: Schema<'static> = Schema::new_member(
2819            shape_id!("test", "InnerMap$key"),
2820            ShapeType::String,
2821            "key",
2822            0,
2823        )
2824        .with_xml_name("Attr");
2825        static INNER_VAL: Schema<'static> = Schema::new_member(
2826            shape_id!("test", "InnerMap$value"),
2827            ShapeType::String,
2828            "value",
2829            1,
2830        )
2831        .with_xml_name("Set");
2832
2833        // The list's member: target shape is InnerMap. Carries the
2834        // chained inner map _KEY/_VALUE schemas.
2835        static LIST_ITEM: Schema<'static> = Schema::new_member(
2836            shape_id!("test", "OuterList$member"),
2837            ShapeType::Map,
2838            "member",
2839            0,
2840        )
2841        .with_map_members(&INNER_KEY, &INNER_VAL);
2842
2843        static OUTER_LIST_SCHEMA: Schema<'static> =
2844            Schema::new_list(shape_id!("test", "OuterList"), &LIST_ITEM);
2845
2846        let mut got: Vec<Vec<(String, String)>> = Vec::new();
2847        deser
2848            .read_list(&OUTER_LIST_SCHEMA, &mut |d| {
2849                let mut entries: Vec<(String, String)> = Vec::new();
2850                d.read_map(&LIST_ITEM, &mut |inner_key, d2| {
2851                    let inner_val = d2.read_string(&INNER_VAL)?;
2852                    entries.push((inner_key, inner_val));
2853                    Ok(())
2854                })?;
2855                got.push(entries);
2856                Ok(())
2857            })
2858            .expect("list-of-map deserialization should succeed");
2859
2860        assert_eq!(got, vec![vec![("k1".to_owned(), "v1".to_owned())]]);
2861    }
2862}
2863
2864/// Tests for the [`ShapeDeserializer::skip_value`] contract.
2865///
2866/// XML is the opposite case from JSON and CBOR: `read_struct` delimits each child before
2867/// dispatching it, and the child's `ScopedDecoder` drop advances the parent's tokenizer
2868/// past the closing tag. So the override must do nothing. These tests are written so that
2869/// an implementation which *did* advance would consume a sibling and fail.
2870#[cfg(test)]
2871mod skip_value_contract {
2872    use super::*;
2873    use aws_smithy_schema::serde::ShapeDeserializer;
2874    use aws_smithy_schema::{shape_id, Schema, ShapeType};
2875
2876    static BOUND: Schema<'static> = Schema::new_member(
2877        shape_id!("test", "Output$bound"),
2878        ShapeType::String,
2879        "bound",
2880        0,
2881    );
2882    static BODY: Schema<'static> = Schema::new_member(
2883        shape_id!("test", "Output$body"),
2884        ShapeType::String,
2885        "body",
2886        1,
2887    );
2888    static TAIL: Schema<'static> = Schema::new_member(
2889        shape_id!("test", "Output$tail"),
2890        ShapeType::String,
2891        "tail",
2892        2,
2893    );
2894    static OUTPUT: Schema<'static> = Schema::new_struct(
2895        shape_id!("test", "Output"),
2896        ShapeType::Structure,
2897        &[&BOUND, &BODY, &TAIL],
2898    );
2899
2900    /// Reads `OUTPUT` from `xml`, skipping `bound` and collecting the other two members.
2901    fn skip_bound(xml: &[u8]) -> Result<(Option<String>, Option<String>), SerdeError> {
2902        let mut deser = XmlDeserializer::new(xml, Arc::new(XmlCodecSettings::default()));
2903        let (mut body, mut tail) = (None, None);
2904        deser.read_struct(&OUTPUT, &mut |member, d| {
2905            match member.member_index() {
2906                Some(0) => d.skip_value()?,
2907                Some(1) => body = Some(d.read_string(member)?),
2908                Some(2) => tail = Some(d.read_string(member)?),
2909                _ => {}
2910            }
2911            Ok(())
2912        })?;
2913        Ok((body, tail))
2914    }
2915
2916    #[test]
2917    fn skipping_a_member_does_not_consume_its_siblings() {
2918        // `bound` sits between the two members we read. An implementation that advanced
2919        // the tokenizer here would swallow `<body>`.
2920        let xml = b"<Output><bound>skipped</bound><body>real</body><tail>end</tail></Output>";
2921        let (body, tail) = skip_bound(xml).unwrap();
2922        assert_eq!(body.as_deref(), Some("real"));
2923        assert_eq!(tail.as_deref(), Some("end"));
2924    }
2925
2926    #[test]
2927    fn skipping_works_regardless_of_element_order() {
2928        for xml in [
2929            &b"<Output><bound>s</bound><body>real</body><tail>end</tail></Output>"[..],
2930            &b"<Output><body>real</body><bound>s</bound><tail>end</tail></Output>"[..],
2931            &b"<Output><body>real</body><tail>end</tail><bound>s</bound></Output>"[..],
2932        ] {
2933            let (body, tail) = skip_bound(xml).unwrap();
2934            assert_eq!(
2935                (body.as_deref(), tail.as_deref()),
2936                (Some("real"), Some("end")),
2937                "failed for {}",
2938                String::from_utf8_lossy(xml)
2939            );
2940        }
2941    }
2942
2943    #[test]
2944    fn skipping_an_aggregate_member_with_nested_content_does_not_consume_siblings() {
2945        // Here `bound` is a structure, so the parent hands the consumer a sub-slice
2946        // view. Skipping that must not disturb the outer iteration.
2947        // Member 0 is aggregate-typed this time, which routes it through
2948        // `dispatch_subslice`; members 1 and 2 stay scalars. It needs no sub-members of
2949        // its own because the consumer never reads it.
2950        static AGG_BOUND: Schema<'static> = Schema::new_member(
2951            shape_id!("test", "Output$bound"),
2952            ShapeType::Structure,
2953            "bound",
2954            0,
2955        );
2956        static AGG_OUTPUT: Schema<'static> = Schema::new_struct(
2957            shape_id!("test", "Output"),
2958            ShapeType::Structure,
2959            &[&AGG_BOUND, &BODY, &TAIL],
2960        );
2961
2962        // The skipped element contains a decoy `<body>`. Because the parent delimited the
2963        // whole `<bound>` element, the decoy must never surface and the outer `<body>`
2964        // must still be read.
2965        let xml = b"<Output><bound><inner>x</inner><body>decoy</body></bound>\
2966                    <body>real</body><tail>end</tail></Output>";
2967        let mut deser = XmlDeserializer::new(xml, Arc::new(XmlCodecSettings::default()));
2968        let (mut body, mut tail) = (None, None);
2969        deser
2970            .read_struct(&AGG_OUTPUT, &mut |member, d| {
2971                match member.member_index() {
2972                    Some(0) => d.skip_value()?,
2973                    Some(1) => body = Some(d.read_string(member)?),
2974                    Some(2) => tail = Some(d.read_string(member)?),
2975                    _ => {}
2976                }
2977                Ok(())
2978            })
2979            .unwrap();
2980        assert_eq!(body.as_deref(), Some("real"));
2981        assert_eq!(tail.as_deref(), Some("end"));
2982    }
2983
2984    #[test]
2985    fn skipping_a_scalar_member_ignores_its_nested_content() {
2986        let xml = b"<Output><bound><body>decoy</body></bound>\
2987                    <body>real</body><tail>end</tail></Output>";
2988        let (body, tail) = skip_bound(xml).unwrap();
2989        assert_eq!(body.as_deref(), Some("real"));
2990        assert_eq!(tail.as_deref(), Some("end"));
2991    }
2992
2993    #[test]
2994    fn reading_a_scalar_member_still_rejects_nested_content() {
2995        let xml = b"<Output><bound><body>decoy</body></bound>\
2996                    <body>real</body><tail>end</tail></Output>";
2997        let mut deser = XmlDeserializer::new(xml, Arc::new(XmlCodecSettings::default()));
2998        let result = deser.read_struct(&OUTPUT, &mut |member, value| {
2999            value.read_string(member)?;
3000            Ok(())
3001        });
3002        assert!(result.is_err());
3003    }
3004
3005    #[test]
3006    fn skipping_a_self_closing_and_empty_element_is_fine() {
3007        for xml in [
3008            &b"<Output><bound/><body>real</body><tail>end</tail></Output>"[..],
3009            &b"<Output><bound></bound><body>real</body><tail>end</tail></Output>"[..],
3010        ] {
3011            let (body, tail) = skip_bound(xml).unwrap();
3012            assert_eq!(
3013                (body.as_deref(), tail.as_deref()),
3014                (Some("real"), Some("end")),
3015                "failed for {}",
3016                String::from_utf8_lossy(xml)
3017            );
3018        }
3019    }
3020
3021    #[test]
3022    fn skip_value_is_a_no_op_on_a_text_deserializer() {
3023        // The leaf case: a deserializer holding pre-extracted text. Skipping must neither
3024        // fail nor consume anything, and must not allocate a discarded document (the
3025        // trait default would call `read_document`).
3026        static V: Schema<'static> =
3027            Schema::new_member(shape_id!("test", "S$v"), ShapeType::String, "v", 0);
3028        let mut deser =
3029            XmlDeserializer::new(b"<v>hello</v>", Arc::new(XmlCodecSettings::default()));
3030        let dynamic: &mut dyn ShapeDeserializer = &mut deser;
3031        dynamic.skip_value().unwrap();
3032        // Still readable afterwards, confirming nothing was consumed.
3033        assert_eq!(deser.read_string(&V).unwrap(), "hello");
3034    }
3035}