• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 19921098942

04 Dec 2025 07:27AM UTC coverage: 57.083% (-2.1%) from 59.134%
19921098942

Pull #1015

github

web-flow
Merge ee47a41e8 into 0012fd8ca
Pull Request #1015: feat: add facet-xml crate for XML serialization/deserialization

504 of 2176 new or added lines in 3 files covered. (23.16%)

3 existing lines in 1 file now uncovered.

21465 of 37603 relevant lines covered (57.08%)

514.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.84
/facet-xml/src/serialize.rs
1
//! XML serialization implementation.
2

3
use std::io::Write;
4

5
use base64::Engine;
6
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
7
use facet_core::{Def, Facet, StructKind};
8
use facet_reflect::{HasFields, Peek, is_spanned_shape};
9

10
use crate::deserialize::XmlFieldExt;
11
use crate::error::{XmlError, XmlErrorKind};
12

13
pub(crate) type Result<T> = std::result::Result<T, XmlError>;
14

15
/// Serialize a value of type `T` to an XML string.
16
///
17
/// The type `T` must be a struct where fields are marked with XML attributes like
18
/// `#[facet(xml::element)]`, `#[facet(xml::attribute)]`, or `#[facet(xml::text)]`.
19
///
20
/// # Example
21
/// ```
22
/// # use facet::Facet;
23
/// # use facet_xml as xml;
24
/// # use facet_xml::to_string;
25
/// #[derive(Facet)]
26
/// struct Person {
27
///     #[facet(xml::attribute)]
28
///     id: u32,
29
///     #[facet(xml::element)]
30
///     name: String,
31
/// }
32
///
33
/// # fn main() -> Result<(), facet_xml::XmlError> {
34
/// let person = Person { id: 42, name: "Alice".into() };
35
/// let xml = to_string(&person)?;
36
/// assert_eq!(xml, r#"<Person id="42"><name>Alice</name></Person>"#);
37
/// # Ok(())
38
/// # }
39
/// ```
NEW
40
pub fn to_string<T: Facet<'static>>(value: &T) -> Result<String> {
×
NEW
41
    let mut output = Vec::new();
×
NEW
42
    to_writer(&mut output, value)?;
×
NEW
43
    Ok(String::from_utf8(output).expect("XML output should be valid UTF-8"))
×
NEW
44
}
×
45

46
/// Serialize a value of type `T` to a writer as XML.
47
///
48
/// This is the streaming version of [`to_string`] - it writes directly to any
49
/// type implementing [`std::io::Write`].
50
///
51
/// # Example
52
///
53
/// Writing to a `Vec<u8>` buffer:
54
/// ```
55
/// # use facet::Facet;
56
/// # use facet_xml as xml;
57
/// # use facet_xml::to_writer;
58
/// #[derive(Facet)]
59
/// struct Person {
60
///     #[facet(xml::attribute)]
61
///     id: u32,
62
///     #[facet(xml::element)]
63
///     name: String,
64
/// }
65
///
66
/// # fn main() -> Result<(), facet_xml::XmlError> {
67
/// let person = Person { id: 42, name: "Alice".into() };
68
/// let mut buffer = Vec::new();
69
/// to_writer(&mut buffer, &person)?;
70
/// let xml = String::from_utf8(buffer).unwrap();
71
/// assert_eq!(xml, r#"<Person id="42"><name>Alice</name></Person>"#);
72
/// # Ok(())
73
/// # }
74
/// ```
NEW
75
pub fn to_writer<W: Write, T: Facet<'static>>(writer: &mut W, value: &T) -> Result<()> {
×
NEW
76
    let peek = Peek::new(value);
×
NEW
77
    let mut serializer = XmlSerializer::new(writer);
×
78

79
    // Get the type name for the root element
NEW
80
    let type_name = peek.shape().type_identifier;
×
NEW
81
    serializer.serialize_element(type_name, peek)
×
NEW
82
}
×
83

84
struct XmlSerializer<W> {
85
    writer: W,
86
}
87

