Skip to main content

facet_dom/serializer/
mod.rs

1//! Tree-based serializer for DOM documents.
2//!
3//! This module provides a serializer trait and shared logic for serializing
4//! facet types to tree-based formats like XML and HTML.
5
6mod write_scalar;
7
8pub use write_scalar::{ScalarBuffer, WriteScalar};
9
10extern crate alloc;
11
12use std::io::Write;
13
14/// A function that formats a floating-point number to a writer.
15///
16/// This is used to customize how `f32` and `f64` values are serialized.
17/// The function receives the value (as `f64`, with `f32` values upcast) and
18/// a writer to write the formatted output to.
19pub type FloatFormatter = fn(f64, &mut dyn Write) -> std::io::Result<()>;
20
21use alloc::borrow::Cow;
22use alloc::string::String;
23use alloc::vec::Vec;
24use core::fmt::Debug;
25
26use facet_core::{Def, StructKind};
27use facet_reflect::{HasFields as _, Peek, ReflectError};
28
29use crate::naming::to_element_name;
30use crate::trace;
31
32/// Low-level serializer interface for DOM-based formats (XML, HTML).
33///
34/// This trait provides callbacks for tree structure events. The shared
35/// serializer logic walks facet types and calls these methods.
36pub trait DomSerializer {
37    /// Format-specific error type.
38    type Error: Debug;
39
40    /// Begin an element with the given tag name.
41    ///
42    /// Followed by zero or more `attribute` calls, then `children_start`.
43    fn element_start(&mut self, tag: &str, namespace: Option<&str>) -> Result<(), Self::Error>;
44
45    /// Emit an attribute on the current element.
46    ///
47    /// Only valid between `element_start` and `children_start`.
48    /// The value is passed as a `Peek` so the serializer can format it directly
49    /// without intermediate allocations.
50    fn attribute(
51        &mut self,
52        name: &str,
53        value: Peek<'_, '_>,
54        namespace: Option<&str>,
55    ) -> Result<(), Self::Error>;
56
57    /// Start the children section of the current element.
58    fn children_start(&mut self) -> Result<(), Self::Error>;
59
60    /// End the children section.
61    fn children_end(&mut self) -> Result<(), Self::Error>;
62
63    /// End the current element.
64    fn element_end(&mut self, tag: &str) -> Result<(), Self::Error>;
65
66    /// Emit text content.
67    fn text(&mut self, content: &str) -> Result<(), Self::Error>;
68
69    /// Emit a comment (usually for debugging or special content).
70    fn comment(&mut self, _content: &str) -> Result<(), Self::Error> {
71        Ok(())
72    }
73
74    /// Emit a DOCTYPE declaration (XML/HTML).
75    ///
76    /// This is called before the root element when a field marked with
77    /// `#[facet(xml::doctype)]` or similar is encountered.
78    fn doctype(&mut self, _content: &str) -> Result<(), Self::Error> {
79        Ok(())
80    }
81
82    // ─────────────────────────────────────────────────────────────────────────
83    // Metadata hooks
84    // ─────────────────────────────────────────────────────────────────────────
85
86    /// Provide struct/container metadata before serializing.
87    ///
88    /// This allows extracting container-level attributes like xml::ns_all.
89    fn struct_metadata(&mut self, _shape: &facet_core::Shape) -> Result<(), Self::Error> {
90        Ok(())
91    }
92
93    /// Provide field metadata before serializing a field.
94    ///
95    /// This allows extracting field-level attributes like xml::attribute,
96    /// xml::text, xml::ns, etc.
97    fn field_metadata(&mut self, _field: &facet_reflect::FieldItem) -> Result<(), Self::Error> {
98        Ok(())
99    }
100
101    /// Provide variant metadata before serializing an enum variant.
102    fn variant_metadata(
103        &mut self,
104        _variant: &'static facet_core::Variant,
105    ) -> Result<(), Self::Error> {
106        Ok(())
107    }
108
109    // ─────────────────────────────────────────────────────────────────────────
110    // Field type hints
111    // ─────────────────────────────────────────────────────────────────────────
112
113    /// Check if the current field should be serialized as an attribute.
114    fn is_attribute_field(&self) -> bool {
115        false
116    }
117
118    /// Check if the current field should be serialized as text content.
119    fn is_text_field(&self) -> bool {
120        false
121    }
122
123    /// Check if the current field is an "elements" list (no wrapper element).
124    fn is_elements_field(&self) -> bool {
125        false
126    }
127
128    /// Check if the current field is a "tag" field (stores the element's tag name).
129    fn is_tag_field(&self) -> bool {
130        false
131    }
132
133    /// Check if the current field is a "doctype" field (stores the DOCTYPE declaration).
134    fn is_doctype_field(&self) -> bool {
135        false
136    }
137
138    /// Clear field-related state after a field is serialized.
139    fn clear_field_state(&mut self) {}
140
141    // ─────────────────────────────────────────────────────────────────────────
142    // Value formatting
143    // ─────────────────────────────────────────────────────────────────────────
144
145    /// Format a floating-point value as a string.
146    ///
147    /// Override this to provide custom float formatting (e.g., fixed decimal places).
148    /// The default implementation uses `Display`. The value is passed as f64
149    /// (f32 values are upcast).
150    fn format_float(&self, value: f64) -> String {
151        value.to_string()
152    }
153
154    // ─────────────────────────────────────────────────────────────────────────
155    // Option handling
156    // ─────────────────────────────────────────────────────────────────────────
157
158    /// Called when serializing `None`. DOM formats typically skip the field entirely.
159    fn serialize_none(&mut self) -> Result<(), Self::Error> {
160        Ok(())
161    }
162
163    /// Returns the format namespace for this serializer (e.g., "xml", "html").
164    ///
165    /// This is used to select format-specific proxy types when a field has
166    /// `#[facet(xml::proxy = XmlProxy)]` or similar format-namespaced proxies.
167    ///
168    /// Returns `None` by default, which falls back to format-agnostic proxies.
169    fn format_namespace(&self) -> Option<&'static str> {
170        None
171    }
172}
173
174/// Error produced by the DOM serializer.
175#[derive(Debug)]
176pub enum DomSerializeError<E: Debug> {
177    /// Format backend error.
178    Backend(E),
179    /// Reflection failed while traversing the value.
180    Reflect(ReflectError),
181    /// Value can't be represented by the DOM serializer.
182    Unsupported(Cow<'static, str>),
183}
184
185impl<E: Debug> core::fmt::Display for DomSerializeError<E> {
186    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
187        match self {
188            DomSerializeError::Backend(_) => f.write_str("DOM serializer error"),
189            DomSerializeError::Reflect(err) => write!(f, "{err}"),
190            DomSerializeError::Unsupported(msg) => f.write_str(msg.as_ref()),
191        }
192    }
193}
194
195impl<E: Debug + 'static> std::error::Error for DomSerializeError<E> {}
196
197/// Serialize a value using the DOM serializer.
198pub fn serialize<S>(
199    serializer: &mut S,
200    value: Peek<'_, '_>,
201) -> Result<(), DomSerializeError<S::Error>>
202where
203    S: DomSerializer,
204{
205    serialize_value(serializer, value, None)
206}
207
208/// Internal: serialize a value, optionally with an element name.
209fn serialize_value<S>(
210    serializer: &mut S,
211    value: Peek<'_, '_>,
212    element_name: Option<&str>,
213) -> Result<(), DomSerializeError<S::Error>>
214where
215    S: DomSerializer,
216{
217    // Dereference smart pointers
218    let value = deref_if_pointer(value);
219    let value = value.innermost_peek();
220
221    // Check for container-level proxy (format-specific or format-agnostic)
222    if value
223        .shape()
224        .effective_proxy(serializer.format_namespace())
225        .is_some()
226    {
227        return serialize_via_proxy(serializer, value, element_name);
228    }
229
230    // Handle scalars
231    if let Some(s) = value_to_string(value, serializer) {
232        if let Some(tag) = element_name {
233            serializer
234                .element_start(tag, None)
235                .map_err(DomSerializeError::Backend)?;
236            serializer
237                .children_start()
238                .map_err(DomSerializeError::Backend)?;
239            serializer.text(&s).map_err(DomSerializeError::Backend)?;
240            serializer
241                .children_end()
242                .map_err(DomSerializeError::Backend)?;
243            serializer
244                .element_end(tag)
245                .map_err(DomSerializeError::Backend)?;
246        } else {
247            serializer.text(&s).map_err(DomSerializeError::Backend)?;
248        }
249        return Ok(());
250    }
251
252    // Handle Option<T>
253    if let Ok(opt) = value.into_option() {
254        return match opt.value() {
255            Some(inner) => serialize_value(serializer, inner, element_name),
256            None => serializer
257                .serialize_none()
258                .map_err(DomSerializeError::Backend),
259        };
260    }
261
262    // Handle lists/arrays
263    // Flat list model: each item uses the field's element name (no wrapper element)
264    if let Def::List(_) | Def::Array(_) | Def::Slice(_) = value.shape().def {
265        let list = value.into_list_like().map_err(DomSerializeError::Reflect)?;
266
267        for item in list.iter() {
268            // Use the field's element name for each item (flat list)
269            serialize_value(serializer, item, element_name)?;
270        }
271
272        return Ok(());
273    }
274
275    // Handle maps
276    if let Ok(map) = value.into_map() {
277        if let Some(tag) = element_name {
278            serializer
279                .element_start(tag, None)
280                .map_err(DomSerializeError::Backend)?;
281            serializer
282                .children_start()
283                .map_err(DomSerializeError::Backend)?;
284        }
285
286        for (key, val) in map.iter() {
287            let key_str = if let Some(s) = key.as_str() {
288                Cow::Borrowed(s)
289            } else {
290                Cow::Owned(alloc::format!("{}", key))
291            };
292            serialize_value(serializer, val, Some(&key_str))?;
293        }
294
295        if let Some(tag) = element_name {
296            serializer
297                .children_end()
298                .map_err(DomSerializeError::Backend)?;
299            serializer
300                .element_end(tag)
301                .map_err(DomSerializeError::Backend)?;
302        }
303
304        return Ok(());
305    }
306
307    // Handle sets
308    // Flat set model: each item uses the field's element name (no wrapper element)
309    // Same as lists for consistency
310    if let Ok(set) = value.into_set() {
311        for item in set.iter() {
312            // Use the field's element name for each item (flat set)
313            serialize_value(serializer, item, element_name)?;
314        }
315
316        return Ok(());
317    }
318
319    // Handle structs
320    if let Ok(struct_) = value.into_struct() {
321        let kind = struct_.ty().kind;
322
323        // For standalone tuple types (A, B, C), serialize as a flat sequence
324        // Each tuple field becomes a sibling element with the same tag name
325        // Note: TupleStruct (struct Foo(A, B)) is handled like regular structs below,
326        // with fields named _0, _1, etc. (valid XML element names)
327        if kind == StructKind::Tuple {
328            for (_field_item, field_value) in struct_.fields_for_serialize() {
329                serialize_value(serializer, field_value, element_name)?;
330            }
331            return Ok(());
332        }
333
334        // Regular struct
335        trace!(type_id = %value.shape().type_identifier, "serializing struct");
336        serializer
337            .struct_metadata(value.shape())
338            .map_err(DomSerializeError::Backend)?;
339
340        // Collect fields first to check for tag field
341        let fields: Vec<_> = struct_.fields_for_serialize().collect();
342
343        // Find the tag field if present (html::tag or xml::tag)
344        // and the doctype field if present (xml::doctype)
345        let (tag_field_value, doctype_field_value): (Option<String>, Option<String>) = {
346            let mut tag_result = None;
347            let mut doctype_result = None;
348            for (field_item, field_value) in &fields {
349                serializer
350                    .field_metadata(field_item)
351                    .map_err(DomSerializeError::Backend)?;
352                if serializer.is_tag_field() {
353                    // Extract the string value from the tag field
354                    if let Some(s) = field_value.as_str() {
355                        tag_result = Some(s.to_string());
356                    } else if let Some(s) = value_to_string(*field_value, serializer) {
357                        tag_result = Some(s);
358                    }
359                } else if serializer.is_doctype_field() {
360                    // Extract the string value from the doctype field
361                    if let Some(s) = field_value.as_str() {
362                        doctype_result = Some(s.to_string());
363                    } else if let Some(s) = value_to_string(*field_value, serializer) {
364                        doctype_result = Some(s);
365                    }
366                }
367                serializer.clear_field_state();
368            }
369            (tag_result, doctype_result)
370        };
371
372        // Determine element name: tag field value > provided name > shape rename > rename_all > lowerCamelCase
373        let tag: Cow<'_, str> = if let Some(ref tag_value) = tag_field_value {
374            Cow::Owned(tag_value.clone())
375        } else if let Some(name) = element_name {
376            Cow::Borrowed(name)
377        } else if let Some(rename) = value.shape().get_builtin_attr_value::<&str>("rename") {
378            Cow::Borrowed(rename)
379        } else if let Some(rename_all) = value.shape().get_builtin_attr_value::<&str>("rename_all")
380        {
381            Cow::Owned(crate::naming::apply_rename_all(
382                value.shape().type_identifier,
383                rename_all,
384            ))
385        } else {
386            // No explicit name - apply lowerCamelCase to type identifier
387            to_element_name(value.shape().type_identifier)
388        };
389        trace!(tag = %tag, "element_start");
390
391        // Emit doctype before element_start if present
392        if let Some(ref doctype_value) = doctype_field_value {
393            trace!(doctype = %doctype_value, "emitting doctype");
394            serializer
395                .doctype(doctype_value)
396                .map_err(DomSerializeError::Backend)?;
397        }
398
399        serializer
400            .element_start(&tag, None)
401            .map_err(DomSerializeError::Backend)?;
402
403        // Fields were already collected above when checking for tag field
404        trace!(field_count = fields.len(), "collected fields for serialize");
405
406        // First pass: emit attributes
407        for (field_item, field_value) in &fields {
408            trace!(field_name = %field_item.name, "processing field for attributes");
409            serializer
410                .field_metadata(field_item)
411                .map_err(DomSerializeError::Backend)?;
412
413            let is_attr = serializer.is_attribute_field();
414            trace!(field_name = %field_item.name, is_attribute = is_attr, "field_metadata result");
415
416            if is_attr {
417                trace!(field_name = %field_item.name, "attribute field");
418                // Compute attribute name: rename > lowerCamelCase(field.name)
419                // BUT for flattened map entries (field is None), use the key as-is
420                let attr_name = if let Some(field) = field_item.field {
421                    field
422                        .rename
423                        .map(Cow::Borrowed)
424                        .unwrap_or_else(|| to_element_name(&field_item.name))
425                } else {
426                    // Flattened map entry - preserve the key exactly as stored
427                    field_item.name.clone()
428                };
429
430                // Check for proxy: first field-level, then container-level on the value's shape
431                let format_ns = serializer.format_namespace();
432                let proxy_def = field_item
433                    .field
434                    .and_then(|f| f.effective_proxy(format_ns))
435                    .or_else(|| field_value.shape().effective_proxy(format_ns));
436
437                if let Some(proxy_def) = proxy_def {
438                    match field_value.custom_serialization_with_proxy(proxy_def) {
439                        Ok(proxy_peek) => {
440                            serializer
441                                .attribute(&attr_name, proxy_peek.as_peek(), None)
442                                .map_err(DomSerializeError::Backend)?;
443                        }
444                        Err(e) => {
445                            return Err(DomSerializeError::Reflect(e));
446                        }
447                    }
448                } else {
449                    serializer
450                        .attribute(&attr_name, *field_value, None)
451                        .map_err(DomSerializeError::Backend)?;
452                }
453                serializer.clear_field_state();
454            }
455        }
456
457        trace!("children_start");
458        serializer
459            .children_start()
460            .map_err(DomSerializeError::Backend)?;
461
462        // Second pass: emit child elements and text
463        for (field_item, field_value) in &fields {
464            serializer
465                .field_metadata(field_item)
466                .map_err(DomSerializeError::Backend)?;
467
468            if serializer.is_attribute_field() {
469                serializer.clear_field_state();
470                continue;
471            }
472
473            // Skip tag fields - the value was already used as the element name
474            if serializer.is_tag_field() {
475                serializer.clear_field_state();
476                continue;
477            }
478
479            // Skip doctype fields - the value was already emitted as DOCTYPE
480            if serializer.is_doctype_field() {
481                serializer.clear_field_state();
482                continue;
483            }
484
485            if serializer.is_text_field() {
486                if let Some(s) = value_to_string(*field_value, serializer) {
487                    serializer.text(&s).map_err(DomSerializeError::Backend)?;
488                }
489                serializer.clear_field_state();
490                continue;
491            }
492
493            // For xml::elements, serialize items directly (they determine their own element names)
494            // Exception: if the field has an explicit rename, use that name for each item
495            let is_elements = serializer.is_elements_field();
496            let explicit_rename = field_item.field.and_then(|f| f.rename);
497
498            // For flattened fields (flatten on Vec<Enum>), the FieldsForSerializeIter
499            // already yields each enum item as a separate field with the variant name.
500            // We should use that name directly (set in field_item.name/rename).
501            let is_flattened = field_item.flattened;
502
503            // Check if this is a text variant from a flattened enum (html::text or xml::text)
504            // Text variants should be serialized as raw text without element wrapping
505            if field_item.is_text_variant {
506                if let Some(s) = value_to_string(*field_value, serializer) {
507                    serializer.text(&s).map_err(DomSerializeError::Backend)?;
508                }
509                serializer.clear_field_state();
510                continue;
511            }
512
513            // Compute field element name: rename > lowerCamelCase(field.name)
514            let field_element_name: Option<Cow<'_, str>> =
515                if is_elements && explicit_rename.is_none() {
516                    None // Items determine their own element names
517                } else if is_flattened {
518                    // Flattened field: the FieldsForSerializeIter expands collections and yields
519                    // individual items. For enums, it yields the variant name in field_item.
520                    // Use that name as the element name for the item.
521                    Some(to_element_name(field_item.effective_name()))
522                } else if let Some(rename) = explicit_rename {
523                    // Use the explicit rename value as-is
524                    Some(Cow::Borrowed(rename))
525                } else {
526                    // Apply lowerCamelCase to field name
527                    Some(to_element_name(&field_item.name))
528                };
529
530            // Check for proxy: first field-level, then container-level on the value's shape
531            let format_ns = serializer.format_namespace();
532            let proxy_def = field_item
533                .field
534                .and_then(|f| f.effective_proxy(format_ns))
535                .or_else(|| field_value.shape().effective_proxy(format_ns));
536
537            if let Some(proxy_def) = proxy_def {
538                // Use custom_serialization_with_proxy for proxy
539                match field_value.custom_serialization_with_proxy(proxy_def) {
540                    Ok(proxy_peek) => {
541                        serialize_value(
542                            serializer,
543                            proxy_peek.as_peek(),
544                            field_element_name.as_deref(),
545                        )?;
546                    }
547                    Err(e) => {
548                        return Err(DomSerializeError::Reflect(e));
549                    }
550                }
551            } else {
552                serialize_value(serializer, *field_value, field_element_name.as_deref())?;
553            }
554
555            serializer.clear_field_state();
556        }
557
558        serializer
559            .children_end()
560            .map_err(DomSerializeError::Backend)?;
561        serializer
562            .element_end(&tag)
563            .map_err(DomSerializeError::Backend)?;
564
565        return Ok(());
566    }
567
568    // Handle enums
569    if let Ok(enum_) = value.into_enum() {
570        let variant = enum_.active_variant().map_err(|_| {
571            DomSerializeError::Unsupported(Cow::Borrowed("opaque enum layout is unsupported"))
572        })?;
573
574        serializer
575            .variant_metadata(variant)
576            .map_err(DomSerializeError::Backend)?;
577
578        let untagged = value.shape().is_untagged();
579        let tag_attr = value.shape().get_tag_attr();
580        let content_attr = value.shape().get_content_attr();
581
582        // Unit variant
583        if variant.data.kind == StructKind::Unit {
584            // Use effective_name() to honor rename_all on enum
585            let variant_name: Cow<'_, str> = if variant.rename.is_some() {
586                Cow::Borrowed(variant.effective_name())
587            } else {
588                to_element_name(variant.name)
589            };
590
591            if untagged {
592                serializer
593                    .text(&variant_name)
594                    .map_err(DomSerializeError::Backend)?;
595            } else if let Some(tag) = element_name {
596                serializer
597                    .element_start(tag, None)
598                    .map_err(DomSerializeError::Backend)?;
599                serializer
600                    .children_start()
601                    .map_err(DomSerializeError::Backend)?;
602                serializer
603                    .text(&variant_name)
604                    .map_err(DomSerializeError::Backend)?;
605                serializer
606                    .children_end()
607                    .map_err(DomSerializeError::Backend)?;
608                serializer
609                    .element_end(tag)
610                    .map_err(DomSerializeError::Backend)?;
611            } else {
612                serializer
613                    .text(&variant_name)
614                    .map_err(DomSerializeError::Backend)?;
615            }
616            return Ok(());
617        }
618
619        // Newtype variant (single unnamed field)
620        if variant.data.kind == StructKind::TupleStruct && variant.data.fields.len() == 1 {
621            let inner = enum_
622                .fields_for_serialize()
623                .next()
624                .map(|(_, v)| v)
625                .ok_or_else(|| {
626                    DomSerializeError::Unsupported(Cow::Borrowed("newtype variant missing field"))
627                })?;
628
629            // Text variant (html::text or xml::text) - emit as plain text, no element wrapper
630            if variant.is_text() {
631                if let Some(s) = value_to_string(inner, serializer) {
632                    serializer.text(&s).map_err(DomSerializeError::Backend)?;
633                }
634                return Ok(());
635            }
636
637            if untagged {
638                return serialize_value(serializer, inner, element_name);
639            }
640
641            // Use effective_name() to honor rename_all on enum
642            let variant_name: Cow<'_, str> = if variant.rename.is_some() {
643                Cow::Borrowed(variant.effective_name())
644            } else {
645                to_element_name(variant.name)
646            };
647
648            // Externally tagged: <Variant>inner</Variant>
649            if let Some(outer_tag) = element_name {
650                serializer
651                    .element_start(outer_tag, None)
652                    .map_err(DomSerializeError::Backend)?;
653                serializer
654                    .children_start()
655                    .map_err(DomSerializeError::Backend)?;
656            }
657
658            serialize_value(serializer, inner, Some(&variant_name))?;
659
660            if let Some(outer_tag) = element_name {
661                serializer
662                    .children_end()
663                    .map_err(DomSerializeError::Backend)?;
664                serializer
665                    .element_end(outer_tag)
666                    .map_err(DomSerializeError::Backend)?;
667            }
668
669            return Ok(());
670        }
671
672        // Struct variant
673        // Use effective_name() to honor rename_all on enum
674        let variant_name: Cow<'_, str> = if variant.rename.is_some() {
675            Cow::Borrowed(variant.effective_name())
676        } else {
677            to_element_name(variant.name)
678        };
679
680        match (tag_attr, content_attr) {
681            // Internally tagged
682            (Some(tag_key), None) => {
683                let tag = element_name.unwrap_or("value");
684                serializer
685                    .element_start(tag, None)
686                    .map_err(DomSerializeError::Backend)?;
687                serializer
688                    .children_start()
689                    .map_err(DomSerializeError::Backend)?;
690
691                // Emit tag field
692                serializer
693                    .element_start(tag_key, None)
694                    .map_err(DomSerializeError::Backend)?;
695                serializer
696                    .children_start()
697                    .map_err(DomSerializeError::Backend)?;
698                serializer
699                    .text(&variant_name)
700                    .map_err(DomSerializeError::Backend)?;
701                serializer
702                    .children_end()
703                    .map_err(DomSerializeError::Backend)?;
704                serializer
705                    .element_end(tag_key)
706                    .map_err(DomSerializeError::Backend)?;
707
708                // Emit variant fields
709                serialize_enum_variant_fields(serializer, enum_)?;
710
711                serializer
712                    .children_end()
713                    .map_err(DomSerializeError::Backend)?;
714                serializer
715                    .element_end(tag)
716                    .map_err(DomSerializeError::Backend)?;
717            }
718
719            // Adjacently tagged
720            (Some(tag_key), Some(content_key)) => {
721                let tag = element_name.unwrap_or("value");
722                serializer
723                    .element_start(tag, None)
724                    .map_err(DomSerializeError::Backend)?;
725                serializer
726                    .children_start()
727                    .map_err(DomSerializeError::Backend)?;
728
729                // Emit tag
730                serializer
731                    .element_start(tag_key, None)
732                    .map_err(DomSerializeError::Backend)?;
733                serializer
734                    .children_start()
735                    .map_err(DomSerializeError::Backend)?;
736                serializer
737                    .text(&variant_name)
738                    .map_err(DomSerializeError::Backend)?;
739                serializer
740                    .children_end()
741                    .map_err(DomSerializeError::Backend)?;
742                serializer
743                    .element_end(tag_key)
744                    .map_err(DomSerializeError::Backend)?;
745
746                // Emit content
747                serializer
748                    .element_start(content_key, None)
749                    .map_err(DomSerializeError::Backend)?;
750                serializer
751                    .children_start()
752                    .map_err(DomSerializeError::Backend)?;
753                serialize_enum_variant_fields(serializer, enum_)?;
754                serializer
755                    .children_end()
756                    .map_err(DomSerializeError::Backend)?;
757                serializer
758                    .element_end(content_key)
759                    .map_err(DomSerializeError::Backend)?;
760
761                serializer
762                    .children_end()
763                    .map_err(DomSerializeError::Backend)?;
764                serializer
765                    .element_end(tag)
766                    .map_err(DomSerializeError::Backend)?;
767            }
768
769            // Externally tagged (default) or untagged
770            _ => {
771                if untagged {
772                    // Serialize just the variant content
773                    let tag = element_name.unwrap_or("value");
774                    serializer
775                        .element_start(tag, None)
776                        .map_err(DomSerializeError::Backend)?;
777                    serialize_enum_variant_fields(serializer, enum_)?;
778                    serializer
779                        .children_end()
780                        .map_err(DomSerializeError::Backend)?;
781                    serializer
782                        .element_end(tag)
783                        .map_err(DomSerializeError::Backend)?;
784                } else {
785                    // Externally tagged: <outer><Variant>...</Variant></outer>
786                    if let Some(outer_tag) = element_name {
787                        serializer
788                            .element_start(outer_tag, None)
789                            .map_err(DomSerializeError::Backend)?;
790                        serializer
791                            .children_start()
792                            .map_err(DomSerializeError::Backend)?;
793                    }
794
795                    serializer
796                        .element_start(&variant_name, None)
797                        .map_err(DomSerializeError::Backend)?;
798                    serialize_enum_variant_fields(serializer, enum_)?;
799                    serializer
800                        .children_end()
801                        .map_err(DomSerializeError::Backend)?;
802                    serializer
803                        .element_end(&variant_name)
804                        .map_err(DomSerializeError::Backend)?;
805
806                    if let Some(outer_tag) = element_name {
807                        serializer
808                            .children_end()
809                            .map_err(DomSerializeError::Backend)?;
810                        serializer
811                            .element_end(outer_tag)
812                            .map_err(DomSerializeError::Backend)?;
813                    }
814                }
815            }
816        }
817
818        return Ok(());
819    }
820
821    Err(DomSerializeError::Unsupported(Cow::Owned(alloc::format!(
822        "unsupported type: {:?}",
823        value.shape().def
824    ))))
825}
826
827/// Serialize enum variant fields, handling attributes correctly.
828///
829/// This function implements a two-pass approach similar to struct serialization:
830/// 1. First pass: emit all fields marked with `xml::attribute` as XML attributes
831/// 2. Second pass: emit remaining fields as child elements or text
832fn serialize_enum_variant_fields<S>(
833    serializer: &mut S,
834    enum_: facet_reflect::PeekEnum<'_, '_>,
835) -> Result<(), DomSerializeError<S::Error>>
836where
837    S: DomSerializer,
838{
839    // Collect all fields into a Vec so we can iterate twice
840    let fields: Vec<_> = enum_.fields_for_serialize().collect();
841
842    // First pass: emit attributes
843    for (field_item, field_value) in &fields {
844        serializer
845            .field_metadata(field_item)
846            .map_err(DomSerializeError::Backend)?;
847
848        if serializer.is_attribute_field() {
849            // Compute attribute name: rename > lowerCamelCase(field.name)
850            let attr_name = if let Some(field) = field_item.field {
851                field
852                    .rename
853                    .map(Cow::Borrowed)
854                    .unwrap_or_else(|| to_element_name(&field_item.name))
855            } else {
856                field_item.name.clone()
857            };
858
859            // Check for proxy
860            let format_ns = serializer.format_namespace();
861            let proxy_def = field_item
862                .field
863                .and_then(|f| f.effective_proxy(format_ns))
864                .or_else(|| field_value.shape().effective_proxy(format_ns));
865
866            if let Some(proxy_def) = proxy_def {
867                match field_value.custom_serialization_with_proxy(proxy_def) {
868                    Ok(proxy_peek) => {
869                        serializer
870                            .attribute(&attr_name, proxy_peek.as_peek(), None)
871                            .map_err(DomSerializeError::Backend)?;
872                    }
873                    Err(e) => {
874                        return Err(DomSerializeError::Reflect(e));
875                    }
876                }
877            } else {
878                serializer
879                    .attribute(&attr_name, *field_value, None)
880                    .map_err(DomSerializeError::Backend)?;
881            }
882        }
883        serializer.clear_field_state();
884    }
885
886    // Start children section
887    serializer
888        .children_start()
889        .map_err(DomSerializeError::Backend)?;
890
891    // Second pass: emit child elements and text
892    for (field_item, field_value) in &fields {
893        serializer
894            .field_metadata(field_item)
895            .map_err(DomSerializeError::Backend)?;
896
897        // Skip attributes (already handled)
898        if serializer.is_attribute_field() {
899            serializer.clear_field_state();
900            continue;
901        }
902
903        // Skip tag fields
904        if serializer.is_tag_field() {
905            serializer.clear_field_state();
906            continue;
907        }
908
909        // Skip doctype fields
910        if serializer.is_doctype_field() {
911            serializer.clear_field_state();
912            continue;
913        }
914
915        // Handle text fields
916        if serializer.is_text_field() {
917            if let Some(s) = value_to_string(*field_value, serializer) {
918                serializer.text(&s).map_err(DomSerializeError::Backend)?;
919            }
920            serializer.clear_field_state();
921            continue;
922        }
923
924        // Handle text variants from flattened enums
925        if field_item.is_text_variant {
926            if let Some(s) = value_to_string(*field_value, serializer) {
927                serializer.text(&s).map_err(DomSerializeError::Backend)?;
928            }
929            serializer.clear_field_state();
930            continue;
931        }
932
933        // Compute field element name
934        let is_elements = serializer.is_elements_field();
935        let explicit_rename = field_item.field.and_then(|f| f.rename);
936        let is_flattened = field_item.flattened;
937
938        let field_element_name: Option<Cow<'_, str>> = if is_elements && explicit_rename.is_none() {
939            None // Items determine their own element names
940        } else if is_flattened {
941            // For flattened collections (Vec, etc.), pass None so items determine their own names
942            None
943        } else if let Some(rename) = explicit_rename {
944            Some(Cow::Borrowed(rename))
945        } else {
946            Some(to_element_name(&field_item.name))
947        };
948
949        // Check for proxy
950        let format_ns = serializer.format_namespace();
951        let proxy_def = field_item
952            .field
953            .and_then(|f| f.effective_proxy(format_ns))
954            .or_else(|| field_value.shape().effective_proxy(format_ns));
955
956        if let Some(proxy_def) = proxy_def {
957            match field_value.custom_serialization_with_proxy(proxy_def) {
958                Ok(proxy_peek) => {
959                    serialize_value(
960                        serializer,
961                        proxy_peek.as_peek(),
962                        field_element_name.as_deref(),
963                    )?;
964                }
965                Err(e) => {
966                    return Err(DomSerializeError::Reflect(e));
967                }
968            }
969        } else {
970            serialize_value(serializer, *field_value, field_element_name.as_deref())?;
971        }
972
973        serializer.clear_field_state();
974    }
975
976    Ok(())
977}
978
979/// Serialize through a proxy type.
980fn serialize_via_proxy<S>(
981    serializer: &mut S,
982    value: Peek<'_, '_>,
983    element_name: Option<&str>,
984) -> Result<(), DomSerializeError<S::Error>>
985where
986    S: DomSerializer,
987{
988    // Use the high-level API that handles allocation and conversion
989    // Pass format namespace for format-specific proxy resolution
990    let owned_peek = value
991        .custom_serialization_from_shape_with_format(serializer.format_namespace())
992        .map_err(DomSerializeError::Reflect)?;
993
994    match owned_peek {
995        Some(proxy_peek) => {
996            // proxy_peek is an OwnedPeek that will auto-deallocate on drop
997            serialize_value(serializer, proxy_peek.as_peek(), element_name)
998        }
999        None => {
1000            // No proxy on shape - this shouldn't happen since we checked proxy exists
1001            Err(DomSerializeError::Unsupported(Cow::Borrowed(
1002                "proxy serialization failed: no proxy on shape",
1003            )))
1004        }
1005    }
1006}
1007
1008/// Dereference smart pointers (Box, Arc, Rc) to get the inner value.
1009fn deref_if_pointer<'mem, 'facet>(value: Peek<'mem, 'facet>) -> Peek<'mem, 'facet> {
1010    if let Ok(ptr) = value.into_pointer()
1011        && let Some(inner) = ptr.borrow_inner()
1012    {
1013        return deref_if_pointer(inner);
1014    }
1015    value
1016}
1017
1018/// Convert a value to a string if it's a scalar type.
1019fn value_to_string<S: DomSerializer>(value: Peek<'_, '_>, serializer: &S) -> Option<String> {
1020    use facet_core::ScalarType;
1021
1022    // Handle Option<T> by unwrapping if Some, returning None if None
1023    if let Def::Option(_) = &value.shape().def
1024        && let Ok(opt) = value.into_option()
1025    {
1026        return match opt.value() {
1027            Some(inner) => value_to_string(inner, serializer),
1028            None => None,
1029        };
1030    }
1031
1032    if let Some(scalar_type) = value.scalar_type() {
1033        let s = match scalar_type {
1034            ScalarType::Unit => return Some("null".into()),
1035            ScalarType::Bool => if *value.get::<bool>().ok()? {
1036                "true"
1037            } else {
1038                "false"
1039            }
1040            .into(),
1041            ScalarType::Char => value.get::<char>().ok()?.to_string(),
1042            ScalarType::Str | ScalarType::String | ScalarType::CowStr => {
1043                value.as_str()?.to_string()
1044            }
1045            ScalarType::F32 => serializer.format_float(*value.get::<f32>().ok()? as f64),
1046            ScalarType::F64 => serializer.format_float(*value.get::<f64>().ok()?),
1047            ScalarType::U8 => value.get::<u8>().ok()?.to_string(),
1048            ScalarType::U16 => value.get::<u16>().ok()?.to_string(),
1049            ScalarType::U32 => value.get::<u32>().ok()?.to_string(),
1050            ScalarType::U64 => value.get::<u64>().ok()?.to_string(),
1051            ScalarType::U128 => value.get::<u128>().ok()?.to_string(),
1052            ScalarType::USize => value.get::<usize>().ok()?.to_string(),
1053            ScalarType::I8 => value.get::<i8>().ok()?.to_string(),
1054            ScalarType::I16 => value.get::<i16>().ok()?.to_string(),
1055            ScalarType::I32 => value.get::<i32>().ok()?.to_string(),
1056            ScalarType::I64 => value.get::<i64>().ok()?.to_string(),
1057            ScalarType::I128 => value.get::<i128>().ok()?.to_string(),
1058            ScalarType::ISize => value.get::<isize>().ok()?.to_string(),
1059            #[cfg(feature = "net")]
1060            ScalarType::IpAddr => value.get::<core::net::IpAddr>().ok()?.to_string(),
1061            #[cfg(feature = "net")]
1062            ScalarType::Ipv4Addr => value.get::<core::net::Ipv4Addr>().ok()?.to_string(),
1063            #[cfg(feature = "net")]
1064            ScalarType::Ipv6Addr => value.get::<core::net::Ipv6Addr>().ok()?.to_string(),
1065            #[cfg(feature = "net")]
1066            ScalarType::SocketAddr => value.get::<core::net::SocketAddr>().ok()?.to_string(),
1067            _ => return None,
1068        };
1069        return Some(s);
1070    }
1071
1072    // Try Display for Def::Scalar types (SmolStr, etc.)
1073    if matches!(value.shape().def, Def::Scalar) && value.shape().vtable.has_display() {
1074        return Some(alloc::format!("{}", value));
1075    }
1076
1077    None
1078}