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 tuples, serialize as a flat sequence (like Vec)
324        // Each tuple field becomes a sibling element with the same tag name
325        if kind == StructKind::Tuple || kind == StructKind::TupleStruct {
326            for (_field_item, field_value) in struct_.fields_for_serialize() {
327                serialize_value(serializer, field_value, element_name)?;
328            }
329            return Ok(());
330        }
331
332        // Regular struct
333        trace!(type_id = %value.shape().type_identifier, "serializing struct");
334        serializer
335            .struct_metadata(value.shape())
336            .map_err(DomSerializeError::Backend)?;
337
338        // Collect fields first to check for tag field
339        let fields: Vec<_> = struct_.fields_for_serialize().collect();
340
341        // Find the tag field if present (html::tag or xml::tag)
342        // and the doctype field if present (xml::doctype)
343        let (tag_field_value, doctype_field_value): (Option<String>, Option<String>) = {
344            let mut tag_result = None;
345            let mut doctype_result = None;
346            for (field_item, field_value) in &fields {
347                serializer
348                    .field_metadata(field_item)
349                    .map_err(DomSerializeError::Backend)?;
350                if serializer.is_tag_field() {
351                    // Extract the string value from the tag field
352                    if let Some(s) = field_value.as_str() {
353                        tag_result = Some(s.to_string());
354                    } else if let Some(s) = value_to_string(*field_value, serializer) {
355                        tag_result = Some(s);
356                    }
357                } else if serializer.is_doctype_field() {
358                    // Extract the string value from the doctype field
359                    if let Some(s) = field_value.as_str() {
360                        doctype_result = Some(s.to_string());
361                    } else if let Some(s) = value_to_string(*field_value, serializer) {
362                        doctype_result = Some(s);
363                    }
364                }
365                serializer.clear_field_state();
366            }
367            (tag_result, doctype_result)
368        };
369
370        // Determine element name: tag field value > provided name > shape rename > type identifier (lowerCamelCase)
371        let tag: Cow<'_, str> = if let Some(ref tag_value) = tag_field_value {
372            Cow::Owned(tag_value.clone())
373        } else if let Some(name) = element_name {
374            Cow::Borrowed(name)
375        } else if let Some(rename) = value.shape().get_builtin_attr_value::<&str>("rename") {
376            Cow::Borrowed(rename)
377        } else {
378            // No explicit name - apply lowerCamelCase to type identifier
379            to_element_name(value.shape().type_identifier)
380        };
381        trace!(tag = %tag, "element_start");
382
383        // Emit doctype before element_start if present
384        if let Some(ref doctype_value) = doctype_field_value {
385            trace!(doctype = %doctype_value, "emitting doctype");
386            serializer
387                .doctype(doctype_value)
388                .map_err(DomSerializeError::Backend)?;
389        }
390
391        serializer
392            .element_start(&tag, None)
393            .map_err(DomSerializeError::Backend)?;
394
395        // Fields were already collected above when checking for tag field
396        trace!(field_count = fields.len(), "collected fields for serialize");
397
398        // First pass: emit attributes
399        for (field_item, field_value) in &fields {
400            trace!(field_name = %field_item.name, "processing field for attributes");
401            serializer
402                .field_metadata(field_item)
403                .map_err(DomSerializeError::Backend)?;
404
405            let is_attr = serializer.is_attribute_field();
406            trace!(field_name = %field_item.name, is_attribute = is_attr, "field_metadata result");
407
408            if is_attr {
409                trace!(field_name = %field_item.name, "attribute field");
410                // Compute attribute name: rename > lowerCamelCase(field.name)
411                // BUT for flattened map entries (field is None), use the key as-is
412                let attr_name = if let Some(field) = field_item.field {
413                    field
414                        .rename
415                        .map(Cow::Borrowed)
416                        .unwrap_or_else(|| to_element_name(&field_item.name))
417                } else {
418                    // Flattened map entry - preserve the key exactly as stored
419                    field_item.name.clone()
420                };
421
422                // Check for proxy: first field-level, then container-level on the value's shape
423                let format_ns = serializer.format_namespace();
424                let proxy_def = field_item
425                    .field
426                    .and_then(|f| f.effective_proxy(format_ns))
427                    .or_else(|| field_value.shape().effective_proxy(format_ns));
428
429                if let Some(proxy_def) = proxy_def {
430                    match field_value.custom_serialization_with_proxy(proxy_def) {
431                        Ok(proxy_peek) => {
432                            serializer
433                                .attribute(&attr_name, proxy_peek.as_peek(), None)
434                                .map_err(DomSerializeError::Backend)?;
435                        }
436                        Err(e) => {
437                            return Err(DomSerializeError::Reflect(e));
438                        }
439                    }
440                } else {
441                    serializer
442                        .attribute(&attr_name, *field_value, None)
443                        .map_err(DomSerializeError::Backend)?;
444                }
445                serializer.clear_field_state();
446            }
447        }
448
449        trace!("children_start");
450        serializer
451            .children_start()
452            .map_err(DomSerializeError::Backend)?;
453
454        // Second pass: emit child elements and text
455        for (field_item, field_value) in &fields {
456            serializer
457                .field_metadata(field_item)
458                .map_err(DomSerializeError::Backend)?;
459
460            if serializer.is_attribute_field() {
461                serializer.clear_field_state();
462                continue;
463            }
464
465            // Skip tag fields - the value was already used as the element name
466            if serializer.is_tag_field() {
467                serializer.clear_field_state();
468                continue;
469            }
470
471            // Skip doctype fields - the value was already emitted as DOCTYPE
472            if serializer.is_doctype_field() {
473                serializer.clear_field_state();
474                continue;
475            }
476
477            if serializer.is_text_field() {
478                if let Some(s) = value_to_string(*field_value, serializer) {
479                    serializer.text(&s).map_err(DomSerializeError::Backend)?;
480                }
481                serializer.clear_field_state();
482                continue;
483            }
484
485            // For xml::elements, serialize items directly (they determine their own element names)
486            // Exception: if the field has an explicit rename, use that name for each item
487            let is_elements = serializer.is_elements_field();
488            let explicit_rename = field_item.field.and_then(|f| f.rename);
489
490            // For flattened fields (flatten on Vec<Enum>), the FieldsForSerializeIter
491            // already yields each enum item as a separate field with the variant name.
492            // We should use that name directly (set in field_item.name/rename).
493            let is_flattened = field_item.flattened;
494
495            // Check if this is a text variant from a flattened enum (html::text or xml::text)
496            // Text variants should be serialized as raw text without element wrapping
497            if field_item.is_text_variant {
498                if let Some(s) = value_to_string(*field_value, serializer) {
499                    serializer.text(&s).map_err(DomSerializeError::Backend)?;
500                }
501                serializer.clear_field_state();
502                continue;
503            }
504
505            // Compute field element name: rename > lowerCamelCase(field.name)
506            let field_element_name: Option<Cow<'_, str>> =
507                if is_elements && explicit_rename.is_none() {
508                    None // Items determine their own element names
509                } else if is_flattened {
510                    // Flattened field: the FieldsForSerializeIter expands collections and yields
511                    // individual items. For enums, it yields the variant name in field_item.
512                    // Use that name as the element name for the item.
513                    Some(to_element_name(field_item.effective_name()))
514                } else if let Some(rename) = explicit_rename {
515                    // Use the explicit rename value as-is
516                    Some(Cow::Borrowed(rename))
517                } else {
518                    // Apply lowerCamelCase to field name
519                    Some(to_element_name(&field_item.name))
520                };
521
522            // Check for proxy: first field-level, then container-level on the value's shape
523            let format_ns = serializer.format_namespace();
524            let proxy_def = field_item
525                .field
526                .and_then(|f| f.effective_proxy(format_ns))
527                .or_else(|| field_value.shape().effective_proxy(format_ns));
528
529            if let Some(proxy_def) = proxy_def {
530                // Use custom_serialization_with_proxy for proxy
531                match field_value.custom_serialization_with_proxy(proxy_def) {
532                    Ok(proxy_peek) => {
533                        serialize_value(
534                            serializer,
535                            proxy_peek.as_peek(),
536                            field_element_name.as_deref(),
537                        )?;
538                    }
539                    Err(e) => {
540                        return Err(DomSerializeError::Reflect(e));
541                    }
542                }
543            } else {
544                serialize_value(serializer, *field_value, field_element_name.as_deref())?;
545            }
546
547            serializer.clear_field_state();
548        }
549
550        serializer
551            .children_end()
552            .map_err(DomSerializeError::Backend)?;
553        serializer
554            .element_end(&tag)
555            .map_err(DomSerializeError::Backend)?;
556
557        return Ok(());
558    }
559
560    // Handle enums
561    if let Ok(enum_) = value.into_enum() {
562        let variant = enum_.active_variant().map_err(|_| {
563            DomSerializeError::Unsupported(Cow::Borrowed("opaque enum layout is unsupported"))
564        })?;
565
566        serializer
567            .variant_metadata(variant)
568            .map_err(DomSerializeError::Backend)?;
569
570        let untagged = value.shape().is_untagged();
571        let tag_attr = value.shape().get_tag_attr();
572        let content_attr = value.shape().get_content_attr();
573
574        // Unit variant
575        if variant.data.kind == StructKind::Unit {
576            let variant_name: Cow<'_, str> = variant
577                .get_builtin_attr("rename")
578                .and_then(|a| a.get_as::<&str>().copied())
579                .map(Cow::Borrowed)
580                .unwrap_or_else(|| to_element_name(variant.name));
581
582            if untagged {
583                serializer
584                    .text(&variant_name)
585                    .map_err(DomSerializeError::Backend)?;
586            } else if let Some(tag) = element_name {
587                serializer
588                    .element_start(tag, None)
589                    .map_err(DomSerializeError::Backend)?;
590                serializer
591                    .children_start()
592                    .map_err(DomSerializeError::Backend)?;
593                serializer
594                    .text(&variant_name)
595                    .map_err(DomSerializeError::Backend)?;
596                serializer
597                    .children_end()
598                    .map_err(DomSerializeError::Backend)?;
599                serializer
600                    .element_end(tag)
601                    .map_err(DomSerializeError::Backend)?;
602            } else {
603                serializer
604                    .text(&variant_name)
605                    .map_err(DomSerializeError::Backend)?;
606            }
607            return Ok(());
608        }
609
610        // Newtype variant (single unnamed field)
611        if variant.data.kind == StructKind::TupleStruct && variant.data.fields.len() == 1 {
612            let inner = enum_
613                .fields_for_serialize()
614                .next()
615                .map(|(_, v)| v)
616                .ok_or_else(|| {
617                    DomSerializeError::Unsupported(Cow::Borrowed("newtype variant missing field"))
618                })?;
619
620            // Text variant (html::text or xml::text) - emit as plain text, no element wrapper
621            if variant.is_text() {
622                if let Some(s) = value_to_string(inner, serializer) {
623                    serializer.text(&s).map_err(DomSerializeError::Backend)?;
624                }
625                return Ok(());
626            }
627
628            if untagged {
629                return serialize_value(serializer, inner, element_name);
630            }
631
632            let variant_name: Cow<'_, str> = variant
633                .get_builtin_attr("rename")
634                .and_then(|a| a.get_as::<&str>().copied())
635                .map(Cow::Borrowed)
636                .unwrap_or_else(|| to_element_name(variant.name));
637
638            // Externally tagged: <Variant>inner</Variant>
639            if let Some(outer_tag) = element_name {
640                serializer
641                    .element_start(outer_tag, None)
642                    .map_err(DomSerializeError::Backend)?;
643                serializer
644                    .children_start()
645                    .map_err(DomSerializeError::Backend)?;
646            }
647
648            serialize_value(serializer, inner, Some(&variant_name))?;
649
650            if let Some(outer_tag) = element_name {
651                serializer
652                    .children_end()
653                    .map_err(DomSerializeError::Backend)?;
654                serializer
655                    .element_end(outer_tag)
656                    .map_err(DomSerializeError::Backend)?;
657            }
658
659            return Ok(());
660        }
661
662        // Struct variant
663        let variant_name: Cow<'_, str> = variant
664            .get_builtin_attr("rename")
665            .and_then(|a| a.get_as::<&str>().copied())
666            .map(Cow::Borrowed)
667            .unwrap_or_else(|| to_element_name(variant.name));
668
669        match (tag_attr, content_attr) {
670            // Internally tagged
671            (Some(tag_key), None) => {
672                let tag = element_name.unwrap_or("value");
673                serializer
674                    .element_start(tag, None)
675                    .map_err(DomSerializeError::Backend)?;
676                serializer
677                    .children_start()
678                    .map_err(DomSerializeError::Backend)?;
679
680                // Emit tag field
681                serializer
682                    .element_start(tag_key, None)
683                    .map_err(DomSerializeError::Backend)?;
684                serializer
685                    .children_start()
686                    .map_err(DomSerializeError::Backend)?;
687                serializer
688                    .text(&variant_name)
689                    .map_err(DomSerializeError::Backend)?;
690                serializer
691                    .children_end()
692                    .map_err(DomSerializeError::Backend)?;
693                serializer
694                    .element_end(tag_key)
695                    .map_err(DomSerializeError::Backend)?;
696
697                // Emit variant fields
698                serialize_enum_variant_fields(serializer, enum_)?;
699
700                serializer
701                    .children_end()
702                    .map_err(DomSerializeError::Backend)?;
703                serializer
704                    .element_end(tag)
705                    .map_err(DomSerializeError::Backend)?;
706            }
707
708            // Adjacently tagged
709            (Some(tag_key), Some(content_key)) => {
710                let tag = element_name.unwrap_or("value");
711                serializer
712                    .element_start(tag, None)
713                    .map_err(DomSerializeError::Backend)?;
714                serializer
715                    .children_start()
716                    .map_err(DomSerializeError::Backend)?;
717
718                // Emit tag
719                serializer
720                    .element_start(tag_key, None)
721                    .map_err(DomSerializeError::Backend)?;
722                serializer
723                    .children_start()
724                    .map_err(DomSerializeError::Backend)?;
725                serializer
726                    .text(&variant_name)
727                    .map_err(DomSerializeError::Backend)?;
728                serializer
729                    .children_end()
730                    .map_err(DomSerializeError::Backend)?;
731                serializer
732                    .element_end(tag_key)
733                    .map_err(DomSerializeError::Backend)?;
734
735                // Emit content
736                serializer
737                    .element_start(content_key, None)
738                    .map_err(DomSerializeError::Backend)?;
739                serializer
740                    .children_start()
741                    .map_err(DomSerializeError::Backend)?;
742                serialize_enum_variant_fields(serializer, enum_)?;
743                serializer
744                    .children_end()
745                    .map_err(DomSerializeError::Backend)?;
746                serializer
747                    .element_end(content_key)
748                    .map_err(DomSerializeError::Backend)?;
749
750                serializer
751                    .children_end()
752                    .map_err(DomSerializeError::Backend)?;
753                serializer
754                    .element_end(tag)
755                    .map_err(DomSerializeError::Backend)?;
756            }
757
758            // Externally tagged (default) or untagged
759            _ => {
760                if untagged {
761                    // Serialize just the variant content
762                    let tag = element_name.unwrap_or("value");
763                    serializer
764                        .element_start(tag, None)
765                        .map_err(DomSerializeError::Backend)?;
766                    serialize_enum_variant_fields(serializer, enum_)?;
767                    serializer
768                        .children_end()
769                        .map_err(DomSerializeError::Backend)?;
770                    serializer
771                        .element_end(tag)
772                        .map_err(DomSerializeError::Backend)?;
773                } else {
774                    // Externally tagged: <outer><Variant>...</Variant></outer>
775                    if let Some(outer_tag) = element_name {
776                        serializer
777                            .element_start(outer_tag, None)
778                            .map_err(DomSerializeError::Backend)?;
779                        serializer
780                            .children_start()
781                            .map_err(DomSerializeError::Backend)?;
782                    }
783
784                    serializer
785                        .element_start(&variant_name, None)
786                        .map_err(DomSerializeError::Backend)?;
787                    serialize_enum_variant_fields(serializer, enum_)?;
788                    serializer
789                        .children_end()
790                        .map_err(DomSerializeError::Backend)?;
791                    serializer
792                        .element_end(&variant_name)
793                        .map_err(DomSerializeError::Backend)?;
794
795                    if let Some(outer_tag) = element_name {
796                        serializer
797                            .children_end()
798                            .map_err(DomSerializeError::Backend)?;
799                        serializer
800                            .element_end(outer_tag)
801                            .map_err(DomSerializeError::Backend)?;
802                    }
803                }
804            }
805        }
806
807        return Ok(());
808    }
809
810    Err(DomSerializeError::Unsupported(Cow::Owned(alloc::format!(
811        "unsupported type: {:?}",
812        value.shape().def
813    ))))
814}
815
816/// Serialize enum variant fields, handling attributes correctly.
817///
818/// This function implements a two-pass approach similar to struct serialization:
819/// 1. First pass: emit all fields marked with `xml::attribute` as XML attributes
820/// 2. Second pass: emit remaining fields as child elements or text
821fn serialize_enum_variant_fields<S>(
822    serializer: &mut S,
823    enum_: facet_reflect::PeekEnum<'_, '_>,
824) -> Result<(), DomSerializeError<S::Error>>
825where
826    S: DomSerializer,
827{
828    // Collect all fields into a Vec so we can iterate twice
829    let fields: Vec<_> = enum_.fields_for_serialize().collect();
830
831    // First pass: emit attributes
832    for (field_item, field_value) in &fields {
833        serializer
834            .field_metadata(field_item)
835            .map_err(DomSerializeError::Backend)?;
836
837        if serializer.is_attribute_field() {
838            // Compute attribute name: rename > lowerCamelCase(field.name)
839            let attr_name = if let Some(field) = field_item.field {
840                field
841                    .rename
842                    .map(Cow::Borrowed)
843                    .unwrap_or_else(|| to_element_name(&field_item.name))
844            } else {
845                field_item.name.clone()
846            };
847
848            // Check for proxy
849            let format_ns = serializer.format_namespace();
850            let proxy_def = field_item
851                .field
852                .and_then(|f| f.effective_proxy(format_ns))
853                .or_else(|| field_value.shape().effective_proxy(format_ns));
854
855            if let Some(proxy_def) = proxy_def {
856                match field_value.custom_serialization_with_proxy(proxy_def) {
857                    Ok(proxy_peek) => {
858                        serializer
859                            .attribute(&attr_name, proxy_peek.as_peek(), None)
860                            .map_err(DomSerializeError::Backend)?;
861                    }
862                    Err(e) => {
863                        return Err(DomSerializeError::Reflect(e));
864                    }
865                }
866            } else {
867                serializer
868                    .attribute(&attr_name, *field_value, None)
869                    .map_err(DomSerializeError::Backend)?;
870            }
871        }
872        serializer.clear_field_state();
873    }
874
875    // Start children section
876    serializer
877        .children_start()
878        .map_err(DomSerializeError::Backend)?;
879
880    // Second pass: emit child elements and text
881    for (field_item, field_value) in &fields {
882        serializer
883            .field_metadata(field_item)
884            .map_err(DomSerializeError::Backend)?;
885
886        // Skip attributes (already handled)
887        if serializer.is_attribute_field() {
888            serializer.clear_field_state();
889            continue;
890        }
891
892        // Skip tag fields
893        if serializer.is_tag_field() {
894            serializer.clear_field_state();
895            continue;
896        }
897
898        // Skip doctype fields
899        if serializer.is_doctype_field() {
900            serializer.clear_field_state();
901            continue;
902        }
903
904        // Handle text fields
905        if serializer.is_text_field() {
906            if let Some(s) = value_to_string(*field_value, serializer) {
907                serializer.text(&s).map_err(DomSerializeError::Backend)?;
908            }
909            serializer.clear_field_state();
910            continue;
911        }
912
913        // Handle text variants from flattened enums
914        if field_item.is_text_variant {
915            if let Some(s) = value_to_string(*field_value, serializer) {
916                serializer.text(&s).map_err(DomSerializeError::Backend)?;
917            }
918            serializer.clear_field_state();
919            continue;
920        }
921
922        // Compute field element name
923        let is_elements = serializer.is_elements_field();
924        let explicit_rename = field_item.field.and_then(|f| f.rename);
925        let is_flattened = field_item.flattened;
926
927        let field_element_name: Option<Cow<'_, str>> = if is_elements && explicit_rename.is_none() {
928            None // Items determine their own element names
929        } else if is_flattened {
930            // For flattened collections (Vec, etc.), pass None so items determine their own names
931            None
932        } else if let Some(rename) = explicit_rename {
933            Some(Cow::Borrowed(rename))
934        } else {
935            Some(to_element_name(&field_item.name))
936        };
937
938        // Check for proxy
939        let format_ns = serializer.format_namespace();
940        let proxy_def = field_item
941            .field
942            .and_then(|f| f.effective_proxy(format_ns))
943            .or_else(|| field_value.shape().effective_proxy(format_ns));
944
945        if let Some(proxy_def) = proxy_def {
946            match field_value.custom_serialization_with_proxy(proxy_def) {
947                Ok(proxy_peek) => {
948                    serialize_value(
949                        serializer,
950                        proxy_peek.as_peek(),
951                        field_element_name.as_deref(),
952                    )?;
953                }
954                Err(e) => {
955                    return Err(DomSerializeError::Reflect(e));
956                }
957            }
958        } else {
959            serialize_value(serializer, *field_value, field_element_name.as_deref())?;
960        }
961
962        serializer.clear_field_state();
963    }
964
965    Ok(())
966}
967
968/// Serialize through a proxy type.
969fn serialize_via_proxy<S>(
970    serializer: &mut S,
971    value: Peek<'_, '_>,
972    element_name: Option<&str>,
973) -> Result<(), DomSerializeError<S::Error>>
974where
975    S: DomSerializer,
976{
977    // Use the high-level API that handles allocation and conversion
978    // Pass format namespace for format-specific proxy resolution
979    let owned_peek = value
980        .custom_serialization_from_shape_with_format(serializer.format_namespace())
981        .map_err(DomSerializeError::Reflect)?;
982
983    match owned_peek {
984        Some(proxy_peek) => {
985            // proxy_peek is an OwnedPeek that will auto-deallocate on drop
986            serialize_value(serializer, proxy_peek.as_peek(), element_name)
987        }
988        None => {
989            // No proxy on shape - this shouldn't happen since we checked proxy exists
990            Err(DomSerializeError::Unsupported(Cow::Borrowed(
991                "proxy serialization failed: no proxy on shape",
992            )))
993        }
994    }
995}
996
997/// Dereference smart pointers (Box, Arc, Rc) to get the inner value.
998fn deref_if_pointer<'mem, 'facet>(value: Peek<'mem, 'facet>) -> Peek<'mem, 'facet> {
999    if let Ok(ptr) = value.into_pointer()
1000        && let Some(inner) = ptr.borrow_inner()
1001    {
1002        return deref_if_pointer(inner);
1003    }
1004    value
1005}
1006
1007/// Convert a value to a string if it's a scalar type.
1008fn value_to_string<S: DomSerializer>(value: Peek<'_, '_>, serializer: &S) -> Option<String> {
1009    use facet_core::ScalarType;
1010
1011    // Handle Option<T> by unwrapping if Some, returning None if None
1012    if let Def::Option(_) = &value.shape().def
1013        && let Ok(opt) = value.into_option()
1014    {
1015        return match opt.value() {
1016            Some(inner) => value_to_string(inner, serializer),
1017            None => None,
1018        };
1019    }
1020
1021    if let Some(scalar_type) = value.scalar_type() {
1022        let s = match scalar_type {
1023            ScalarType::Unit => return Some("null".into()),
1024            ScalarType::Bool => if *value.get::<bool>().ok()? {
1025                "true"
1026            } else {
1027                "false"
1028            }
1029            .into(),
1030            ScalarType::Char => value.get::<char>().ok()?.to_string(),
1031            ScalarType::Str | ScalarType::String | ScalarType::CowStr => {
1032                value.as_str()?.to_string()
1033            }
1034            ScalarType::F32 => serializer.format_float(*value.get::<f32>().ok()? as f64),
1035            ScalarType::F64 => serializer.format_float(*value.get::<f64>().ok()?),
1036            ScalarType::U8 => value.get::<u8>().ok()?.to_string(),
1037            ScalarType::U16 => value.get::<u16>().ok()?.to_string(),
1038            ScalarType::U32 => value.get::<u32>().ok()?.to_string(),
1039            ScalarType::U64 => value.get::<u64>().ok()?.to_string(),
1040            ScalarType::U128 => value.get::<u128>().ok()?.to_string(),
1041            ScalarType::USize => value.get::<usize>().ok()?.to_string(),
1042            ScalarType::I8 => value.get::<i8>().ok()?.to_string(),
1043            ScalarType::I16 => value.get::<i16>().ok()?.to_string(),
1044            ScalarType::I32 => value.get::<i32>().ok()?.to_string(),
1045            ScalarType::I64 => value.get::<i64>().ok()?.to_string(),
1046            ScalarType::I128 => value.get::<i128>().ok()?.to_string(),
1047            ScalarType::ISize => value.get::<isize>().ok()?.to_string(),
1048            #[cfg(feature = "net")]
1049            ScalarType::IpAddr => value.get::<core::net::IpAddr>().ok()?.to_string(),
1050            #[cfg(feature = "net")]
1051            ScalarType::Ipv4Addr => value.get::<core::net::Ipv4Addr>().ok()?.to_string(),
1052            #[cfg(feature = "net")]
1053            ScalarType::Ipv6Addr => value.get::<core::net::Ipv6Addr>().ok()?.to_string(),
1054            #[cfg(feature = "net")]
1055            ScalarType::SocketAddr => value.get::<core::net::SocketAddr>().ok()?.to_string(),
1056            _ => return None,
1057        };
1058        return Some(s);
1059    }
1060
1061    // Try Display for Def::Scalar types (SmolStr, etc.)
1062    if matches!(value.shape().def, Def::Scalar) && value.shape().vtable.has_display() {
1063        return Some(alloc::format!("{}", value));
1064    }
1065
1066    None
1067}