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