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