88
impl<W: Write> XmlSerializer<W> {
NEW
89
    fn new(writer: W) -> Self {
×
NEW
90
        Self { writer }
×
NEW
91
    }
×
92

NEW
93
    fn serialize_element<'mem, 'facet>(
×
NEW
94
        &mut self,
×
NEW
95
        element_name: &str,
×
NEW
96
        peek: Peek<'mem, 'facet>,
×
NEW
97
    ) -> Result<()> {
×
98
        // Handle Option<T> - skip if None
NEW
99
        if let Ok(opt_peek) = peek.into_option() {
×
NEW
100
            if opt_peek.is_none() {
×
NEW
101
                return Ok(());
×
NEW
102
            }
×
NEW
103
            if let Some(inner) = opt_peek.value() {
×
NEW
104
                return self.serialize_element(element_name, inner);
×
NEW
105
            }
×
NEW
106
            return Ok(());
×
NEW
107
        }
×
108

109
        // Handle Spanned<T> - unwrap to the inner value
NEW
110
        if is_spanned_shape(peek.shape()) {
×
NEW
111
            if let Ok(struct_peek) = peek.into_struct() {
×
NEW
112
                if let Ok(value_field) = struct_peek.field_by_name("value") {
×
NEW
113
                    return self.serialize_element(element_name, value_field);
×
NEW
114
                }
×
NEW
115
            }
×
NEW
116
        }
×
117

118
        // Check if this is a struct
NEW
119
        if let Ok(struct_peek) = peek.into_struct() {
×
NEW
120
            return self.serialize_struct_as_element(element_name, struct_peek);
×
NEW
121
        }
×
122

123
        // Check if this is an enum
NEW
124
        if let Ok(enum_peek) = peek.into_enum() {
×
NEW
125
            return self.serialize_enum_as_element(element_name, enum_peek);
×
NEW
126
        }
×
127

128
        // Check if this is a byte slice/array - serialize as base64
NEW
129
        if self.try_serialize_bytes_as_element(element_name, peek)? {
×
NEW
130
            return Ok(());
×
NEW
131
        }
×
132

133
        // Check if this is a list-like type (Vec, array, set)
NEW
134
        if let Ok(list_peek) = peek.into_list_like() {
×
NEW
135
            return self.serialize_list_as_element(element_name, list_peek);
×
NEW
136
        }
×
137

138
        // Check if this is a map
NEW
139
        if let Ok(map_peek) = peek.into_map() {
×
NEW
140
            return self.serialize_map_as_element(element_name, map_peek);
×
NEW
141
        }
×
142

143
        // For scalars/primitives, serialize as element with text content
NEW
144
        write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
145
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
146
        self.serialize_value(peek)?;
×
NEW
147
        write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
148
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
149

NEW
150
        Ok(())
×
NEW
151
    }
×
152

NEW
153
    fn serialize_enum_as_element<'mem, 'facet>(
×
NEW
154
        &mut self,
×
NEW
155
        element_name: &str,
×
NEW
156
        enum_peek: facet_reflect::PeekEnum<'mem, 'facet>,
×
NEW
157
    ) -> Result<()> {
×
NEW
158
        let shape = enum_peek.shape();
×
NEW
159
        let variant_name = enum_peek
×
NEW
160
            .variant_name_active()
×
NEW
161
            .map_err(|_| XmlErrorKind::SerializeUnknownElementType)?;
×
162

NEW
163
        let fields: Vec<_> = enum_peek.fields_for_serialize().collect();
×
164

165
        // Determine enum tagging strategy
NEW
166
        let is_untagged = shape.is_untagged();
×
NEW
167
        let tag_attr = shape.get_tag_attr();
×
NEW
168
        let content_attr = shape.get_content_attr();
×
169

NEW
170
        if is_untagged {
×
171
            // Untagged: serialize content directly with element name
NEW
172
            self.serialize_enum_content(element_name, variant_name, &fields)?;
×
NEW
173
        } else if let Some(tag) = tag_attr {
×
NEW
174
            if let Some(content) = content_attr {
×
175
                // Adjacently tagged: <Element tag="Variant"><content>...</content></Element>
NEW
176
                write!(
×
NEW
177
                    self.writer,
×
178
                    "<{} {}=\"{}\">",
NEW
179
                    escape_element_name(element_name),
×
NEW
180
                    escape_element_name(tag),
×
NEW
181
                    escape_attribute_value(variant_name)
×
182
                )
NEW
183
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
184

NEW
185
                if !fields.is_empty() {
×
186
                    // Wrap content in the content element
NEW
187
                    write!(self.writer, "<{}>", escape_element_name(content))
×
NEW
188
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
189
                    self.serialize_variant_fields(&fields)?;
×
NEW
190
                    write!(self.writer, "</{}>", escape_element_name(content))
×
NEW
191
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
192
                }
×
193

NEW
194
                write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
195
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
196
            } else {
197
                // Internally tagged: <Element tag="Variant">...fields...</Element>
NEW
198
                write!(
×
NEW
199
                    self.writer,
×
200
                    "<{} {}=\"{}\">",
NEW
201
                    escape_element_name(element_name),
×
NEW
202
                    escape_element_name(tag),
×
NEW
203
                    escape_attribute_value(variant_name)
×
204
                )
NEW
205
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
206

207
                // Serialize fields directly (not wrapped in variant element)
NEW
208
                self.serialize_variant_fields(&fields)?;
×
209

NEW
210
                write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
211
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
212
            }
213
        } else {
214
            // Externally tagged (default): <Element><Variant>...</Variant></Element>
NEW
215
            write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
216
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
217

NEW
218
            if fields.is_empty() {
×
219
                // Unit variant - just the variant name as an empty element
NEW
220
                write!(self.writer, "<{}/>", escape_element_name(variant_name))
×
NEW
221
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
222
            } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
×
223
                // Newtype variant - serialize the inner value with variant name
NEW
224
                self.serialize_element(variant_name, fields[0].1)?;
×
225
            } else {
226
                // Struct-like variant
NEW
227
                write!(self.writer, "<{}>", escape_element_name(variant_name))
×
NEW
228
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
229

NEW
230
                for (field_item, field_peek) in fields {
×
NEW
231
                    self.serialize_element(field_item.name, field_peek)?;
×
232
                }
233

NEW
234
                write!(self.writer, "</{}>", escape_element_name(variant_name))
×
NEW
235
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
236
            }
237

NEW
238
            write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
239
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
240
        }
241

NEW
242
        Ok(())
×
NEW
243
    }
