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