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