×
244

245
    /// Serialize enum content for untagged enums
NEW
246
    fn serialize_enum_content<'mem, 'facet>(
×
NEW
247
        &mut self,
×
NEW
248
        element_name: &str,
×
NEW
249
        variant_name: &str,
×
NEW
250
        fields: &[(facet_reflect::FieldItem, Peek<'mem, 'facet>)],
×
NEW
251
    ) -> Result<()> {
×
NEW
252
        if fields.is_empty() {
×
253
            // Unit variant - empty element
NEW
254
            write!(self.writer, "<{}/>", escape_element_name(element_name))
×
NEW
255
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
256
        } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
×
257
            // Newtype variant - serialize inner directly
NEW
258
            self.serialize_element(element_name, fields[0].1)?;
×
259
        } else {
260
            // Struct-like variant - serialize as struct
NEW
261
            write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
262
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
263

NEW
264
            for (field_item, field_peek) in fields {
×
265
                // Use variant_name as hint for structs in untagged context
NEW
266
                let _ = variant_name; // Available if needed for disambiguation
×
NEW
267
                self.serialize_element(field_item.name, *field_peek)?;
×
268
            }
269

NEW
270
            write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
271
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
272
        }
NEW
273
        Ok(())
×
NEW
274
    }
×
275

276
    /// Serialize variant fields without wrapper
NEW
277
    fn serialize_variant_fields<'mem, 'facet>(
×
NEW
278
        &mut self,
×
NEW
279
        fields: &[(facet_reflect::FieldItem, Peek<'mem, 'facet>)],
×
NEW
280
    ) -> Result<()> {
×
NEW
281
        if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
×
282
            // Single tuple field - serialize value directly
NEW
283
            self.serialize_value(fields[0].1)?;
×
284
        } else {
NEW
285
            for (field_item, field_peek) in fields {
×
NEW
286
                self.serialize_element(field_item.name, *field_peek)?;
×
287
            }
288
        }
NEW
289
        Ok(())
×
NEW
290
    }
×
291

NEW
292
    fn serialize_list_as_element<'mem, 'facet>(
×
NEW
293
        &mut self,
×
NEW
294
        element_name: &str,
×
NEW
295
        list_peek: facet_reflect::PeekListLike<'mem, 'facet>,
×
NEW
296
    ) -> Result<()> {
×
NEW
297
        write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
298
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
299

NEW
300
        for item in list_peek.iter() {
×
NEW
301
            self.serialize_list_item_element(item)?;
×
302
        }
303

NEW
304
        write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
305
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
306

NEW
307
        Ok(())
×
NEW
308
    }
×
309

