Skip to main content

aws_smithy_xml/codec/
serializer.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! XML serializer.
7//!
8//! Implements the schema-serde [`ShapeSerializer`] trait for the AWS REST XML
9//! protocol. Serialization is single-pass with a deferred start-tag flush:
10//! when a struct's start tag is written, the closing `>` is held back until
11//! either an attribute is added or the first child element / text is
12//! written. This lets us emit attributes inline with the start tag without
13//! buffering the start tag separately.
14
15use super::XmlCodecSettings;
16use aws_smithy_schema::codec::FinishSerializer;
17use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
18use aws_smithy_schema::Schema;
19use aws_smithy_types::date_time::Format as TimestampFormat;
20use aws_smithy_types::{BigDecimal, BigInteger, DateTime};
21
22use aws_smithy_schema::extension::SchemaExtensionKey;
23use aws_smithy_types::Document;
24use std::sync::Arc;
25
26/// An `@xmlNamespace` as `(uri, prefix)`.
27type Namespace = (String, Option<String>);
28
29fn namespace_of(schema: Option<&Schema<'_>>) -> Option<Arc<Namespace>> {
30    schema
31        .and_then(|s| s.xml_namespace())
32        .map(|ns| Arc::new((ns.uri().to_owned(), ns.prefix().map(|p| p.to_owned()))))
33}
34
35/// `@xmlName`, then the member name, then `default`.
36fn member_element_name<'s>(member: Option<&'s Schema<'s>>, default: &'static str) -> &'s str {
37    member
38        .and_then(|m| m.xml_name().map(|n| n.value()).or(m.member_name()))
39        .unwrap_or(default)
40}
41
42/// Element names a `write_list` call hands to its items, cached on the list schema.
43///
44/// Each item is written by a separate `write_*` call, so the serializer has to hold the
45/// names between calls. Sharing them makes that a reference count increment per list
46/// instead of a `String` copy per item.
47#[derive(Debug)]
48struct XmlListNames {
49    /// The item element. A wrapped list's items use the list member's `@xmlName`, then its
50    /// member name, then `member`. A flattened list has no wrapper, so its items use the
51    /// list's own element name (unless an enclosing list hands down a different one).
52    item: Arc<str>,
53    /// The list member's `@xmlNamespace`, inherited by items whose schema has none.
54    item_namespace: Option<Arc<Namespace>>,
55}
56
57static XML_LIST_NAMES: SchemaExtensionKey<XmlListNames> =
58    SchemaExtensionKey::new(|list| XmlListNames {
59        item: Arc::from(if list.xml_flattened() {
60            XmlSerializer::element_name(list)
61        } else {
62            member_element_name(list.member(), "member")
63        }),
64        item_namespace: namespace_of(list.member()),
65    });
66
67/// Element names for the entries of a `write_map` call, cached on the map schema. See
68/// [`XmlListNames`] for why they are shared.
69#[derive(Debug)]
70struct XmlMapNames {
71    entry: Arc<str>,
72    key: Arc<str>,
73    value: Arc<str>,
74    key_namespace: Option<Arc<Namespace>>,
75    value_namespace: Option<Arc<Namespace>>,
76}
77
78static XML_MAP_NAMES: SchemaExtensionKey<XmlMapNames> =
79    SchemaExtensionKey::new(|map| XmlMapNames {
80        entry: Arc::from(if map.xml_flattened() {
81            XmlSerializer::element_name(map)
82        } else {
83            "entry"
84        }),
85        key: Arc::from(member_element_name(map.key(), "key")),
86        value: Arc::from(member_element_name(map.member(), "value")),
87        key_namespace: namespace_of(map.key()),
88        value_namespace: namespace_of(map.member()),
89    });
90
91/// XML serializer that implements the [`ShapeSerializer`] trait.
92pub struct XmlSerializer {
93    output: String,
94    settings: Arc<XmlCodecSettings>,
95    /// Stack of open elements. Top of stack is the deepest currently-open
96    /// element. The frame state distinguishes "start tag still open" from
97    /// "start tag closed, inside body".
98    frames: Vec<Frame>,
99    /// When inside a `write_map` callback, tracks the entry/key/value state.
100    map_state: Option<MapState>,
101    /// When inside a `write_list` callback, overrides the element name for items.
102    list_item_name: Option<Arc<str>>,
103    /// When inside a `write_list` callback, propagates the inner-list-member
104    /// `@xmlNamespace` (uri, prefix) to scalar item writes whose schema is a
105    /// generic prelude type and therefore doesn't carry the trait itself.
106    list_item_namespace: Option<Arc<Namespace>>,
107    /// Two-pass `serialize_members` filter for the immediately containing
108    /// `write_struct` call. XML attributes must appear inside the start tag,
109    /// before any child element closes it; the codegen-generated
110    /// `serialize_members` does not order attribute members first, so the
111    /// codec calls `serialize_members` twice — once with `AttributesOnly` to
112    /// emit only `@xmlAttribute` members, and once with `NonAttributesOnly`
113    /// to emit everything else. This keeps generated code protocol-neutral
114    /// and preserves runtime protocol selection: the order of attribute vs.
115    /// element members is the codec's concern, not the generated code's.
116    ///
117    /// Trade-off: `serialize_members` is iterated twice per struct. The
118    /// actual byte-writing work happens once (the skipped pass exits early
119    /// at each `write_*` filter check), so the overhead is proportional to
120    /// member count and dominated by the work of iterating the generated
121    /// `if let Some(ref val) = self.x` chain — measurable for very wide
122    /// structs but not for typical shapes.
123    ///
124    /// Alternative considered: a single-pass buffered-children approach.
125    /// `write_struct` would record `attr_insert_pos` (the byte offset just
126    /// after the element name in `output`), keep that position valid even
127    /// after the start tag is "flushed", and route attribute writes through
128    /// `output.insert_str(attr_insert_pos, ...)` no matter when they arrive.
129    /// That would let `serialize_members` run once in declaration order
130    /// while still placing attributes inside the start tag. The cost is an
131    /// `O(n)` shift of the output buffer per attribute write where `n` is
132    /// the bytes already written after the start tag — potentially worse
133    /// than two-pass for structs with many child elements followed by an
134    /// attribute, and noticeably more code for the frame-tracking. We chose
135    /// two-pass for simplicity and predictability; revisit if benchmarks
136    /// show double-iteration is a real cost.
137    member_filter: MemberFilter,
138    /// One-shot override for the wrapper element name on the next
139    /// document-root `write_struct`. Consumed (`take`d) on first use.
140    /// Set externally (e.g. by `AwsRestXmlProtocol::serialize_request`)
141    /// when the body root must be named after a member's `@xmlName` that
142    /// the codec couldn't see — for example, an `@httpPayload` struct member
143    /// whose codegen passes the *target* shape's `SCHEMA` (which may carry
144    /// its own `@xmlName`) but whose member-level `@xmlName` should win
145    /// per the Smithy spec.
146    next_root_xml_name: Option<String>,
147    /// One-shot override for the `xmlns` attribute on the next document-root
148    /// element. Consumed on first use. Intended for protocol-layer use:
149    /// REST XML services may declare a service-level `@xmlNamespace` that
150    /// applies as the default xmlns to every operation's request/response
151    /// XML root, but per the Smithy spec a shape- or member-level
152    /// `@xmlNamespace` overrides it. Schemas only carry shape/member
153    /// namespaces; the protocol pre-sets this field with the service
154    /// default, and the codec consumes it on the next root write — but
155    /// only if the schema itself has no `xml_namespace`.
156    next_root_xml_namespace: Option<(String, Option<String>)>,
157}
158
159#[derive(Debug, Copy, Clone, PartialEq, Eq)]
160enum MemberFilter {
161    /// No filtering — emit every write call.
162    None,
163    /// Emit only members whose schema has `@xmlAttribute`.
164    AttributesOnly,
165    /// Emit only members whose schema does NOT have `@xmlAttribute`.
166    NonAttributesOnly,
167}
168
169/// Tracks alternating key/value writes inside a map callback.
170#[derive(Debug)]
171struct MapState {
172    /// Element name for each entry (e.g., "entry" or the member name for flattened).
173    entry_name: Arc<str>,
174    /// Element name for the key (default "key", overridable via @xmlName).
175    key_name: Arc<str>,
176    /// Element name for the value (default "value", overridable via @xmlName).
177    value_name: Arc<str>,
178    /// `@xmlNamespace` (uri, prefix) on the key member, if any.
179    key_namespace: Option<Arc<Namespace>>,
180    /// `@xmlNamespace` (uri, prefix) on the value member, if any.
181    value_namespace: Option<Arc<Namespace>>,
182    /// True when the next write is a key (odd writes), false for value (even writes).
183    expecting_key: bool,
184}
185
186/// An open element.
187///
188/// A frame does not hold the element's name: every element is opened and closed within one
189/// `write_*` call, which passes the name to [`XmlSerializer::close_element`].
190#[derive(Debug)]
191enum Frame {
192    /// `<name` has been written; the closing `>` is deferred so that
193    /// attributes and namespaces can still be added inline with the start tag.
194    /// Attributes are buffered so they can arrive in any order relative to
195    /// child elements (protocol-neutral serialize_members ordering).
196    StartTagPending { attrs: String },
197    /// `<name attrs>` has been fully written; we are now inside the element body.
198    Open,
199}
200
201/// Append the `xmlns="..."` (or `xmlns:prefix="..."`) attribute fragment
202/// for `ns` directly into `out`. A no-op when `ns` is `None`.
203///
204/// Used by inline element emission paths (e.g., map entries) where the
205/// frame-based [`XmlSerializer::write_xmlns`] helper isn't applicable
206/// because the element is emitted via `write!(self.output, ...)` rather
207/// than going through `open_element`.
208///
209/// Replaces an earlier `format_xmlns_attr -> String` helper that
210/// allocated a fresh `String` per call. Per-entry map serialization can
211/// call this twice per entry (key + value), so on map-heavy payloads the
212/// allocations added up.
213fn write_xmlns_attr(out: &mut String, ns: Option<&Namespace>) {
214    use std::fmt::Write;
215    match ns {
216        Some((uri, Some(prefix))) => {
217            write!(out, " xmlns:{prefix}=\"{uri}\"").unwrap();
218        }
219        Some((uri, None)) => {
220            write!(out, " xmlns=\"{uri}\"").unwrap();
221        }
222        None => {}
223    }
224}
225
226impl XmlSerializer {
227    /// Creates a new XML serializer with the given settings.
228    pub(crate) fn new(settings: Arc<XmlCodecSettings>) -> Self {
229        Self {
230            output: String::new(),
231            settings,
232            frames: Vec::new(),
233            map_state: None,
234            list_item_name: None,
235            list_item_namespace: None,
236            member_filter: MemberFilter::None,
237            next_root_xml_name: None,
238            next_root_xml_namespace: None,
239        }
240    }
241
242    /// Sets a one-shot override for the wrapper element name on the next
243    /// document-root `write_struct`. Consumed on first use. Intended for
244    /// protocol-layer use (e.g. REST XML routing of `@httpPayload` struct
245    /// members whose member-level `@xmlName` would otherwise be invisible
246    /// to the codec — codegen passes the target shape's `SCHEMA`, which
247    /// carries the target's `@xmlName` but not the member's).
248    pub fn set_next_root_xml_name(&mut self, name: String) {
249        self.next_root_xml_name = Some(name);
250    }
251
252    /// Sets a one-shot fallback xmlns for the document-root element. Consumed
253    /// on the first root-level `write_struct` only when the struct's own
254    /// schema has no `xml_namespace` (per the Smithy spec, a shape-level
255    /// `@xmlNamespace` overrides any service-level default). Intended for
256    /// the REST XML protocol to apply the service-level `@xmlNamespace`
257    /// to operation request/response root elements.
258    pub fn set_next_root_xml_namespace(&mut self, uri: String, prefix: Option<String>) {
259        self.next_root_xml_namespace = Some((uri, prefix));
260    }
261
262    /// Returns true if a write of `schema` should produce output under the
263    /// current `member_filter`. Top-level calls (no filter set) always pass.
264    fn filter_allows(&self, schema: &Schema<'_>) -> bool {
265        match self.member_filter {
266            MemberFilter::None => true,
267            MemberFilter::AttributesOnly => schema.xml_attribute(),
268            MemberFilter::NonAttributesOnly => !schema.xml_attribute(),
269        }
270    }
271
272    /// Resolve the XML element name for a schema being serialized as an element.
273    ///
274    /// Resolution order:
275    /// 1. `@xmlName` (member-level wins over shape-level via the codegen-emitted
276    ///    member schema).
277    /// 2. `original_name` — the synthetic shape's pre-rename name. Set only on
278    ///    operation input/output synthetic shapes.
279    /// 3. `member_name` — the smithy member name, set on member schemas.
280    /// 4. `shape_id().shape_name()` — fallback for non-synthetic, non-member shapes.
281    fn element_name<'a>(schema: &'a Schema<'a>) -> &'a str {
282        schema
283            .xml_name()
284            .map(|t| t.value())
285            .or_else(|| schema.original_name())
286            .or_else(|| schema.member_name())
287            .unwrap_or_else(|| schema.shape_id().shape_name())
288    }
289
290    /// If the top frame is a [`Frame::StartTagPending`], close its start tag
291    /// (write `>`) and transition the frame to [`Frame::Open`]. No-op
292    /// otherwise. Called before any child content (text or nested element)
293    /// is written.
294    fn flush_start_tag(&mut self) {
295        if let Some(frame) = self.frames.last_mut() {
296            if let Frame::StartTagPending { attrs } = frame {
297                self.output.push_str(attrs);
298                self.output.push('>');
299                *frame = Frame::Open;
300            }
301        }
302    }
303
304    /// Write `<name` and push a new [`Frame::StartTagPending`]. The caller
305    /// must have already flushed any parent's pending start tag (via
306    /// [`Self::flush_start_tag`]) so the new element doesn't end up nested
307    /// inside an unclosed tag.
308    fn open_element(&mut self, name: &str) {
309        self.output.push('<');
310        self.output.push_str(name);
311        self.frames.push(Frame::StartTagPending {
312            attrs: String::new(),
313        });
314    }
315
316    /// Append `xmlns` / `xmlns:prefix` attribute(s) to the most recently
317    /// opened start tag. The schema's `@xmlNamespace` is preferred. If the
318    /// schema has no namespace and `inherited` is `Some`, that fallback is
319    /// used — this is how a list's inner-member `@xmlNamespace` reaches each
320    /// scalar item write whose schema is a generic prelude type and therefore
321    /// doesn't carry the trait itself.
322    ///
323    /// If neither schema nor `inherited` provides a namespace AND this is
324    /// the document-root frame (only one frame on the stack), the
325    /// one-shot [`Self::next_root_xml_namespace`] override is consumed.
326    /// This is how the REST XML protocol applies a service-level
327    /// `@xmlNamespace` to the request/response root element without
328    /// codec-time knowledge of the service.
329    fn write_xmlns(&mut self, schema: &Schema<'_>, inherited: Option<&Namespace>) {
330        use std::fmt::Write;
331        let is_document_root = self.frames.len() == 1;
332        // Consume the document-root override only at the root, only when
333        // the schema and any caller-provided fallback don't already carry a
334        // namespace. Take it eagerly so it cannot leak to a subsequent root
335        // write on the same serializer (which is unusual but possible).
336        let root_override =
337            if is_document_root && schema.xml_namespace().is_none() && inherited.is_none() {
338                self.next_root_xml_namespace.take()
339            } else {
340                None
341            };
342        let Some(Frame::StartTagPending { attrs, .. }) = self.frames.last_mut() else {
343            return;
344        };
345        if let Some(ns) = schema.xml_namespace() {
346            match ns.prefix() {
347                Some(prefix) => write!(attrs, " xmlns:{prefix}=\"{}\"", ns.uri()).unwrap(),
348                None => write!(attrs, " xmlns=\"{}\"", ns.uri()).unwrap(),
349            }
350        } else if let Some((uri, prefix)) = inherited {
351            match prefix.as_deref() {
352                Some(p) => write!(attrs, " xmlns:{p}=\"{uri}\"").unwrap(),
353                None => write!(attrs, " xmlns=\"{uri}\"").unwrap(),
354            }
355        } else if let Some((uri, prefix)) = root_override {
356            match prefix {
357                Some(p) => write!(attrs, " xmlns:{p}=\"{uri}\"").unwrap(),
358                None => write!(attrs, " xmlns=\"{uri}\"").unwrap(),
359            }
360        }
361    }
362
363    /// Pop the top frame and emit the closing tag for `name`, the name the element was
364    /// opened with.
365    ///
366    /// Always emits `<name attrs>...</name>` form, never `<name attrs/>`.
367    /// Both forms are equivalent XML, but legacy smithy-rs (and S3's recorded
368    /// request fixtures) use the explicit-close form. Matching that prevents
369    /// false-positive content-length mismatches in DVR-replay tests like
370    /// `s3::select_object_content::test_success`, where `<CSV></CSV>` differs
371    /// from `<CSV/>` by 5 bytes.
372    fn close_element(&mut self, name: &str) {
373        let frame = self
374            .frames
375            .pop()
376            .expect("close_element called with empty frame stack");
377        if let Frame::StartTagPending { attrs } = frame {
378            self.output.push_str(&attrs);
379            self.output.push('>');
380        }
381        self.output.push_str("</");
382        self.output.push_str(name);
383        self.output.push('>');
384    }
385
386    /// The name of the element for `schema`: its `@xmlName` if it has one, otherwise the
387    /// name an enclosing list handed to its items, otherwise [`Self::element_name`].
388    ///
389    /// A handed-down name is moved into `inherited`, which the caller owns, so the result
390    /// borrows neither `self` nor a fresh allocation.
391    fn resolve_name<'s>(
392        &self,
393        schema: &'s Schema<'s>,
394        inherited: &'s mut Option<Arc<str>>,
395    ) -> &'s str {
396        if schema.xml_name().is_none() {
397            inherited.clone_from(&self.list_item_name);
398        }
399        match inherited {
400            Some(name) => name,
401            None => Self::element_name(schema),
402        }
403    }
404
405    /// Emit `<name>content</name>` where `content` is already safe (no
406    /// XML-special chars). Used for numbers, booleans, base64 — values that
407    /// are known not to need escaping. If the schema has `@xmlAttribute`,
408    /// writes an attribute on the parent's pending start tag instead.
409    fn write_safe_element(&mut self, schema: &Schema<'_>, content: &str) {
410        if !self.filter_allows(schema) {
411            return;
412        }
413        if self.try_write_attribute(schema, content) {
414            return;
415        }
416        use std::fmt::Write;
417        self.flush_start_tag();
418        if let Some(map_state) = &mut self.map_state {
419            if map_state.expecting_key {
420                // Open entry element, write key (with optional key @xmlNamespace)
421                let (entry, key) = (&map_state.entry_name, &map_state.key_name);
422                write!(self.output, "<{entry}><{key}").unwrap();
423                write_xmlns_attr(&mut self.output, map_state.key_namespace.as_deref());
424                write!(self.output, ">{content}</{key}>").unwrap();
425                map_state.expecting_key = false;
426            } else {
427                // Write value (with optional value @xmlNamespace), close entry element
428                let (entry, value) = (&map_state.entry_name, &map_state.value_name);
429                write!(self.output, "<{value}").unwrap();
430                write_xmlns_attr(&mut self.output, map_state.value_namespace.as_deref());
431                write!(self.output, ">{content}</{value}></{entry}>").unwrap();
432                map_state.expecting_key = true;
433            }
434        } else {
435            let mut inherited_name = None;
436            let name = self.resolve_name(schema, &mut inherited_name);
437            // Use open_element/close_element so namespace attributes can be
438            // emitted via write_xmlns into the still-pending start tag.
439            self.open_element(name);
440            let inherited = self.list_item_namespace.clone();
441            self.write_xmlns(schema, inherited.as_deref());
442            self.flush_start_tag();
443            self.output.push_str(content);
444            self.close_element(name);
445        }
446    }
447
448    /// If the schema has `@xmlAttribute` and the parent frame's start tag is
449    /// still pending, write ` name="escaped_value"` into the attrs buffer and
450    /// return `true`. Otherwise return `false` (caller should emit a child
451    /// element instead).
452    fn try_write_attribute(&mut self, schema: &Schema<'_>, value: &str) -> bool {
453        use std::fmt::Write;
454        if !schema.xml_attribute() {
455            return false;
456        }
457        // Buffer the attribute on the nearest pending start tag frame.
458        // If the frame was already flushed to Open, we can't add attributes —
459        // but with the current design, flush only happens when opening a nested
460        // element, and attributes are always scalars, so this case shouldn't occur
461        // in practice. We handle it gracefully by checking.
462        if let Some(Frame::StartTagPending { attrs, .. }) = self.frames.last_mut() {
463            let name = Self::element_name(schema);
464            let escaped = crate::escape::escape(value);
465            write!(attrs, " {name}=\"{escaped}\"").unwrap();
466            return true;
467        }
468        false
469    }
470
471    /// Resolve the timestamp format for a member. Member-level
472    /// `@timestampFormat` wins; otherwise the codec's default (`date-time`
473    /// for REST XML body bindings).
474    fn resolve_timestamp_format(&self, schema: &Schema<'_>) -> Result<TimestampFormat, SerdeError> {
475        let Some(t) = schema.timestamp_format() else {
476            return Ok(self.settings.default_timestamp_format());
477        };
478        Ok(match t.format() {
479            aws_smithy_schema::traits::TimestampFormat::EpochSeconds => {
480                TimestampFormat::EpochSeconds
481            }
482            aws_smithy_schema::traits::TimestampFormat::DateTime => TimestampFormat::DateTime,
483            aws_smithy_schema::traits::TimestampFormat::HttpDate => TimestampFormat::HttpDate,
484            other => {
485                return Err(SerdeError::unsupported(format!(
486                    "unsupported timestamp format {other:?}"
487                )))
488            }
489        })
490    }
491}
492
493impl FinishSerializer for XmlSerializer {
494    fn finish(self) -> Vec<u8> {
495        debug_assert!(
496            self.frames.is_empty(),
497            "XmlSerializer::finish called with {} unclosed frame(s)",
498            self.frames.len()
499        );
500        self.output.into_bytes()
501    }
502}
503
504impl ShapeSerializer for XmlSerializer {
505    fn write_struct(
506        &mut self,
507        schema: &Schema<'_>,
508        value: &dyn SerializableStruct,
509    ) -> Result<(), SerdeError> {
510        if schema.xml_attribute() {
511            return Err(SerdeError::custom(
512                "@xmlAttribute is not supported on aggregate types",
513            ));
514        }
515        if !self.filter_allows(schema) {
516            return Ok(());
517        }
518        self.flush_start_tag();
519
520        // Handle map entry framing when this struct is a map value.
521        let in_map_value = if let Some(map_state) = &mut self.map_state {
522            if map_state.expecting_key {
523                false
524            } else {
525                // This struct is the map value — open entry's value element.
526                // The struct's members are serialized directly inside <value>,
527                // without the struct's own element wrapper (per Smithy XML spec).
528                // Note: <entry> was already opened by the preceding key write.
529                use std::fmt::Write;
530                write!(self.output, "<{}>", map_state.value_name).unwrap();
531                map_state.expecting_key = true;
532                true
533            }
534        } else {
535            false
536        };
537
538        // Temporarily clear map_state so nested writes don't get map framing
539        let saved_map_state = if in_map_value {
540            self.map_state.take()
541        } else {
542            None
543        };
544
545        if in_map_value {
546            // Map value struct: serialize members directly (no struct element wrapper).
547            // Two-pass for attribute ordering: pass 1 emits attribute members,
548            // pass 2 emits non-attribute members. The first pass is skipped
549            // entirely when no member of this struct has `@xmlAttribute` —
550            // every `write_*` call would otherwise no-op via `filter_allows`,
551            // but the codegen-generated `serialize_members` body still
552            // iterates each value field on the way to those no-ops. Avoiding
553            // a full pass over value fields when there's nothing to emit
554            // halves the per-struct serialization cost on the common case
555            // (Smithy structures with no attributes).
556            let saved_filter = self.member_filter;
557            // See the comment on the corresponding `has_attrs` check in the
558            // non-map-value branch below: member schemas (`Schema::new_member`)
559            // leave `members()` empty regardless of the target's shape type,
560            // so we run the AttributesOnly pass conservatively when the
561            // schema is a member.
562            let has_attrs = schema.member_name().is_some()
563                || schema.members().iter().any(|m| m.xml_attribute());
564            if has_attrs {
565                self.member_filter = MemberFilter::AttributesOnly;
566                value.serialize_members(self)?;
567            }
568            self.member_filter = MemberFilter::NonAttributesOnly;
569            value.serialize_members(self)?;
570            self.member_filter = saved_filter;
571            // Close value element and entry element
572            use std::fmt::Write;
573            let saved = saved_map_state.as_ref().unwrap();
574            let val_name = &saved.value_name;
575            let entry = &saved.entry_name;
576            write!(self.output, "</{val_name}></{entry}>").unwrap();
577            self.map_state = saved_map_state;
578        } else {
579            // Document-root override: if a protocol layer pre-set
580            // `next_root_xml_name` (e.g. REST XML resolving an
581            // `@httpPayload` member's `@xmlName` that codegen couldn't
582            // surface), consume it here. Only applies at the document root —
583            // nested struct calls always have at least one open frame.
584            let root_override = if self.frames.is_empty() {
585                self.next_root_xml_name.take().or_else(|| {
586                    schema
587                        .traits()
588                        .and_then(|traits| traits.get_fqn("smithy.api#error"))
589                        .and(self.settings.error_root_name)
590                        .map(str::to_owned)
591                })
592            } else {
593                None
594            };
595            let saved_list_item = self.list_item_name.take();
596            let name = root_override
597                .as_deref()
598                .or(saved_list_item.as_deref())
599                .unwrap_or_else(|| Self::element_name(schema));
600            self.open_element(name);
601            self.write_xmlns(schema, None);
602            // Two-pass: attributes first (so they land in the still-pending
603            // start tag), non-attributes second (which flushes the start tag
604            // and emits child elements). The attributes pass is skipped
605            // entirely when no member has `@xmlAttribute` — see comment in
606            // the map-value branch above.
607            //
608            // Member schemas (`Schema::new_member`) carry the target shape's
609            // `shape_type` but leave `members()` empty — the target struct's
610            // member list lives on the target schema, not on the member
611            // wrapper. When `schema` is a member schema, `members().iter()`
612            // can't see the target's `@xmlAttribute` members, so we
613            // conservatively run the AttributesOnly pass and let the
614            // per-member `filter_allows` checks gate emission. Root-struct
615            // calls (`schema` is a struct schema with `members()`
616            // populated) keep the optimization. The discriminator is
617            // `member_name()` — `Some(_)` only when the schema was built
618            // via `Schema::new_member`.
619            let saved_filter = self.member_filter;
620            let has_attrs = schema.member_name().is_some()
621                || schema.members().iter().any(|m| m.xml_attribute());
622            if has_attrs {
623                self.member_filter = MemberFilter::AttributesOnly;
624                value.serialize_members(self)?;
625            }
626            self.member_filter = MemberFilter::NonAttributesOnly;
627            value.serialize_members(self)?;
628            self.member_filter = saved_filter;
629            self.close_element(name);
630            self.list_item_name = saved_list_item;
631        }
632        Ok(())
633    }
634
635    fn write_list(
636        &mut self,
637        schema: &Schema<'_>,
638        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
639    ) -> Result<(), SerdeError> {
640        if schema.xml_attribute() {
641            return Err(SerdeError::custom(
642                "@xmlAttribute is not supported on aggregate types",
643            ));
644        }
645        if !self.filter_allows(schema) {
646            return Ok(());
647        }
648
649        self.flush_start_tag();
650
651        // Handle being called as a map value
652        let in_map_value = if let Some(map_state) = &mut self.map_state {
653            if !map_state.expecting_key {
654                use std::fmt::Write;
655                write!(self.output, "<{}>", map_state.value_name).unwrap();
656                map_state.expecting_key = true;
657                true
658            } else {
659                false
660            }
661        } else {
662            false
663        };
664        let saved_map_state = if in_map_value {
665            self.map_state.take()
666        } else {
667            None
668        };
669
670        // Resolve the item element name.
671        // Per the Smithy XML spec:
672        //  - For wrapped lists, items are emitted using the inner list member's
673        //    name (default "member", overridable via @xmlName on the list's
674        //    member shape).
675        //  - For flattened lists, the wrapper element is omitted and items are
676        //    emitted using the OUTER member's name (the list member's xml_name
677        //    or its smithy member_name) — the inner list member's name is
678        //    ignored because there is no wrapper to host it.
679        let names = schema.extension(&XML_LIST_NAMES);
680        let item_name = if schema.xml_flattened() && schema.xml_name().is_none() {
681            // Flattened: outer member's resolved element name (xml_name → list_item_name → member_name).
682            self.list_item_name
683                .clone()
684                .unwrap_or_else(|| names.item.clone())
685        } else {
686            names.item.clone()
687        };
688        // Resolve the wrapper element name. If the schema has @xmlName, use it.
689        // Otherwise, when this list is itself a list element of an outer call
690        // (parent set `self.list_item_name`), use the parent's child-name. This
691        // lets nested write_list calls produce the correct wrapper name even
692        // when codegen passes a generic placeholder schema (e.g. prelude::DOCUMENT)
693        // for the inner aggregate. Only matters for non-flattened, non-map-value
694        // wrappers.
695        let mut inherited_name = None;
696        let wrapper_name = self.resolve_name(schema, &mut inherited_name);
697
698        let saved_list_item = self.list_item_name.take();
699        self.list_item_name = Some(item_name);
700
701        // Propagate the list's inner-member @xmlNamespace so scalar item
702        // writes (whose schema is typically a generic prelude type) can
703        // inherit the namespace declaration. Only wrap-level lists set this;
704        // for flattened lists the items are at the parent level and use the
705        // parent member's own namespace if any.
706        let saved_list_item_ns =
707            std::mem::replace(&mut self.list_item_namespace, names.item_namespace.clone());
708
709        if schema.xml_flattened() || in_map_value {
710            write_elements(self)?;
711        } else {
712            self.open_element(wrapper_name);
713            // The wrapper element gets the OUTER list member's @xmlNamespace
714            // (which is on `schema` itself), not the inner-member namespace.
715            self.write_xmlns(schema, None);
716            write_elements(self)?;
717            self.close_element(wrapper_name);
718        }
719
720        self.list_item_name = saved_list_item;
721        self.list_item_namespace = saved_list_item_ns;
722
723        if in_map_value {
724            use std::fmt::Write;
725            let saved = saved_map_state.as_ref().unwrap();
726            let val_name = &saved.value_name;
727            let entry = &saved.entry_name;
728            write!(self.output, "</{val_name}></{entry}>").unwrap();
729            self.map_state = saved_map_state;
730        }
731        Ok(())
732    }
733
734    fn write_map(
735        &mut self,
736        schema: &Schema<'_>,
737        write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
738    ) -> Result<(), SerdeError> {
739        if schema.xml_attribute() {
740            return Err(SerdeError::custom(
741                "@xmlAttribute is not supported on aggregate types",
742            ));
743        }
744        if !self.filter_allows(schema) {
745            return Ok(());
746        }
747
748        self.flush_start_tag();
749
750        // Handle being called as a map value (nested maps)
751        let in_map_value = if let Some(map_state) = &mut self.map_state {
752            if !map_state.expecting_key {
753                use std::fmt::Write;
754                write!(self.output, "<{}>", map_state.value_name).unwrap();
755                map_state.expecting_key = true;
756                true
757            } else {
758                false
759            }
760        } else {
761            false
762        };
763
764        let outer_map_state = self.map_state.take();
765
766        // Resolve entry/key/value element names from the schema's map members.
767        let names = schema.extension(&XML_MAP_NAMES);
768        self.map_state = Some(MapState {
769            entry_name: names.entry.clone(),
770            key_name: names.key.clone(),
771            value_name: names.value.clone(),
772            key_namespace: names.key_namespace.clone(),
773            value_namespace: names.value_namespace.clone(),
774            expecting_key: true,
775        });
776
777        // Resolve the wrapper element name. If the schema has @xmlName, use it.
778        // Otherwise, when this map is itself an element of an outer list (parent set
779        // `self.list_item_name`), use the parent's child-name. This lets nested
780        // write_map calls produce the correct wrapper name even when codegen
781        // passes a generic placeholder schema (e.g. prelude::DOCUMENT) for the
782        // inner aggregate. Only matters for non-flattened, non-map-value wrappers.
783        let mut inherited_name = None;
784        let wrapper_name = self.resolve_name(schema, &mut inherited_name);
785
786        // Don't propagate parent list_item_name into map entries — entries are
787        // framed by map_state instead.
788        let saved_list_item = self.list_item_name.take();
789
790        if schema.xml_flattened() || in_map_value {
791            write_entries(self)?;
792        } else {
793            self.open_element(wrapper_name);
794            self.write_xmlns(schema, None);
795            write_entries(self)?;
796            self.close_element(wrapper_name);
797        }
798
799        self.list_item_name = saved_list_item;
800
801        self.map_state = outer_map_state;
802
803        if in_map_value {
804            use std::fmt::Write;
805            let saved = self.map_state.as_ref().unwrap();
806            let val_name = &saved.value_name;
807            let entry = &saved.entry_name;
808            write!(self.output, "</{val_name}></{entry}>").unwrap();
809        }
810        Ok(())
811    }
812
813    fn write_boolean(&mut self, schema: &Schema<'_>, value: bool) -> Result<(), SerdeError> {
814        self.write_safe_element(schema, if value { "true" } else { "false" });
815        Ok(())
816    }
817
818    fn write_byte(&mut self, schema: &Schema<'_>, value: i8) -> Result<(), SerdeError> {
819        self.write_safe_element(schema, &value.to_string());
820        Ok(())
821    }
822
823    fn write_short(&mut self, schema: &Schema<'_>, value: i16) -> Result<(), SerdeError> {
824        self.write_safe_element(schema, &value.to_string());
825        Ok(())
826    }
827
828    fn write_integer(&mut self, schema: &Schema<'_>, value: i32) -> Result<(), SerdeError> {
829        self.write_safe_element(schema, &value.to_string());
830        Ok(())
831    }
832
833    fn write_long(&mut self, schema: &Schema<'_>, value: i64) -> Result<(), SerdeError> {
834        self.write_safe_element(schema, &value.to_string());
835        Ok(())
836    }
837
838    fn write_float(&mut self, schema: &Schema<'_>, value: f32) -> Result<(), SerdeError> {
839        let text = if value.is_nan() {
840            "NaN".to_owned()
841        } else if value.is_infinite() {
842            if value.is_sign_positive() {
843                "Infinity".to_owned()
844            } else {
845                "-Infinity".to_owned()
846            }
847        } else {
848            value.to_string()
849        };
850        self.write_safe_element(schema, &text);
851        Ok(())
852    }
853
854    fn write_double(&mut self, schema: &Schema<'_>, value: f64) -> Result<(), SerdeError> {
855        let text = if value.is_nan() {
856            "NaN".to_owned()
857        } else if value.is_infinite() {
858            if value.is_sign_positive() {
859                "Infinity".to_owned()
860            } else {
861                "-Infinity".to_owned()
862            }
863        } else {
864            value.to_string()
865        };
866        self.write_safe_element(schema, &text);
867        Ok(())
868    }
869
870    fn write_big_integer(
871        &mut self,
872        schema: &Schema<'_>,
873        value: &BigInteger,
874    ) -> Result<(), SerdeError> {
875        self.write_safe_element(schema, value.as_ref());
876        Ok(())
877    }
878
879    fn write_big_decimal(
880        &mut self,
881        schema: &Schema<'_>,
882        value: &BigDecimal,
883    ) -> Result<(), SerdeError> {
884        self.write_safe_element(schema, value.as_ref());
885        Ok(())
886    }
887
888    fn write_string(&mut self, schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
889        if !self.filter_allows(schema) {
890            return Ok(());
891        }
892        if self.try_write_attribute(schema, value) {
893            return Ok(());
894        }
895        use std::fmt::Write;
896        self.flush_start_tag();
897        let escaped = crate::escape::escape(value);
898        if let Some(map_state) = &mut self.map_state {
899            if map_state.expecting_key {
900                let (entry, key) = (&map_state.entry_name, &map_state.key_name);
901                write!(self.output, "<{entry}><{key}").unwrap();
902                write_xmlns_attr(&mut self.output, map_state.key_namespace.as_deref());
903                write!(self.output, ">{escaped}</{key}>").unwrap();
904                map_state.expecting_key = false;
905            } else {
906                let (entry, val_name) = (&map_state.entry_name, &map_state.value_name);
907                write!(self.output, "<{val_name}").unwrap();
908                write_xmlns_attr(&mut self.output, map_state.value_namespace.as_deref());
909                write!(self.output, ">{escaped}</{val_name}></{entry}>").unwrap();
910                map_state.expecting_key = true;
911            }
912        } else {
913            let mut inherited_name = None;
914            let name = self.resolve_name(schema, &mut inherited_name);
915            // Use open_element/close_element so namespace attributes can be
916            // emitted via write_xmlns into the still-pending start tag.
917            self.open_element(name);
918            let inherited = self.list_item_namespace.clone();
919            self.write_xmlns(schema, inherited.as_deref());
920            self.flush_start_tag();
921            self.output.push_str(&escaped);
922            self.close_element(name);
923        }
924        Ok(())
925    }
926
927    fn write_blob(
928        &mut self,
929        schema: &Schema<'_>,
930        value: aws_smithy_types::Blob,
931    ) -> Result<(), SerdeError> {
932        let encoded = aws_smithy_types::base64::encode(value.as_ref());
933        self.write_safe_element(schema, &encoded);
934        Ok(())
935    }
936
937    fn write_timestamp(&mut self, schema: &Schema<'_>, value: &DateTime) -> Result<(), SerdeError> {
938        let format = self.resolve_timestamp_format(schema)?;
939        let formatted = value
940            .fmt(format)
941            .map_err(|e| SerdeError::custom(e.to_string()))?;
942        // Timestamp text is safe (digits, dashes, colons, T, Z, dots) — no escaping needed.
943        self.write_safe_element(schema, &formatted);
944        Ok(())
945    }
946
947    fn write_document(
948        &mut self,
949        _schema: &Schema<'_>,
950        _value: &Document,
951    ) -> Result<(), SerdeError> {
952        Err(SerdeError::custom(
953            "document types are not supported by REST XML",
954        ))
955    }
956
957    fn write_null(&mut self, _schema: &Schema<'_>) -> Result<(), SerdeError> {
958        // XML represents null/absent members by omitting the element entirely.
959        // Generated code skips None fields, so this should rarely be called.
960        // If it is (e.g. sparse collections), we simply emit nothing.
961        Ok(())
962    }
963}
964
965#[cfg(test)]
966mod tests {
967    use super::*;
968    use aws_smithy_schema::{prelude, shape_id, Schema, ShapeType};
969    use aws_smithy_types::Blob;
970
971    /// Renders a struct with one string member named `name`.
972    static NAME_MEMBER: Schema<'static> = Schema::new_member(
973        shape_id!("test", "Person$name"),
974        ShapeType::String,
975        "name",
976        0,
977    );
978    static PERSON_SCHEMA: Schema<'static> = Schema::new_struct(
979        shape_id!("test", "Person"),
980        ShapeType::Structure,
981        &[&NAME_MEMBER],
982    );
983
984    struct Person<'a> {
985        name: &'a str,
986    }
987
988    impl SerializableStruct for Person<'_> {
989        fn schema(&self) -> &Schema<'_> {
990            &PERSON_SCHEMA
991        }
992
993        fn serialize_members(
994            &self,
995            serializer: &mut dyn ShapeSerializer,
996        ) -> Result<(), SerdeError> {
997            serializer.write_string(&NAME_MEMBER, self.name)
998        }
999    }
1000
1001    fn serialize<F>(write: F) -> String
1002    where
1003        F: FnOnce(&mut XmlSerializer) -> Result<(), SerdeError>,
1004    {
1005        let mut ser = XmlSerializer::new(Arc::new(XmlCodecSettings::default()));
1006        write(&mut ser).expect("serialization failed");
1007        String::from_utf8(<XmlSerializer as FinishSerializer>::finish(ser)).unwrap()
1008    }
1009
1010    #[test]
1011    fn nested_collections_use_member_and_map_value_schemas() {
1012        static CELL: Schema<'static> = Schema::new_member(
1013            shape_id!("test", "NestedCollectionsMatrixListMemberList", "member"),
1014            ShapeType::Short,
1015            "member",
1016            0,
1017        );
1018        static ROW: Schema<'static> = Schema::new_member(
1019            shape_id!("test", "NestedCollectionsMatrixList", "member"),
1020            ShapeType::List,
1021            "member",
1022            0,
1023        )
1024        .with_list_member(&CELL);
1025        static MATRIX: Schema<'static> = Schema::new_member(
1026            shape_id!("test", "NestedCollections", "matrix"),
1027            ShapeType::List,
1028            "matrix",
1029            0,
1030        )
1031        .with_list_member(&ROW);
1032        static KEY: Schema<'static> = Schema::new_member(
1033            shape_id!("test", "NestedCollectionsValuesMap", "key"),
1034            ShapeType::String,
1035            "key",
1036            0,
1037        );
1038        static FLAG: Schema<'static> = Schema::new_member(
1039            shape_id!("test", "NestedCollectionsValuesMapValueList", "member"),
1040            ShapeType::Boolean,
1041            "member",
1042            0,
1043        );
1044        static VALUE: Schema<'static> = Schema::new_member(
1045            shape_id!("test", "NestedCollectionsValuesMap", "value"),
1046            ShapeType::List,
1047            "value",
1048            1,
1049        )
1050        .with_list_member(&FLAG);
1051        static VALUES: Schema<'static> = Schema::new_member(
1052            shape_id!("test", "NestedCollections", "values"),
1053            ShapeType::Map,
1054            "values",
1055            1,
1056        )
1057        .with_map_members(&KEY, &VALUE);
1058        static COLLECTIONS: Schema<'static> = Schema::new_struct(
1059            shape_id!("test", "NestedCollections"),
1060            ShapeType::Structure,
1061            &[&MATRIX, &VALUES],
1062        );
1063        struct NestedCollections {
1064            matrix: Vec<Vec<i16>>,
1065            values: std::collections::HashMap<String, Vec<bool>>,
1066        }
1067        impl SerializableStruct for NestedCollections {
1068            fn schema(&self) -> &Schema<'_> {
1069                &COLLECTIONS
1070            }
1071
1072            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1073                ser.write_list(&MATRIX, &|ser| {
1074                    for row in &self.matrix {
1075                        ser.write_list(&ROW, &|ser| {
1076                            for cell in row {
1077                                ser.write_short(&CELL, *cell)?;
1078                            }
1079                            Ok(())
1080                        })?;
1081                    }
1082                    Ok(())
1083                })?;
1084                ser.write_map(&VALUES, &|ser| {
1085                    for (key, flags) in &self.values {
1086                        ser.write_string(&KEY, key)?;
1087                        ser.write_list(&VALUE, &|ser| {
1088                            for flag in flags {
1089                                ser.write_boolean(&FLAG, *flag)?;
1090                            }
1091                            Ok(())
1092                        })?;
1093                    }
1094                    Ok(())
1095                })
1096            }
1097        }
1098        let value = NestedCollections {
1099            matrix: vec![vec![1, 2]],
1100            values: std::collections::HashMap::from([("flags".into(), vec![false, true])]),
1101        };
1102        let output = serialize(|ser| ser.write_struct(&COLLECTIONS, &value));
1103        assert_eq!(output, "<NestedCollections><matrix><member><member>1</member><member>2</member></member></matrix><values><entry><key>flags</key><value><member>false</member><member>true</member></value></entry></values></NestedCollections>");
1104    }
1105
1106    #[test]
1107    fn struct_with_string_member() {
1108        let p = Person { name: "Iago" };
1109        let out = serialize(|ser| ser.write_struct(&PERSON_SCHEMA, &p));
1110        assert_eq!(out, "<Person><name>Iago</name></Person>");
1111    }
1112
1113    #[test]
1114    fn list_item_names_override_union_target_xml_name() {
1115        static TEXT: Schema<'static> = Schema::new_member(
1116            shape_id!("test", "Choice", "text"),
1117            ShapeType::String,
1118            "text",
1119            0,
1120        );
1121        static CHOICE: Schema<'static> =
1122            Schema::new_struct(shape_id!("test", "Choice"), ShapeType::Union, &[&TEXT])
1123                .with_xml_name("ChoiceTargetRoot");
1124        struct Choice;
1125        impl SerializableStruct for Choice {
1126            fn schema(&self) -> &Schema<'_> {
1127                &CHOICE
1128            }
1129
1130            fn serialize_members(
1131                &self,
1132                serializer: &mut dyn ShapeSerializer,
1133            ) -> Result<(), SerdeError> {
1134                serializer.write_string(&TEXT, "hello")
1135            }
1136        }
1137        let member = Schema::new_member(
1138            shape_id!("test", "Choices", "member"),
1139            ShapeType::Union,
1140            "member",
1141            0,
1142        );
1143        let renamed = Schema::new_member(
1144            shape_id!("test", "Choices", "member"),
1145            ShapeType::Union,
1146            "member",
1147            0,
1148        )
1149        .with_xml_name("item");
1150        for (item_schema, flattened, expected) in [
1151            (
1152                &member,
1153                false,
1154                "<choices><member><text>hello</text></member></choices>",
1155            ),
1156            (
1157                &renamed,
1158                false,
1159                "<choices><item><text>hello</text></item></choices>",
1160            ),
1161            (&member, true, "<choices><text>hello</text></choices>"),
1162        ] {
1163            let mut list = Schema::new_member(
1164                shape_id!("test", "Output", "choices"),
1165                ShapeType::List,
1166                "choices",
1167                0,
1168            )
1169            .with_list_member(item_schema);
1170            if flattened {
1171                list = list.with_xml_flattened();
1172            }
1173            let output = serialize(|serializer| {
1174                serializer.write_list(&list, &|serializer| {
1175                    serializer.write_struct(&CHOICE, &Choice)
1176                })
1177            });
1178            assert_eq!(output, expected);
1179        }
1180        assert_eq!(
1181            serialize(|serializer| serializer.write_struct(&CHOICE, &Choice)),
1182            "<ChoiceTargetRoot><text>hello</text></ChoiceTargetRoot>",
1183        );
1184    }
1185
1186    #[test]
1187    fn struct_with_no_members_self_closes() {
1188        struct Empty;
1189        impl SerializableStruct for Empty {
1190            fn schema(&self) -> &Schema<'_> {
1191                &EMPTY_SCHEMA
1192            }
1193
1194            fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1195                Ok(())
1196            }
1197        }
1198        static EMPTY_SCHEMA: Schema<'static> =
1199            Schema::new_struct(shape_id!("test", "Empty"), ShapeType::Structure, &[]);
1200
1201        let out = serialize(|ser| ser.write_struct(&EMPTY_SCHEMA, &Empty));
1202        assert_eq!(out, "<Empty></Empty>");
1203    }
1204
1205    #[test]
1206    fn struct_string_value_is_escaped() {
1207        let p = Person { name: "<a&b>" };
1208        let out = serialize(|ser| ser.write_struct(&PERSON_SCHEMA, &p));
1209        assert_eq!(out, "<Person><name>&lt;a&amp;b&gt;</name></Person>");
1210    }
1211
1212    #[test]
1213    fn struct_string_value_eol_is_encoded() {
1214        // Per the XML EOL Encoding SEP, \r and \n must be escaped as
1215        // numeric character references to survive XML EOL normalization.
1216        let p = Person { name: "a\r\nb" };
1217        let out = serialize(|ser| ser.write_struct(&PERSON_SCHEMA, &p));
1218        assert_eq!(out, "<Person><name>a&#xD;&#xA;b</name></Person>");
1219    }
1220
1221    #[test]
1222    fn nested_structs_close_correctly() {
1223        // Schemas: Outer { inner: Inner { name: String } }.
1224        // Note: the inner-struct schema is not exercised directly because
1225        // member dispatch in this codec uses the *member* schema, not the
1226        // target shape's schema. The Inner type's `SerializableStruct` impl
1227        // is what drives inner serialization.
1228        static INNER_NAME: Schema<'static> = Schema::new_member(
1229            shape_id!("test", "Inner$name"),
1230            ShapeType::String,
1231            "name",
1232            0,
1233        );
1234        static OUTER_INNER: Schema<'static> = Schema::new_member(
1235            shape_id!("test", "Outer$inner"),
1236            ShapeType::Structure,
1237            "inner",
1238            0,
1239        );
1240        static OUTER_SCHEMA: Schema<'static> = Schema::new_struct(
1241            shape_id!("test", "Outer"),
1242            ShapeType::Structure,
1243            &[&OUTER_INNER],
1244        );
1245
1246        struct Inner<'a> {
1247            name: &'a str,
1248        }
1249        impl SerializableStruct for Inner<'_> {
1250            fn schema(&self) -> &Schema<'_> {
1251                static SCHEMA: Schema<'static> = Schema::new_struct(
1252                    aws_smithy_schema::shape_id!("test", "Inner"),
1253                    aws_smithy_schema::ShapeType::Structure,
1254                    &[&INNER_NAME],
1255                );
1256                &SCHEMA
1257            }
1258
1259            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1260                ser.write_string(&INNER_NAME, self.name)
1261            }
1262        }
1263        struct Outer<'a> {
1264            inner: Inner<'a>,
1265        }
1266        impl SerializableStruct for Outer<'_> {
1267            fn schema(&self) -> &Schema<'_> {
1268                &OUTER_SCHEMA
1269            }
1270
1271            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1272                // For nested structs, dispatch on the *member* schema so that
1273                // the resolved element name is the field's name (or its
1274                // @xmlName), not the target shape's name.
1275                ser.write_struct(&OUTER_INNER, &self.inner)
1276            }
1277        }
1278
1279        let o = Outer {
1280            inner: Inner { name: "v" },
1281        };
1282        let out = serialize(|ser| ser.write_struct(&OUTER_SCHEMA, &o));
1283        assert_eq!(out, "<Outer><inner><name>v</name></inner></Outer>");
1284    }
1285
1286    #[test]
1287    fn xml_name_overrides_member_name() {
1288        static RENAMED_MEMBER: Schema<'static> = Schema::new_member(
1289            shape_id!("test", "Person$name"),
1290            ShapeType::String,
1291            "name",
1292            0,
1293        )
1294        .with_xml_name("FullName");
1295        static PERSON_SCHEMA: Schema<'static> = Schema::new_struct(
1296            shape_id!("test", "Person"),
1297            ShapeType::Structure,
1298            &[&RENAMED_MEMBER],
1299        );
1300
1301        struct P;
1302        impl SerializableStruct for P {
1303            fn schema(&self) -> &Schema<'_> {
1304                &PERSON_SCHEMA
1305            }
1306
1307            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1308                ser.write_string(&RENAMED_MEMBER, "v")
1309            }
1310        }
1311
1312        let out = serialize(|ser| ser.write_struct(&PERSON_SCHEMA, &P));
1313        assert_eq!(out, "<Person><FullName>v</FullName></Person>");
1314    }
1315
1316    /// The same override, driven by a schema materialized at runtime. XML is
1317    /// the largest consumer of trait-value accessors, so this covers the bulk
1318    /// of the read surface for runtime-built schemas in one test. Also asserts
1319    /// `@xmlNamespace`, whose wrapper carries two runtime strings.
1320    #[test]
1321    fn xml_name_and_namespace_from_a_runtime_schema() {
1322        // Arena the caller owns; nothing here is 'static.
1323        let arena: Vec<String> = vec![
1324            String::from("name"),                // member name
1325            String::from("RuntimeFullName"),     // @xmlName
1326            String::from("https://ns.example/"), // @xmlNamespace uri
1327        ];
1328
1329        let member: Schema<'_> = Schema::new_member(
1330            shape_id!("test", "Person$name"),
1331            ShapeType::String,
1332            &arena[0],
1333            0,
1334        )
1335        .with_xml_name(&arena[1]);
1336
1337        let members = [&member];
1338        let person: Schema<'_> =
1339            Schema::new_struct(shape_id!("test", "Person"), ShapeType::Structure, &members)
1340                .with_xml_namespace(&arena[2], None);
1341
1342        struct P<'a> {
1343            schema: &'a Schema<'a>,
1344            member: &'a Schema<'a>,
1345        }
1346        impl SerializableStruct for P<'_> {
1347            fn schema(&self) -> &Schema<'_> {
1348                self.schema
1349            }
1350
1351            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1352                ser.write_string(self.member, "v")
1353            }
1354        }
1355        let value = P {
1356            schema: &person,
1357            member: &member,
1358        };
1359        let erased: &dyn SerializableStruct = &value;
1360        assert!(std::ptr::eq(erased.schema(), &person));
1361        let out = serialize(|ser| ser.write_struct(&person, erased));
1362        assert_eq!(
1363            out,
1364            "<Person xmlns=\"https://ns.example/\"><RuntimeFullName>v</RuntimeFullName></Person>"
1365        );
1366    }
1367
1368    #[test]
1369    fn original_name_overrides_id_for_synthetic_root() {
1370        // Synthetic shapes have id name "OperationInput" but original_name is
1371        // "OperationRequest" (the user-authored name). The codec should use
1372        // the original name for the root element when there's no @xmlName.
1373        static SYNTHETIC: Schema<'static> = Schema::new_struct(
1374            shape_id!("test.synthetic", "FooInput"),
1375            ShapeType::Structure,
1376            &[],
1377        )
1378        .with_original_name("FooRequest");
1379
1380        struct Empty;
1381        impl SerializableStruct for Empty {
1382            fn schema(&self) -> &Schema<'_> {
1383                &SYNTHETIC
1384            }
1385
1386            fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1387                Ok(())
1388            }
1389        }
1390
1391        let out = serialize(|ser| ser.write_struct(&SYNTHETIC, &Empty));
1392        assert_eq!(out, "<FooRequest></FooRequest>");
1393    }
1394
1395    // Scalar member writes (boolean, ints, floats, blob, timestamp).
1396
1397    static SCALAR_MEMBER: Schema<'static> =
1398        Schema::new_member(shape_id!("test", "S$v"), ShapeType::Integer, "v", 0);
1399
1400    #[test]
1401    fn write_boolean_true() {
1402        let out = serialize(|ser| ser.write_boolean(&SCALAR_MEMBER, true));
1403        assert_eq!(out, "<v>true</v>");
1404    }
1405
1406    #[test]
1407    fn write_boolean_false() {
1408        let out = serialize(|ser| ser.write_boolean(&SCALAR_MEMBER, false));
1409        assert_eq!(out, "<v>false</v>");
1410    }
1411
1412    #[test]
1413    fn write_integer_negative() {
1414        let out = serialize(|ser| ser.write_integer(&SCALAR_MEMBER, -42));
1415        assert_eq!(out, "<v>-42</v>");
1416    }
1417
1418    #[test]
1419    fn write_long_large() {
1420        let out = serialize(|ser| ser.write_long(&SCALAR_MEMBER, i64::MAX));
1421        assert_eq!(out, format!("<v>{}</v>", i64::MAX));
1422    }
1423
1424    #[test]
1425    fn write_float_special_values() {
1426        let out = serialize(|ser| ser.write_float(&SCALAR_MEMBER, f32::NAN));
1427        assert_eq!(out, "<v>NaN</v>");
1428        let out = serialize(|ser| ser.write_float(&SCALAR_MEMBER, f32::INFINITY));
1429        assert_eq!(out, "<v>Infinity</v>");
1430        let out = serialize(|ser| ser.write_float(&SCALAR_MEMBER, f32::NEG_INFINITY));
1431        assert_eq!(out, "<v>-Infinity</v>");
1432    }
1433
1434    #[test]
1435    fn write_double_normal() {
1436        let out = serialize(|ser| ser.write_double(&SCALAR_MEMBER, 1.5));
1437        assert_eq!(out, "<v>1.5</v>");
1438    }
1439
1440    #[test]
1441    fn write_blob_base64() {
1442        let blob = Blob::new(b"hello");
1443        let out = serialize(|ser| ser.write_blob(&SCALAR_MEMBER, blob.clone()));
1444        assert_eq!(out, "<v>aGVsbG8=</v>");
1445    }
1446
1447    #[test]
1448    fn write_timestamp_default_datetime() {
1449        // Default format for REST XML is date-time (ISO 8601).
1450        let ts = DateTime::from_secs(1515531081);
1451        let out = serialize(|ser| ser.write_timestamp(&SCALAR_MEMBER, &ts));
1452        assert_eq!(out, "<v>2018-01-09T20:51:21Z</v>");
1453    }
1454
1455    #[test]
1456    fn write_timestamp_epoch_seconds_override() {
1457        use aws_smithy_schema::traits::TimestampFormat as SchemaTimestampFormat;
1458        static TS_MEMBER: Schema<'static> =
1459            Schema::new_member(shape_id!("test", "S$t"), ShapeType::Timestamp, "t", 0)
1460                .with_timestamp_format(SchemaTimestampFormat::EpochSeconds);
1461        let ts = DateTime::from_secs(1515531081);
1462        let out = serialize(|ser| ser.write_timestamp(&TS_MEMBER, &ts));
1463        assert_eq!(out, "<t>1515531081</t>");
1464    }
1465
1466    #[test]
1467    fn write_null_emits_nothing() {
1468        let out = serialize(|ser| ser.write_null(&SCALAR_MEMBER));
1469        assert_eq!(out, "");
1470    }
1471
1472    // `@xmlAttribute` emission and ordering relative to child elements.
1473
1474    #[test]
1475    fn attribute_string_on_struct() {
1476        static ATTR_MEMBER: Schema<'static> =
1477            Schema::new_member(shape_id!("test", "X$id"), ShapeType::String, "id", 0)
1478                .with_xml_attribute();
1479        static CHILD_MEMBER: Schema<'static> =
1480            Schema::new_member(shape_id!("test", "X$name"), ShapeType::String, "name", 1);
1481        static X_SCHEMA: Schema<'static> = Schema::new_struct(
1482            shape_id!("test", "X"),
1483            ShapeType::Structure,
1484            &[&ATTR_MEMBER, &CHILD_MEMBER],
1485        );
1486
1487        struct X;
1488        impl SerializableStruct for X {
1489            fn schema(&self) -> &Schema<'_> {
1490                &X_SCHEMA
1491            }
1492
1493            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1494                ser.write_string(&ATTR_MEMBER, "42")?;
1495                ser.write_string(&CHILD_MEMBER, "hello")
1496            }
1497        }
1498
1499        let out = serialize(|ser| ser.write_struct(&X_SCHEMA, &X));
1500        assert_eq!(out, "<X id=\"42\"><name>hello</name></X>");
1501    }
1502
1503    #[test]
1504    fn attribute_integer_on_struct() {
1505        static ATTR: Schema<'static> =
1506            Schema::new_member(shape_id!("test", "X$count"), ShapeType::Integer, "count", 0)
1507                .with_xml_attribute();
1508        static X_SCHEMA: Schema<'static> =
1509            Schema::new_struct(shape_id!("test", "X"), ShapeType::Structure, &[&ATTR]);
1510
1511        struct X;
1512        impl SerializableStruct for X {
1513            fn schema(&self) -> &Schema<'_> {
1514                &X_SCHEMA
1515            }
1516
1517            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1518                ser.write_integer(&ATTR, 7)
1519            }
1520        }
1521
1522        let out = serialize(|ser| ser.write_struct(&X_SCHEMA, &X));
1523        assert_eq!(out, "<X count=\"7\"></X>");
1524    }
1525
1526    #[test]
1527    fn attribute_value_is_escaped() {
1528        static ATTR: Schema<'static> =
1529            Schema::new_member(shape_id!("test", "X$v"), ShapeType::String, "v", 0)
1530                .with_xml_attribute();
1531        static X_SCHEMA: Schema<'static> =
1532            Schema::new_struct(shape_id!("test", "X"), ShapeType::Structure, &[&ATTR]);
1533
1534        struct X;
1535        impl SerializableStruct for X {
1536            fn schema(&self) -> &Schema<'_> {
1537                &X_SCHEMA
1538            }
1539
1540            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1541                ser.write_string(&ATTR, "a\"b&c")
1542            }
1543        }
1544
1545        let out = serialize(|ser| ser.write_struct(&X_SCHEMA, &X));
1546        assert_eq!(out, "<X v=\"a&quot;b&amp;c\"></X>");
1547    }
1548
1549    #[test]
1550    fn attribute_on_struct_returns_error() {
1551        static ATTR_STRUCT: Schema<'static> = Schema::new_member(
1552            shape_id!("test", "X$inner"),
1553            ShapeType::Structure,
1554            "inner",
1555            0,
1556        )
1557        .with_xml_attribute();
1558
1559        struct Empty;
1560        impl SerializableStruct for Empty {
1561            fn schema(&self) -> &Schema<'_> {
1562                &aws_smithy_schema::prelude::UNIT
1563            }
1564
1565            fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1566                Ok(())
1567            }
1568        }
1569
1570        let mut ser = XmlSerializer::new(Arc::new(XmlCodecSettings::default()));
1571        let result = ser.write_struct(&ATTR_STRUCT, &Empty);
1572        assert!(result.is_err());
1573    }
1574
1575    // `@xmlNamespace` emission on root and child elements.
1576
1577    #[test]
1578    fn namespace_on_struct() {
1579        static NS_SCHEMA: Schema<'static> =
1580            Schema::new_struct(shape_id!("test", "X"), ShapeType::Structure, &[])
1581                .with_xml_namespace("https://example.com", None);
1582
1583        struct Empty;
1584        impl SerializableStruct for Empty {
1585            fn schema(&self) -> &Schema<'_> {
1586                &NS_SCHEMA
1587            }
1588
1589            fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1590                Ok(())
1591            }
1592        }
1593
1594        let out = serialize(|ser| ser.write_struct(&NS_SCHEMA, &Empty));
1595        assert_eq!(out, "<X xmlns=\"https://example.com\"></X>");
1596    }
1597
1598    #[test]
1599    fn namespace_with_prefix() {
1600        static NS_SCHEMA: Schema<'static> =
1601            Schema::new_struct(shape_id!("test", "X"), ShapeType::Structure, &[])
1602                .with_xml_namespace("https://example.com", Some("ex"));
1603
1604        struct Empty;
1605        impl SerializableStruct for Empty {
1606            fn schema(&self) -> &Schema<'_> {
1607                &NS_SCHEMA
1608            }
1609
1610            fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1611                Ok(())
1612            }
1613        }
1614
1615        let out = serialize(|ser| ser.write_struct(&NS_SCHEMA, &Empty));
1616        assert_eq!(out, "<X xmlns:ex=\"https://example.com\"></X>");
1617    }
1618
1619    #[test]
1620    fn namespace_with_children() {
1621        static CHILD: Schema<'static> =
1622            Schema::new_member(shape_id!("test", "X$v"), ShapeType::String, "v", 0);
1623        static NS_SCHEMA: Schema<'static> =
1624            Schema::new_struct(shape_id!("test", "X"), ShapeType::Structure, &[&CHILD])
1625                .with_xml_namespace("urn:foo", None);
1626
1627        struct X;
1628        impl SerializableStruct for X {
1629            fn schema(&self) -> &Schema<'_> {
1630                &NS_SCHEMA
1631            }
1632
1633            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1634                ser.write_string(&CHILD, "hi")
1635            }
1636        }
1637
1638        let out = serialize(|ser| ser.write_struct(&NS_SCHEMA, &X));
1639        assert_eq!(out, "<X xmlns=\"urn:foo\"><v>hi</v></X>");
1640    }
1641
1642    // List serialization: wrapped, flattened, and `@xmlName` overrides.
1643
1644    #[test]
1645    fn list_wrapped() {
1646        // Schema: struct S { items: List<String> }
1647        // List member schema defaults to name "member".
1648        static LIST_ITEM: Schema<'static> = Schema::new_member(
1649            shape_id!("test", "L$member"),
1650            ShapeType::String,
1651            "member",
1652            0,
1653        );
1654        static LIST_MEMBER: Schema<'static> =
1655            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 0);
1656
1657        let out = serialize(|ser| {
1658            ser.write_list(&LIST_MEMBER, &|ser| {
1659                ser.write_string(&LIST_ITEM, "a")?;
1660                ser.write_string(&LIST_ITEM, "b")
1661            })
1662        });
1663        assert_eq!(out, "<items><member>a</member><member>b</member></items>");
1664    }
1665
1666    #[test]
1667    fn list_wrapped_with_xml_name_on_item() {
1668        // @xmlName("Item") on the list's member schema.
1669        static LIST_ITEM: Schema<'static> = Schema::new_member(
1670            shape_id!("test", "L$member"),
1671            ShapeType::String,
1672            "member",
1673            0,
1674        )
1675        .with_xml_name("Item");
1676        static LIST_MEMBER: Schema<'static> =
1677            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 0);
1678
1679        let out = serialize(|ser| {
1680            ser.write_list(&LIST_MEMBER, &|ser| {
1681                ser.write_string(&LIST_ITEM, "a")?;
1682                ser.write_string(&LIST_ITEM, "b")
1683            })
1684        });
1685        assert_eq!(out, "<items><Item>a</Item><Item>b</Item></items>");
1686    }
1687
1688    #[test]
1689    fn list_flattened() {
1690        // @xmlFlattened on the struct member. Items use the member schema
1691        // passed to write_string (which carries the item element name).
1692        static LIST_ITEM: Schema<'static> = Schema::new_member(
1693            shape_id!("test", "L$member"),
1694            ShapeType::String,
1695            "member",
1696            0,
1697        )
1698        .with_xml_name("item");
1699        static LIST_MEMBER: Schema<'static> =
1700            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 0)
1701                .with_xml_flattened();
1702
1703        let out = serialize(|ser| {
1704            ser.write_list(&LIST_MEMBER, &|ser| {
1705                ser.write_string(&LIST_ITEM, "a")?;
1706                ser.write_string(&LIST_ITEM, "b")
1707            })
1708        });
1709        // Flattened: no wrapper, items emitted directly.
1710        assert_eq!(out, "<item>a</item><item>b</item>");
1711    }
1712
1713    // Map serialization: wrapped, flattened, and `@xmlName` on key/value.
1714
1715    #[test]
1716    fn map_wrapped() {
1717        static KEY_SCHEMA: Schema<'static> =
1718            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1719        static VALUE_SCHEMA: Schema<'static> =
1720            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1);
1721        static MAP_MEMBER: Schema<'static> =
1722            Schema::new_member(shape_id!("test", "S$myMap"), ShapeType::Map, "myMap", 0)
1723                .with_map_members(&KEY_SCHEMA, &VALUE_SCHEMA);
1724
1725        let out = serialize(|ser| {
1726            ser.write_map(&MAP_MEMBER, &|ser| {
1727                ser.write_string(&prelude::STRING, "k1")?;
1728                ser.write_string(&prelude::STRING, "v1")?;
1729                ser.write_string(&prelude::STRING, "k2")?;
1730                ser.write_string(&prelude::STRING, "v2")
1731            })
1732        });
1733        assert_eq!(
1734            out,
1735            "<myMap><entry><key>k1</key><value>v1</value></entry><entry><key>k2</key><value>v2</value></entry></myMap>"
1736        );
1737    }
1738
1739    #[test]
1740    fn map_wrapped_with_renamed_key_value() {
1741        // @xmlName on key/value schemas.
1742        static REN_KEY: Schema<'static> =
1743            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0)
1744                .with_xml_name("Attribute");
1745        static REN_VALUE: Schema<'static> =
1746            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1)
1747                .with_xml_name("Setting");
1748
1749        static MAP_MEMBER: Schema<'static> =
1750            Schema::new_member(shape_id!("test", "S$m"), ShapeType::Map, "m", 0)
1751                .with_map_members(&REN_KEY, &REN_VALUE);
1752
1753        let out = serialize(|ser| {
1754            ser.write_map(&MAP_MEMBER, &|ser| {
1755                ser.write_string(&prelude::STRING, "k")?;
1756                ser.write_string(&prelude::STRING, "v")
1757            })
1758        });
1759        assert_eq!(
1760            out,
1761            "<m><entry><Attribute>k</Attribute><Setting>v</Setting></entry></m>"
1762        );
1763    }
1764
1765    #[test]
1766    fn map_flattened() {
1767        static KEY_SCHEMA: Schema<'static> =
1768            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1769        static VALUE_SCHEMA: Schema<'static> =
1770            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1);
1771        static MAP_MEMBER: Schema<'static> =
1772            Schema::new_member(shape_id!("test", "S$m"), ShapeType::Map, "m", 0)
1773                .with_xml_flattened()
1774                .with_map_members(&KEY_SCHEMA, &VALUE_SCHEMA);
1775
1776        let out = serialize(|ser| {
1777            ser.write_map(&MAP_MEMBER, &|ser| {
1778                ser.write_string(&prelude::STRING, "a")?;
1779                ser.write_string(&prelude::STRING, "1")?;
1780                ser.write_string(&prelude::STRING, "b")?;
1781                ser.write_string(&prelude::STRING, "2")
1782            })
1783        });
1784        // Flattened: no wrapper, entries use member name as entry element.
1785        assert_eq!(
1786            out,
1787            "<m><key>a</key><value>1</value></m><m><key>b</key><value>2</value></m>"
1788        );
1789    }
1790
1791    #[test]
1792    fn write_document_returns_error() {
1793        let mut ser = XmlSerializer::new(Arc::new(XmlCodecSettings::default()));
1794        let result = ser.write_document(&SCALAR_MEMBER, &Document::Object(Default::default()));
1795        assert_eq!(
1796            result.unwrap_err().to_string(),
1797            "document types are not supported by REST XML"
1798        );
1799    }
1800
1801    #[test]
1802    fn map_with_struct_value() {
1803        static HI: Schema<'static> =
1804            Schema::new_member(shape_id!("test", "G$hi"), ShapeType::String, "hi", 0);
1805        static GREETING: Schema<'static> = Schema::new_struct(
1806            shape_id!("test", "GreetingStruct"),
1807            ShapeType::Structure,
1808            &[&HI],
1809        );
1810        static KEY: Schema<'static> =
1811            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0);
1812        static VALUE: Schema<'static> =
1813            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1);
1814        static MAP: Schema<'static> =
1815            Schema::new_member(shape_id!("test", "S$myMap"), ShapeType::Map, "myMap", 0)
1816                .with_map_members(&KEY, &VALUE);
1817
1818        struct G;
1819        impl SerializableStruct for G {
1820            fn schema(&self) -> &Schema<'_> {
1821                &GREETING
1822            }
1823
1824            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1825                ser.write_string(&HI, "bye")
1826            }
1827        }
1828
1829        let out = serialize(|ser| {
1830            ser.write_map(&MAP, &|ser| {
1831                ser.write_string(&prelude::STRING, "baz")?;
1832                ser.write_struct(&GREETING, &G)
1833            })
1834        });
1835        assert_eq!(
1836            out,
1837            "<myMap><entry><key>baz</key><value><hi>bye</hi></value></entry></myMap>"
1838        );
1839    }
1840
1841    /// Regression test for a `has_attrs` short-circuit bug:
1842    /// `write_struct(member_schema, value)` where `member_schema` was
1843    /// produced by `Schema::new_member` and the target struct has
1844    /// `@xmlAttribute` members.
1845    ///
1846    /// Pre-fix: `has_attrs = schema.members().iter().any(|m| m.xml_attribute())`
1847    /// looked at `member_schema.members()`, which is empty for member
1848    /// schemas regardless of the target's actual member list. The
1849    /// AttributesOnly pass was therefore skipped and the attribute was
1850    /// dropped.
1851    ///
1852    /// Post-fix: when `schema.member_name().is_some()` (i.e. the schema
1853    /// was built via `Schema::new_member`), the codec runs the
1854    /// AttributesOnly pass conservatively. The per-member
1855    /// `filter_allows` check then correctly emits the attribute.
1856    ///
1857    /// Surfaced by the Smithy `XmlNamespaceSimpleScalarProperties`
1858    /// protocol test where `<Nested xsi:someName="...">` was missing
1859    /// the `someName` attribute when emitted via a member schema.
1860    #[test]
1861    fn member_schema_struct_emits_attribute_member() {
1862        // Inner struct with an attribute member.
1863        static ATTR_FIELD_MEMBER: Schema<'static> = Schema::new_member(
1864            shape_id!("test", "Inner$attrField"),
1865            ShapeType::String,
1866            "attrField",
1867            0,
1868        )
1869        .with_xml_name("xsi:someName")
1870        .with_xml_attribute();
1871        static INNER_TARGET: Schema<'static> = Schema::new_struct(
1872            shape_id!("test", "Inner"),
1873            ShapeType::Structure,
1874            &[&ATTR_FIELD_MEMBER],
1875        );
1876        // Outer struct with a member pointing at `Inner`. Crucially this
1877        // is a member schema (Schema::new_member), not the target
1878        // struct's schema.
1879        static INNER_MEMBER: Schema<'static> = Schema::new_member(
1880            shape_id!("test", "Outer$nested"),
1881            ShapeType::Structure,
1882            "nested",
1883            0,
1884        );
1885        static OUTER_SCHEMA: Schema<'static> = Schema::new_struct(
1886            shape_id!("test", "Outer"),
1887            ShapeType::Structure,
1888            &[&INNER_MEMBER],
1889        );
1890
1891        struct Inner<'a> {
1892            attr: &'a str,
1893        }
1894        impl SerializableStruct for Inner<'_> {
1895            fn schema(&self) -> &Schema<'_> {
1896                &INNER_TARGET
1897            }
1898
1899            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1900                ser.write_string(&ATTR_FIELD_MEMBER, self.attr)
1901            }
1902        }
1903        struct Outer<'a> {
1904            inner: Inner<'a>,
1905        }
1906        impl SerializableStruct for Outer<'_> {
1907            fn schema(&self) -> &Schema<'_> {
1908                &OUTER_SCHEMA
1909            }
1910
1911            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
1912                // Pass the *member* schema (matches codegen output), not
1913                // the target struct's `INNER_TARGET`. This is the
1914                // configuration that triggered the original bug.
1915                let _ = &INNER_TARGET;
1916                ser.write_struct(&INNER_MEMBER, &self.inner)
1917            }
1918        }
1919
1920        let outer = Outer {
1921            inner: Inner { attr: "v" },
1922        };
1923        let out = serialize(|ser| ser.write_struct(&OUTER_SCHEMA, &outer));
1924        assert_eq!(out, r#"<Outer><nested xsi:someName="v"></nested></Outer>"#);
1925    }
1926
1927    /// Outer map whose value member targets a map shape with `@xmlName`
1928    /// on the inner map's key and value. Verifies that element-name
1929    /// overrides on inner key/value flow through the nested
1930    /// `write_map` call when the inner-map schema is passed directly
1931    /// (i.e., when the outer's value-member schema chains the inner
1932    /// map's `_KEY`/`_VALUE`).
1933    #[test]
1934    fn map_value_is_inner_map_with_renamed_inner_key_value() {
1935        // Inner map's renamed key and value member schemas.
1936        static INNER_KEY: Schema<'static> = Schema::new_member(
1937            shape_id!("test", "InnerMap$key"),
1938            ShapeType::String,
1939            "key",
1940            0,
1941        )
1942        .with_xml_name("InnerKey");
1943        static INNER_VAL: Schema<'static> = Schema::new_member(
1944            shape_id!("test", "InnerMap$value"),
1945            ShapeType::String,
1946            "value",
1947            1,
1948        )
1949        .with_xml_name("InnerVal");
1950
1951        // Outer map's value member: target shape is the inner Map.
1952        // Codegen chains the inner map's key/value schemas onto this
1953        // member schema, so `schema.key()` / `.member()` resolve to the
1954        // renamed inner schemas inside the nested `write_map`.
1955        static OUTER_VALUE: Schema<'static> = Schema::new_member(
1956            shape_id!("test", "OuterMap$value"),
1957            ShapeType::Map,
1958            "value",
1959            1,
1960        )
1961        .with_map_members(&INNER_KEY, &INNER_VAL);
1962
1963        // Outer map's key (no rename) and the outer map member itself.
1964        static OUTER_KEY: Schema<'static> = Schema::new_member(
1965            shape_id!("test", "OuterMap$key"),
1966            ShapeType::String,
1967            "key",
1968            0,
1969        );
1970        static OUTER_MAP: Schema<'static> = Schema::new_member(
1971            shape_id!("test", "S$outerMap"),
1972            ShapeType::Map,
1973            "outerMap",
1974            0,
1975        )
1976        .with_map_members(&OUTER_KEY, &OUTER_VALUE);
1977
1978        let out = serialize(|ser| {
1979            ser.write_map(&OUTER_MAP, &|ser| {
1980                ser.write_string(&prelude::STRING, "ok")?;
1981                // Nested write_map is called with the inner map's chained
1982                // schema directly — no runtime substitution needed.
1983                ser.write_map(&OUTER_VALUE, &|ser| {
1984                    ser.write_string(&prelude::STRING, "ik")?;
1985                    ser.write_string(&prelude::STRING, "iv")
1986                })
1987            })
1988        });
1989
1990        assert_eq!(
1991            out,
1992            "<outerMap><entry><key>ok</key><value><entry><InnerKey>ik</InnerKey><InnerVal>iv</InnerVal></entry></value></entry></outerMap>"
1993        );
1994    }
1995
1996    /// List whose member targets a map shape with `@xmlName` on the
1997    /// inner map's key and value. Mirrors the map-of-map case for
1998    /// list-of-map nesting.
1999    #[test]
2000    fn list_member_is_inner_map_with_renamed_inner_key_value() {
2001        static INNER_KEY: Schema<'static> = Schema::new_member(
2002            shape_id!("test", "InnerMap$key"),
2003            ShapeType::String,
2004            "key",
2005            0,
2006        )
2007        .with_xml_name("Attr");
2008        static INNER_VAL: Schema<'static> = Schema::new_member(
2009            shape_id!("test", "InnerMap$value"),
2010            ShapeType::String,
2011            "value",
2012            1,
2013        )
2014        .with_xml_name("Set");
2015
2016        // The list's member target is the inner Map.
2017        static LIST_ITEM: Schema<'static> = Schema::new_member(
2018            shape_id!("test", "OuterList$member"),
2019            ShapeType::Map,
2020            "member",
2021            0,
2022        )
2023        .with_map_members(&INNER_KEY, &INNER_VAL);
2024
2025        static OUTER_LIST: Schema<'static> =
2026            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 0)
2027                .with_list_member(&LIST_ITEM);
2028
2029        let out = serialize(|ser| {
2030            ser.write_list(&OUTER_LIST, &|ser| {
2031                // Each list element is a (single-entry) inner map.
2032                ser.write_map(&LIST_ITEM, &|ser| {
2033                    ser.write_string(&prelude::STRING, "k1")?;
2034                    ser.write_string(&prelude::STRING, "v1")
2035                })
2036            })
2037        });
2038
2039        // Wrapped list, single element. The element wrapper is `<member>`
2040        // (the list's member element name); inside it is the inner map
2041        // with renamed key/value.
2042        assert_eq!(
2043            out,
2044            "<items><member><entry><Attr>k1</Attr><Set>v1</Set></entry></member></items>"
2045        );
2046    }
2047
2048    // --- Required value-type members are serialized even when equal to the
2049    // zero/default (bool false). Mirrors the generated non-optional branch
2050    // (unconditional write, never a skip-if-default). Shapes the ELB
2051    // `LoadBalancerAttributes { ConnectionDraining { Enabled = false } }` case.
2052    static CD_ENABLED_X: Schema<'static> = Schema::new_member(
2053        shape_id!("test", "ConnectionDraining$enabled"),
2054        ShapeType::Boolean,
2055        "enabled",
2056        0,
2057    );
2058    static LBA_CD_X: Schema<'static> = Schema::new_member(
2059        shape_id!("test", "LoadBalancerAttributes$connectionDraining"),
2060        ShapeType::Structure,
2061        "connectionDraining",
2062        0,
2063    );
2064    static LBA_SCHEMA_X: Schema<'static> = Schema::new_struct(
2065        shape_id!("test", "LoadBalancerAttributes"),
2066        ShapeType::Structure,
2067        &[&LBA_CD_X],
2068    );
2069
2070    struct ConnectionDrainingX {
2071        enabled: bool,
2072    }
2073    impl SerializableStruct for ConnectionDrainingX {
2074        fn schema(&self) -> &Schema<'_> {
2075            static SCHEMA: Schema<'static> = Schema::new_struct(
2076                aws_smithy_schema::shape_id!("test", "ConnectionDraining"),
2077                aws_smithy_schema::ShapeType::Structure,
2078                &[&CD_ENABLED_X],
2079            );
2080            &SCHEMA
2081        }
2082
2083        fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2084            let val = &self.enabled;
2085            ser.write_boolean(&CD_ENABLED_X, *val)
2086        }
2087    }
2088    struct LoadBalancerAttributesX {
2089        connection_draining: ConnectionDrainingX,
2090    }
2091    impl SerializableStruct for LoadBalancerAttributesX {
2092        fn schema(&self) -> &Schema<'_> {
2093            &LBA_SCHEMA_X
2094        }
2095
2096        fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
2097            ser.write_struct(&LBA_CD_X, &self.connection_draining)
2098        }
2099    }
2100
2101    #[test]
2102    fn required_value_type_false_bool_is_serialized() {
2103        let out = serialize(|ser| {
2104            ser.write_struct(
2105                &LBA_SCHEMA_X,
2106                &LoadBalancerAttributesX {
2107                    connection_draining: ConnectionDrainingX { enabled: false },
2108                },
2109            )
2110        });
2111        assert_eq!(
2112            out,
2113            "<LoadBalancerAttributes><connectionDraining><enabled>false</enabled></connectionDraining></LoadBalancerAttributes>",
2114            "the required-default value-type member must be present with false, not dropped"
2115        );
2116    }
2117
2118    /// Element names cached on a list or map schema must not capture serializer state. A
2119    /// nested aggregate arrives with the `prelude::DOCUMENT` placeholder, so its element name
2120    /// comes from the enclosing list; the same placeholder schema must take whatever name
2121    /// each enclosing list hands down, every time.
2122    #[test]
2123    fn cached_names_do_not_capture_names_from_an_enclosing_list() {
2124        static ROW: Schema<'static> = Schema::new_member(
2125            shape_id!("test", "Rows$member"),
2126            ShapeType::List,
2127            "member",
2128            0,
2129        )
2130        .with_xml_name("Row");
2131        static ROWS: Schema<'static> =
2132            Schema::new_member(shape_id!("test", "S$rows"), ShapeType::List, "rows", 0)
2133                .with_list_member(&ROW);
2134        static COL: Schema<'static> = Schema::new_member(
2135            shape_id!("test", "Cols$member"),
2136            ShapeType::List,
2137            "member",
2138            0,
2139        )
2140        .with_xml_name("Col");
2141        static COLS: Schema<'static> =
2142            Schema::new_member(shape_id!("test", "S$cols"), ShapeType::List, "cols", 1)
2143                .with_list_member(&COL);
2144
2145        let nested = |outer: &'static Schema<'static>| {
2146            serialize(move |ser| {
2147                ser.write_list(outer, &|ser| {
2148                    ser.write_list(&prelude::DOCUMENT, &|ser| {
2149                        ser.write_string(&prelude::STRING, "a")
2150                    })
2151                })
2152            })
2153        };
2154        for _ in 0..2 {
2155            assert_eq!(nested(&ROWS), "<rows><Row><member>a</member></Row></rows>");
2156            assert_eq!(nested(&COLS), "<cols><Col><member>a</member></Col></cols>");
2157        }
2158    }
2159
2160    /// A flattened list's items take the list's own element name. When the list is itself an
2161    /// item of an enclosing list, that name comes from the enclosing list instead, and must
2162    /// not be cached on the flattened list's schema.
2163    #[test]
2164    fn flattened_list_item_name_follows_the_enclosing_list() {
2165        static FLAT: Schema<'static> =
2166            Schema::new_member(shape_id!("test", "S$flat"), ShapeType::List, "flat", 0)
2167                .with_xml_flattened();
2168        static OUTER_ITEM: Schema<'static> = Schema::new_member(
2169            shape_id!("test", "Outer$member"),
2170            ShapeType::List,
2171            "member",
2172            0,
2173        )
2174        .with_xml_name("Inherited");
2175        static OUTER: Schema<'static> =
2176            Schema::new_member(shape_id!("test", "S$outer"), ShapeType::List, "outer", 0)
2177                .with_list_member(&OUTER_ITEM);
2178
2179        let flat = || {
2180            serialize(|ser| ser.write_list(&FLAT, &|ser| ser.write_string(&prelude::STRING, "x")))
2181        };
2182        let inherited = || {
2183            serialize(|ser| {
2184                ser.write_list(&OUTER, &|ser| {
2185                    ser.write_list(&FLAT, &|ser| ser.write_string(&prelude::STRING, "x"))
2186                })
2187            })
2188        };
2189        for _ in 0..2 {
2190            assert_eq!(flat(), "<flat>x</flat>");
2191            assert_eq!(inherited(), "<outer><Inherited>x</Inherited></outer>");
2192        }
2193    }
2194
2195    /// Namespaces cached for list items and map keys and values are written on every
2196    /// request, not only the one that computed the cache entry.
2197    #[test]
2198    fn cached_namespaces_are_written_on_every_request() {
2199        static ITEM: Schema<'static> = Schema::new_member(
2200            shape_id!("test", "L$member"),
2201            ShapeType::String,
2202            "member",
2203            0,
2204        )
2205        .with_xml_namespace("urn:item", Some("i"));
2206        static LIST: Schema<'static> =
2207            Schema::new_member(shape_id!("test", "S$items"), ShapeType::List, "items", 0)
2208                .with_list_member(&ITEM);
2209        static KEY: Schema<'static> =
2210            Schema::new_member(shape_id!("test", "M$key"), ShapeType::String, "key", 0)
2211                .with_xml_name("K")
2212                .with_xml_namespace("urn:key", None);
2213        static VALUE: Schema<'static> =
2214            Schema::new_member(shape_id!("test", "M$value"), ShapeType::String, "value", 1)
2215                .with_xml_namespace("urn:value", Some("v"));
2216        static MAP: Schema<'static> =
2217            Schema::new_member(shape_id!("test", "S$m"), ShapeType::Map, "m", 1)
2218                .with_map_members(&KEY, &VALUE);
2219
2220        for _ in 0..2 {
2221            let list = serialize(|ser| {
2222                ser.write_list(&LIST, &|ser| {
2223                    ser.write_string(&prelude::STRING, "a")?;
2224                    ser.write_integer(&prelude::INTEGER, 1)
2225                })
2226            });
2227            assert_eq!(
2228                list,
2229                "<items><member xmlns:i=\"urn:item\">a</member><member xmlns:i=\"urn:item\">1</member></items>"
2230            );
2231            let map = serialize(|ser| {
2232                ser.write_map(&MAP, &|ser| {
2233                    ser.write_string(&prelude::STRING, "k")?;
2234                    ser.write_string(&prelude::STRING, "v")
2235                })
2236            });
2237            assert_eq!(
2238                map,
2239                "<m><entry><K xmlns=\"urn:key\">k</K><value xmlns:v=\"urn:value\">v</value></entry></m>"
2240            );
2241        }
2242    }
2243}