NEW
310
    fn serialize_map_as_element<'mem, 'facet>(
×
NEW
311
        &mut self,
×
NEW
312
        element_name: &str,
×
NEW
313
        map_peek: facet_reflect::PeekMap<'mem, 'facet>,
×
NEW
314
    ) -> Result<()> {
×
NEW
315
        write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
316
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
317

NEW
318
        for (key, value) in map_peek.iter() {
×
319
            // Use the key as the element name
NEW
320
            if let Some(key_str) = key.as_str() {
×
NEW
321
                self.serialize_element(key_str, value)?;
×
NEW
322
            } else if let Some(key_val) = value_to_string(key) {
×
NEW
323
                self.serialize_element(&key_val, value)?;
×
324
            } else {
325
                // Fallback: use "entry" as element name with key as text
NEW
326
                write!(self.writer, "<entry>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
327
                self.serialize_value(key)?;
×
NEW
328
                self.serialize_value(value)?;
×
NEW
329
                write!(self.writer, "</entry>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
330
            }
331
        }
332

NEW
333
        write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
334
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
335

NEW
336
        Ok(())
×
NEW
337
    }
×
338

339
    /// Try to serialize bytes (`Vec<u8>`, `&[u8]`, `[u8; N]`) as base64-encoded element.
340
    /// Returns Ok(true) if bytes were handled, Ok(false) if not bytes.
NEW
341
    fn try_serialize_bytes_as_element<'mem, 'facet>(
×
NEW
342
        &mut self,
×
NEW
343
        element_name: &str,
×
NEW
344
        peek: Peek<'mem, 'facet>,
×
NEW
345
    ) -> Result<bool> {
×
NEW
346
        let shape = peek.shape();
×
347

348
        // Check for Vec<u8>
NEW
349
        if let Def::List(ld) = &shape.def {
×
NEW
350
            if ld.t().is_type::<u8>() {
×
NEW
351
                if let Some(bytes) = peek.as_bytes() {
×
NEW
352
                    let encoded = BASE64_STANDARD.encode(bytes);
×
NEW
353
                    write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
354
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
355
                    write!(self.writer, "{}", escape_text(&encoded))
×
NEW
356
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
357
                    write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
358
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
359
                    return Ok(true);
×
NEW
360
                }
×
NEW
361
            }
×
NEW
362
        }
×
363

364
        // Check for [u8; N]
NEW
365
        if let Def::Array(ad) = &shape.def {
×
NEW
366
            if ad.t().is_type::<u8>() {
×
367
                // Collect bytes from the array
NEW
368
                if let Ok(list_peek) = peek.into_list_like() {
×
NEW
369
                    let bytes: Vec<u8> = list_peek
×
NEW
370
                        .iter()
×
NEW
371
                        .filter_map(|p| p.get::<u8>().ok().copied())
×
NEW
372
                        .collect();
×
NEW
373
                    let encoded = BASE64_STANDARD.encode(&bytes);
×
NEW
374
                    write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
375
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
376
                    write!(self.writer, "{}", escape_text(&encoded))
×
NEW
377
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
378
                    write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
379
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
380
                    return Ok(true);
×
NEW
381
                }
×
NEW
382
            }
×
NEW
383
        }
×
384

385
        // Check for &[u8]
NEW
386
        if let Def::Slice(sd) = &shape.def {
×
NEW
387
            if sd.t().is_type::<u8>() {
×
NEW
388
                if let Some(bytes) = peek.as_bytes() {
×
NEW
389
                    let encoded = BASE64_STANDARD.encode(bytes);
×
NEW
390
                    write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
391
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
392
                    write!(self.writer, "{}", escape_text(&encoded))
×
NEW
393
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
394
                    write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
395
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
396
                    return Ok(true);
×
NEW
397
                }
×
NEW
398
            }
×
NEW
399
        }
×
400

NEW
401
        Ok(false)
×
NEW
402
    }
×
403

NEW
404
    fn serialize_struct_as_element<'mem, 'facet>(
×
NEW
405
        &mut self,
×
NEW
406
        element_name: &str,
×
NEW
407
        struct_peek: facet_reflect::PeekStruct<'mem, 'facet>,
×
NEW
408
    ) -> Result<()> {
×
NEW
409
        match struct_peek.ty().kind {
×
410
            StructKind::Unit => {
411
                // Unit struct - just output empty element
NEW
412
                write!(self.writer, "<{}/>", escape_element_name(element_name))
×
NEW
413
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
414
                return Ok(());
×
415
            }
416
            StructKind::Tuple | StructKind::TupleStruct => {
417
                // Tuple struct - serialize fields in order as child elements
NEW
418
                let fields: Vec<_> = struct_peek.fields_for_serialize().collect();
×
NEW
419
                if fields.is_empty() {
×
NEW
420
                    write!(self.writer, "<{}/>", escape_element_name(element_name))
×
NEW
421
                        .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
422
                    return Ok(());
×
NEW
423
                }
×
424

NEW
425
                write!(self.writer, "<{}>", escape_element_name(element_name))
×
NEW
426
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
427

NEW
428
                for (i, (_, field_peek)) in fields.into_iter().enumerate() {
×
429
                    // Use indexed element names for tuple fields
NEW
430
                    let field_name = format!("_{i}");
×
NEW
431
                    self.serialize_element(&field_name, field_peek)?;
×
432
                }
433

NEW
434
                write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
435
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
436
                return Ok(());
×
437
            }
NEW
438
            StructKind::Struct => {
×
NEW
439
                // Named struct - fall through to normal handling
×
NEW
440
            }
×
441
        }
442

443
        // Collect attributes, elements, and text content
444
        // We store field name (&'static str) instead of &Field to avoid lifetime issues
NEW
445
        let mut attributes: Vec<(&str, String)> = Vec::new();
×
NEW
446
        let mut elements: Vec<(facet_reflect::FieldItem, Peek<'mem, 'facet>)> = Vec::new();
×
NEW
447
        let mut elements_list: Vec<Peek<'mem, 'facet>> = Vec::new();
×
NEW
448
        let mut text_content: Option<Peek<'mem, 'facet>> = None;
×
449

NEW
450
        for (field_item, field_peek) in struct_peek.fields_for_serialize() {
×
NEW
451
            let field = &field_item.field;
×
452

453
            // Handle custom serialization for attributes - get value immediately
NEW
454
            if field.is_xml_attribute() {
×
NEW
455
                let value = if field.proxy_convert_out_fn().is_some() {
×
456
                    // Get the intermediate representation for serialization
NEW
457
                    if let Ok(owned) = field_peek.custom_serialization(*field) {
×
NEW
458
                        value_to_string(owned.as_peek())
×
459
                    } else {
NEW
460
                        value_to_string(field_peek)
×
461
                    }
462
                } else {
NEW
463
                    value_to_string(field_peek)
×
464
                };
NEW
465
                if let Some(value) = value {
×
NEW
466
                    attributes.push((field_item.name, value));
×
NEW
467
                }
×
NEW
468
            } else if field.is_xml_element() {
×
NEW
469
                elements.push((field_item, field_peek));
×
NEW
470
            } else if field.is_xml_elements() {
×
NEW
471
                elements_list.push(field_peek);
×
NEW
472
            } else if field.is_xml_text() {
×
NEW
473
                text_content = Some(field_peek);
×
NEW
474
            }
×
475
        }
476

477
        // Determine if we need content
NEW
478
        let has_content =
×
NEW
479
            !elements.is_empty() || !elements_list.is_empty() || text_content.is_some();
×
480

481
        // Write opening tag with attributes
NEW
482
        write!(self.writer, "<{}", escape_element_name(element_name))
×
NEW
483
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
484

NEW
485
        for (attr_name, attr_value) in &attributes {
×
NEW
486
            write!(
×
NEW
487
                self.writer,
×
488
                " {}=\"{}\"",
NEW
489
                escape_element_name(attr_name),
×
NEW
490
                escape_attribute_value(attr_value)
×
491
            )
NEW
492
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
493
        }
494

NEW
495
        if !has_content {
×
496
            // Self-closing tag
NEW
497
            write!(self.writer, "/>").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
498
            return Ok(());
×
NEW
499
        }
×
500

NEW
501
        write!(self.writer, ">").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
502

503
        // Write text content if present
NEW
504
        if let Some(text_peek) = text_content {
×
NEW
505
            self.serialize_text_value(text_peek)?;
×
NEW
506
        }
×
507

508
        // Write child elements
NEW
509
        for (field_item, field_peek) in elements {
×
510
            // Handle custom serialization for elements
NEW
511
            if field_item.field.proxy_convert_out_fn().is_some() {
×
NEW
512
                if let Ok(owned) = field_peek.custom_serialization(field_item.field) {
×
NEW
513
                    self.serialize_named_element(field_item.name, owned.as_peek())?;
×
514
                } else {
NEW
515
                    self.serialize_named_element(field_item.name, field_peek)?;
×
516
                }
517
            } else {
NEW
518
                self.serialize_named_element(field_item.name, field_peek)?;
×
519
            }
520
        }
521

522
        // Write elements lists
NEW
523
        for field_peek in elements_list {
×
NEW
524
            self.serialize_elements_list(field_peek)?;
×
525
        }
526

527
        // Write closing tag
NEW
528
        write!(self.writer, "</{}>", escape_element_name(element_name))
×
NEW
529
            .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
530

NEW
531
        Ok(())
×
NEW
532
    }
×
533

NEW
534
    fn serialize_named_element<'mem, 'facet>(
×
NEW
535
        &mut self,
×
NEW
536
        name: &str,
×
NEW
537
        peek: Peek<'mem, 'facet>,
×
NEW
538
    ) -> Result<()> {
×
539
        // Handle Option<T> - skip if None
NEW
540
        if let Ok(opt_peek) = peek.into_option() {
×
NEW
541
            if opt_peek.is_none() {
×
NEW
542
                return Ok(());
×
NEW
543
            }
×
NEW
544
            if let Some(inner) = opt_peek.value() {
×
NEW
545
                return self.serialize_named_element(name, inner);
×
NEW
546
            }
×
NEW
547
            return Ok(());
×
NEW
548
        }
×
549

NEW
550
        self.serialize_element(name, peek)
×
NEW
551
    }
×
552

NEW
553
    fn serialize_elements_list<'mem, 'facet>(&mut self, peek: Peek<'mem, 'facet>) -> Result<()> {
×
NEW
554
        let list_peek = peek
×
NEW
555
            .into_list()
×
NEW
556
            .map_err(|_| XmlErrorKind::SerializeNotList)?;
×
557

NEW
558
        for item_peek in list_peek.iter() {
×
NEW
559
            self.serialize_list_item_element(item_peek)?;
×
560
        }
561

NEW
562
        Ok(())
×
NEW
563
    }
×
564

NEW
565
    fn serialize_list_item_element<'mem, 'facet>(
×
NEW
566
        &mut self,
×
NEW
567
        peek: Peek<'mem, 'facet>,
×
NEW
568
    ) -> Result<()> {
×
569
        // For enums, use variant name as element name
NEW
570
        if let Ok(enum_peek) = peek.into_enum() {
×
NEW
571
            let variant_name = enum_peek
×
NEW
572
                .variant_name_active()
×
NEW
573
                .map_err(|_| XmlErrorKind::SerializeUnknownElementType)?;
×
574

575
            // Get the variant's fields (respecting skip_serializing)
NEW
576
            let fields: Vec<_> = enum_peek.fields_for_serialize().collect();
×
577

NEW
578
            if fields.is_empty() {
×
579
                // Unit variant - empty element
NEW
580
                write!(self.writer, "<{}/>", escape_element_name(variant_name))
×
NEW
581
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
582
            } else if fields.len() == 1 && fields[0].0.name.parse::<usize>().is_ok() {
×
583
                // Tuple variant with single field - serialize the inner value
NEW
584
                self.serialize_element(variant_name, fields[0].1)?;
×
585
            } else {
586
                // Struct-like variant
NEW
587
                write!(self.writer, "<{}>", escape_element_name(variant_name))
×
NEW
588
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
589

NEW
590
                for (field_item, field_peek) in fields {
×
NEW
591
                    self.serialize_element(field_item.name, field_peek)?;
×
592
                }
593

NEW
594
                write!(self.writer, "</{}>", escape_element_name(variant_name))
×
NEW
595
                    .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
596
            }
NEW
597
            return Ok(());
×
NEW
598
        }
×
599

600
        // For structs, use type name as element name
NEW
601
        let type_name = peek.shape().type_identifier;
×
NEW
602
        self.serialize_element(type_name, peek)
×
NEW
603
    }
×
604

NEW
605
    fn serialize_text_value<'mem, 'facet>(&mut self, peek: Peek<'mem, 'facet>) -> Result<()> {
×
606
        // Handle Option<T>
NEW
607
        if let Ok(opt_peek) = peek.into_option() {
×
NEW
608
            if opt_peek.is_none() {
×
NEW
609
                return Ok(());
×
NEW
610
            }
×
NEW
611
            if let Some(inner) = opt_peek.value() {
×
NEW
612
                return self.serialize_text_value(inner);
×
NEW
613
            }
×
NEW
614
            return Ok(());
×
NEW
615
        }
×
616

617
        // Handle Spanned<T>
NEW
618
        if is_spanned_shape(peek.shape()) {
×
NEW
619
            if let Ok(struct_peek) = peek.into_struct() {
×
NEW
620
                if let Ok(value_peek) = struct_peek.field_by_name("value") {
×
NEW
621
                    return self.serialize_text_value(value_peek);
×
NEW
622
                }
×
NEW
623
            }
×
NEW
624
        }
×
625

NEW
626
        self.serialize_value(peek)
×
NEW
627
    }
×
628

NEW
629
    fn serialize_value<'mem, 'facet>(&mut self, peek: Peek<'mem, 'facet>) -> Result<()> {
×
630
        // Handle Option<T>
NEW
631
        if let Ok(opt_peek) = peek.into_option() {
×
NEW
632
            if opt_peek.is_none() {
×
NEW
633
                return Ok(());
×
NEW
634
            }
×
NEW
635
            if let Some(inner) = opt_peek.value() {
×
NEW
636
                return self.serialize_value(inner);
×
NEW
637
            }
×
NEW
638
            return Ok(());
×
NEW
639
        }
×
640

641
        // Handle Spanned<T>
NEW
642
        if is_spanned_shape(peek.shape()) {
×
NEW
643
            if let Ok(struct_peek) = peek.into_struct() {
×
NEW
644
                if let Ok(value_peek) = struct_peek.field_by_name("value") {
×
NEW
645
                    return self.serialize_value(value_peek);
×
NEW
646
                }
×
NEW
647
            }
×
NEW
648
        }
×
649

650
        // Unwrap transparent wrappers
NEW
651
        let peek = peek.innermost_peek();
×
652

653
        // Try string first
NEW
654
        if let Some(s) = peek.as_str() {
×
NEW
655
            write!(self.writer, "{}", escape_text(s))
×
NEW
656
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
657
            return Ok(());
×
NEW
658
        }
×
659

660
        // Try various types
NEW
661
        if let Ok(v) = peek.get::<bool>() {
×
NEW
662
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
663
            return Ok(());
×
NEW
664
        }
×
665

NEW
666
        if let Ok(v) = peek.get::<i8>() {
×
NEW
667
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
668
            return Ok(());
×
NEW
669
        }
×
NEW
670
        if let Ok(v) = peek.get::<i16>() {
×
NEW
671
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
672
            return Ok(());
×
NEW
673
        }
×
NEW
674
        if let Ok(v) = peek.get::<i32>() {
×
NEW
675
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
676
            return Ok(());
×
NEW
677
        }
×
NEW
678
        if let Ok(v) = peek.get::<i64>() {
×
NEW
679
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
680
            return Ok(());
×
NEW
681
        }
×
682

NEW
683
        if let Ok(v) = peek.get::<u8>() {
×
NEW
684
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
685
            return Ok(());
×
NEW
686
        }
×
NEW
687
        if let Ok(v) = peek.get::<u16>() {
×
NEW
688
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
689
            return Ok(());
×
NEW
690
        }
×
NEW
691
        if let Ok(v) = peek.get::<u32>() {
×
NEW
692
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
693
            return Ok(());
×
NEW
694
        }
×
NEW
695
        if let Ok(v) = peek.get::<u64>() {
×
NEW
696
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
697
            return Ok(());
×
NEW
698
        }
×
699

NEW
700
        if let Ok(v) = peek.get::<f32>() {
×
NEW
701
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
702
            return Ok(());
×
NEW
703
        }
×
NEW
704
        if let Ok(v) = peek.get::<f64>() {
×
NEW
705
            write!(self.writer, "{v}").map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
706
            return Ok(());
×
NEW
707
        }
×
708

NEW
709
        if let Ok(v) = peek.get::<char>() {
×
NEW
710
            write!(self.writer, "{}", escape_text(&v.to_string()))
×
NEW
711
                .map_err(|e| XmlErrorKind::Io(e.to_string()))?;
×
NEW
712
            return Ok(());
×
NEW
713
        }
×
714

NEW
715
        Err(XmlErrorKind::SerializeUnknownValueType.into())
×
NEW
716
    }
×
717
}
718

719
/// Convert a Peek value to a string representation, handling Options, Spanned, and transparent wrappers.
720
fn value_to_string<'mem, 'facet>(peek: Peek<'mem, 'facet>) -> Option<String> {
3✔
721
    // Handle Option<T>
722
    if let Ok(opt_peek) = peek.into_option() {
3✔
NEW
723
        if opt_peek.is_none() {
×
NEW
724
            return None;
×
NEW
725
        }
×
NEW
726
        if let Some(inner) = opt_peek.value() {
×
NEW
727
            return value_to_string(inner);
×
NEW
728
        }
×
NEW
729
        return None;
×
730
    }
3✔
731

732
    // Handle Spanned<T>
733
    if is_spanned_shape(peek.shape()) {
3✔
NEW
734
        if let Ok(struct_peek) = peek.into_struct() {
×
NEW
735
            if let Ok(value_peek) = struct_peek.field_by_name("value") {
×
NEW
736
                return value_to_string(value_peek);
×
NEW
737
            }
×
NEW
738
        }
×
739
    }
3✔
740

741
    // Unwrap transparent wrappers
742
    let peek = peek.innermost_peek();
3✔
743

744
    // Try string first
745
    if let Some(s) = peek.as_str() {
3✔
NEW
746
        return Some(s.to_string());
×
747
    }
3✔
748

749
    // Try various types
750
    if let Ok(v) = peek.get::<bool>() {
3✔
NEW
751
        return Some(v.to_string());
×
752
    }
3✔
753

754
    if let Ok(v) = peek.get::<i8>() {
3✔
NEW
755
        return Some(v.to_string());
×
756
    }
3✔
757
    if let Ok(v) = peek.get::<i16>() {
3✔
NEW
758
        return Some(v.to_string());
×
759
    }
3✔
760
    if let Ok(v) = peek.get::<i32>() {
3✔
NEW
761
        return Some(v.to_string());
×
762
    }
3✔
763
    if let Ok(v) = peek.get::<i64>() {
3✔
NEW
764
        return Some(v.to_string());
×
765
    }
3✔
766

767
    if let Ok(v) = peek.get::<u8>() {
3✔
NEW
768
        return Some(v.to_string());
×
769
    }
3✔
770
    if let Ok(v) = peek.get::<u16>() {
3✔
NEW
771
        return Some(v.to_string());
×
772
    }
3✔
773
    if let Ok(v) = peek.get::<u32>() {
3✔
774
        return Some(v.to_string());
3✔
NEW
775
    }
×
NEW
776
    if let Ok(v) = peek.get::<u64>() {
×
NEW
777
        return Some(v.to_string());
×
NEW
778
    }
×
779

NEW
780
    if let Ok(v) = peek.get::<f32>() {
×
NEW
781
        return Some(v.to_string());
×
NEW
782
    }
×
NEW
783
    if let Ok(v) = peek.get::<f64>() {
×
NEW
784
        return Some(v.to_string());
×
NEW
785
    }
×
786

NEW
787
    if let Ok(v) = peek.get::<char>() {
×
NEW
788
        return Some(v.to_string());
×
NEW
789
    }
×
790

NEW
791
    None
×
792
}
3✔
793

794
/// Escape special characters in XML text content.
795
fn escape_text(s: &str) -> String {
3✔
796
    let mut result = String::with_capacity(s.len());
3✔
797
    for c in s.chars() {
15✔
798
        match c {
15✔
NEW
799
            '<' => result.push_str("&lt;"),
×
NEW
800
            '>' => result.push_str("&gt;"),
×
NEW
801
            '&' => result.push_str("&amp;"),
×
802
            _ => result.push(c),
15✔
803
        }
804
    }
805
    result
3✔
806
}
3✔
807

808
/// Escape special characters in XML attribute values.
809
fn escape_attribute_value(s: &str) -> String {
3✔
810
    let mut result = String::with_capacity(s.len());
3✔
811
    for c in s.chars() {
6✔
812
        match c {
6✔
NEW
813
            '<' => result.push_str("&lt;"),
×
NEW
814
            '>' => result.push_str("&gt;"),
×
NEW
815
            '&' => result.push_str("&amp;"),
×
NEW
816
            '"' => result.push_str("&quot;"),
×
NEW
817
            '\'' => result.push_str("&apos;"),
×
818
            _ => result.push(c),
6✔
819
        }
820
    }
821
    result
3✔
822
}
3✔
823

824
/// Escape element name (for now, assume valid XML names).
825
fn escape_element_name(name: &str) -> &str {
15✔
826
    name
15✔
827
}
15✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc