facet_dom/deserializer/
mod.rs

1//! Tree-based deserializer for DOM documents.
2
3use std::borrow::Cow;
4
5use facet_core::{Def, StructKind, Type, UserType};
6use facet_reflect::Partial;
7
8use crate::error::DomDeserializeError;
9use crate::naming::to_element_name;
10use crate::trace;
11use crate::{AttributeRecord, DomEvent, DomParser, DomParserExt};
12
13mod entrypoints;
14mod field_map;
15mod struct_deser;
16
17use struct_deser::StructDeserializer;
18
19/// Extension trait for chaining deserialization on `Partial`.
20pub(crate) trait PartialDeserializeExt<'de, const BORROW: bool, P: DomParser<'de>> {
21    /// Deserialize into this partial using the given deserializer.
22    fn deserialize_with(
23        self,
24        deserializer: &mut DomDeserializer<'de, BORROW, P>,
25    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>>;
26}
27
28impl<'de, const BORROW: bool, P: DomParser<'de>> PartialDeserializeExt<'de, BORROW, P>
29    for Partial<'de, BORROW>
30{
31    fn deserialize_with(
32        self,
33        deserializer: &mut DomDeserializer<'de, BORROW, P>,
34    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
35        deserializer.deserialize_into(self)
36    }
37}
38
39/// DOM deserializer.
40///
41/// The `BORROW` parameter controls whether strings can be borrowed from the input:
42/// - `BORROW = true`: Allows zero-copy deserialization of `&str` and `Cow<str>`
43/// - `BORROW = false`: All strings are owned, input doesn't need to outlive result
44pub struct DomDeserializer<'de, const BORROW: bool, P> {
45    parser: P,
46    _marker: std::marker::PhantomData<&'de ()>,
47}
48
49impl<'de, const BORROW: bool, P> DomDeserializer<'de, BORROW, P>
50where
51    P: DomParser<'de>,
52{
53    /// Deserialize a value into an existing Partial.
54    ///
55    /// # Parser State Contract
56    ///
57    /// **Entry:** The parser should be positioned such that the next event represents
58    /// the value to deserialize. For structs/enums, this means a `NodeStart` is next
59    /// (peeked but not consumed). For scalars within an element, the parser should be
60    /// inside the element (after `ChildrenStart`).
61    ///
62    /// **Exit:** The parser will have consumed all events related to this value,
63    /// including the closing `NodeEnd` for struct types.
64    pub fn deserialize_into(
65        &mut self,
66        mut wip: Partial<'de, BORROW>,
67    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
68        let shape = wip.shape();
69        #[cfg(any(test, feature = "tracing"))]
70        {
71            use owo_colors::OwoColorize;
72            let module_path = shape.module_path.unwrap_or("?");
73            let module = module_path.dimmed();
74            let name = shape.cyan();
75            trace!(into = %format_args!("{module}::{name}"));
76        }
77
78        // Check for RawMarkup BEFORE transparent wrapper handling
79        // (RawMarkup has inner=String but needs special raw capture handling)
80        if crate::raw_markup::is_raw_markup(shape) {
81            return self.deserialize_raw_markup(wip);
82        }
83
84        // Handle transparent wrappers (like NonZero, newtype structs with #[facet(transparent)])
85        // Collections (List/Map/Set/Array), Option, and Pointer have .inner for variance but shouldn't use this path
86        if shape.inner.is_some()
87            && !matches!(
88                &shape.def,
89                Def::List(_)
90                    | Def::Map(_)
91                    | Def::Set(_)
92                    | Def::Array(_)
93                    | Def::Option(_)
94                    | Def::Pointer(_)
95            )
96        {
97            wip = wip.begin_inner().map_err(DomDeserializeError::Reflect)?;
98            wip = self.deserialize_into(wip)?;
99            wip = wip.end().map_err(DomDeserializeError::Reflect)?;
100            return Ok(wip);
101        }
102
103        match &shape.ty {
104            Type::User(UserType::Struct(_)) => self.deserialize_struct(wip),
105            Type::User(UserType::Enum(_)) => self.deserialize_enum(wip),
106            _ => match &shape.def {
107                Def::Scalar => self.deserialize_scalar(wip),
108                Def::Pointer(_) => self.deserialize_pointer(wip),
109                Def::List(_) => self.deserialize_list(wip),
110                Def::Set(_) => self.deserialize_set(wip),
111                Def::Map(_) => self.deserialize_map(wip),
112                Def::Option(_) => self.deserialize_option(wip),
113                _ => Err(DomDeserializeError::Unsupported(format!(
114                    "unsupported type: {:?}",
115                    shape.ty
116                ))),
117            },
118        }
119    }
120
121    /// Deserialize a struct type.
122    ///
123    /// # Parser State Contract
124    ///
125    /// **Entry:** Parser is positioned before the struct's `NodeStart` (peeked, not consumed).
126    ///
127    /// **Exit:** Parser has consumed through the struct's closing `NodeEnd`.
128    fn deserialize_struct(
129        &mut self,
130        wip: Partial<'de, BORROW>,
131    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
132        let shape = wip.shape();
133        let struct_def = match &shape.ty {
134            Type::User(UserType::Struct(def)) => def,
135            _ => {
136                return Err(DomDeserializeError::Unsupported(
137                    "expected struct type".into(),
138                ));
139            }
140        };
141
142        // Compute expected element name from shape: rename > lowerCamelCase(type_identifier)
143        let expected_name = shape
144            .get_builtin_attr_value::<&str>("rename")
145            .map(Cow::Borrowed)
146            .unwrap_or_else(|| to_element_name(shape.type_identifier));
147
148        self.deserialize_struct_innards(wip, struct_def, expected_name)
149    }
150
151    /// Deserialize the innards of a struct-like thing (struct, tuple, or enum variant data).
152    ///
153    /// Delegates to `StructDeserializer` for the actual implementation.
154    fn deserialize_struct_innards(
155        &mut self,
156        wip: Partial<'de, BORROW>,
157        struct_def: &'static facet_core::StructType,
158        expected_name: Cow<'static, str>,
159    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
160        // Extract xml::ns_all attribute from the shape
161        let ns_all = wip
162            .shape()
163            .attributes
164            .iter()
165            .find(|attr| attr.ns == Some("xml") && attr.key == "ns_all")
166            .and_then(|attr| attr.get_as::<&str>().copied());
167
168        // Check if deny_unknown_fields is set
169        let deny_unknown_fields = wip.shape().has_deny_unknown_fields_attr();
170
171        StructDeserializer::new(self, struct_def, ns_all, expected_name, deny_unknown_fields)
172            .deserialize(wip)
173    }
174
175    /// Deserialize an enum type.
176    ///
177    /// # Parser State Contract
178    ///
179    /// **Entry:** Parser is positioned at either:
180    /// - A `NodeStart` event (element-based variant), or
181    /// - A `Text` event (text-based variant, e.g., for enums with a `#[xml::text]` variant)
182    ///
183    /// **Exit:** All events for this enum have been consumed:
184    /// - If entry was `NodeStart`: through the closing `NodeEnd`
185    /// - If entry was `Text`: just that text event
186    ///
187    /// # Variant Selection
188    ///
189    /// For `NodeStart`: The element tag name is matched against variant names (considering
190    /// `#[rename]` attributes). If no match, looks for a variant with `#[xml::custom_element]`.
191    ///
192    /// For `Text`: Looks for a variant with `#[xml::text]` attribute.
193    fn deserialize_enum(
194        &mut self,
195        mut wip: Partial<'de, BORROW>,
196    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
197        let event = self.parser.peek_event_or_eof("NodeStart or Text")?;
198
199        match event {
200            DomEvent::NodeStart { tag, .. } => {
201                let tag = tag.clone();
202                let enum_def = match &wip.shape().ty {
203                    Type::User(UserType::Enum(def)) => def,
204                    _ => {
205                        return Err(DomDeserializeError::Unsupported(
206                            "expected enum type".into(),
207                        ));
208                    }
209                };
210
211                // For untagged enums, the element tag is the enum's name (not a variant name)
212                // We need to select the first variant and deserialize the content into it
213                let is_untagged = wip.shape().is_untagged();
214
215                let variant_idx = if is_untagged {
216                    // For untagged enums, select the first (and typically only) variant
217                    // The element tag should match the enum's rename, not a variant name
218                    trace!(tag = %tag, "untagged enum - selecting first variant");
219                    0
220                } else {
221                    // For tagged enums, match the element tag against variant names.
222                    // Compute effective element name: use rename attribute if present,
223                    // otherwise convert to lowerCamelCase.
224                    enum_def
225                        .variants
226                        .iter()
227                        .position(|v| {
228                            let effective_name: Cow<'_, str> = if v.rename.is_some() {
229                                Cow::Borrowed(v.effective_name())
230                            } else {
231                                to_element_name(v.name)
232                            };
233                            effective_name == tag
234                        })
235                        .or_else(|| enum_def.variants.iter().position(|v| v.is_custom_element()))
236                        .ok_or_else(|| DomDeserializeError::UnknownElement {
237                            tag: tag.to_string(),
238                        })?
239                };
240
241                let variant = &enum_def.variants[variant_idx];
242                wip = wip.select_nth_variant(variant_idx)?;
243                trace!(variant_name = variant.name, variant_kind = ?variant.data.kind, is_untagged, "selected variant");
244
245                // Handle variant based on its kind
246                match variant.data.kind {
247                    StructKind::Unit => {
248                        // Unit variant: just consume the element
249                        self.parser.expect_node_start()?;
250                        // Skip to end of element
251                        let event = self.parser.peek_event_or_eof("ChildrenStart or NodeEnd")?;
252                        if matches!(event, DomEvent::ChildrenStart) {
253                            self.parser.expect_children_start()?;
254                            self.parser.expect_children_end()?;
255                        }
256                        self.parser.expect_node_end()?;
257                    }
258                    StructKind::TupleStruct => {
259                        // Newtype variant: deserialize the inner type
260                        // The variant data has one field (index 0)
261                        let inner_field = &variant.data.fields[0];
262                        let inner_shape = inner_field.shape();
263
264                        // Check if the inner type is a struct - if so, we need to deserialize
265                        // its fields from the same element that identified the variant
266                        if let Type::User(UserType::Struct(inner_struct_def)) = &inner_shape.ty {
267                            // For struct newtypes, use the variant's element name and deserialize
268                            // the struct's fields directly from this element
269                            let expected_name: Cow<'_, str> = if is_untagged {
270                                let shape = wip.shape();
271                                if let Some(renamed) =
272                                    shape.get_builtin_attr_value::<&str>("rename")
273                                {
274                                    Cow::Borrowed(renamed)
275                                } else {
276                                    to_element_name(shape.type_identifier)
277                                }
278                            } else if variant.rename.is_some() {
279                                Cow::Borrowed(variant.effective_name())
280                            } else {
281                                to_element_name(variant.name)
282                            };
283
284                            // Get ns_all from the inner struct's shape
285                            let ns_all = inner_shape
286                                .attributes
287                                .iter()
288                                .find(|attr| attr.ns == Some("xml") && attr.key == "ns_all")
289                                .and_then(|attr| attr.get_as::<&str>().copied());
290
291                            let deny_unknown_fields = inner_shape.has_deny_unknown_fields_attr();
292
293                            wip = wip.begin_nth_field(0)?;
294                            wip = StructDeserializer::new(
295                                self,
296                                inner_struct_def,
297                                ns_all,
298                                expected_name,
299                                deny_unknown_fields,
300                            )
301                            .deserialize(wip)?;
302                            wip = wip.end()?;
303                        } else {
304                            // For non-struct newtypes (e.g., Text(String)), deserialize normally
305                            wip = wip.begin_nth_field(0)?.deserialize_with(self)?.end()?;
306                        }
307                    }
308                    StructKind::Struct | StructKind::Tuple => {
309                        // Struct/tuple variant: deserialize using the variant's data as a StructType
310                        // For untagged enums, use the enum's rename as element name (already matched above)
311                        // For tagged enums, use variant rename if present, else lowerCamelCase(variant.name)
312                        let expected_name: Cow<'_, str> = if is_untagged {
313                            // For untagged, the element name is the enum's name/rename
314                            let shape = wip.shape();
315                            if let Some(renamed) = shape.get_builtin_attr_value::<&str>("rename") {
316                                Cow::Borrowed(renamed)
317                            } else {
318                                to_element_name(shape.type_identifier)
319                            }
320                        } else if variant.rename.is_some() {
321                            Cow::Borrowed(variant.effective_name())
322                        } else {
323                            to_element_name(variant.name)
324                        };
325                        wip = self.deserialize_struct_innards(wip, &variant.data, expected_name)?;
326                    }
327                }
328            }
329            DomEvent::Text(_) => {
330                let text = self.parser.expect_text()?;
331                wip = self.deserialize_text_into_enum(wip, text)?;
332            }
333            other => {
334                return Err(DomDeserializeError::TypeMismatch {
335                    expected: "NodeStart or Text",
336                    got: format!("{other:?}"),
337                });
338            }
339        }
340
341        Ok(wip)
342    }
343
344    /// Deserialize text content into an enum by selecting the `#[xml::text]` variant.
345    ///
346    /// # Parser State Contract
347    ///
348    /// **Entry:** The text has already been consumed from the parser (passed as argument).
349    ///
350    /// **Exit:** No parser state change (text was already consumed).
351    ///
352    /// # Fallback
353    ///
354    /// If `wip` is not actually an enum, falls back to `set_string_value`.
355    fn deserialize_text_into_enum(
356        &mut self,
357        mut wip: Partial<'de, BORROW>,
358        text: Cow<'de, str>,
359    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
360        let enum_def = match &wip.shape().ty {
361            Type::User(UserType::Enum(def)) => def,
362            _ => {
363                return self.set_string_value(wip, text);
364            }
365        };
366
367        let text_variant_idx = match enum_def.variants.iter().position(|v| v.is_text()) {
368            Some(idx) => idx,
369            None => {
370                // No text variant - either error (XML) or silently discard (HTML)
371                if self.parser.is_lenient() {
372                    return Ok(wip);
373                } else {
374                    return Err(DomDeserializeError::Unsupported(
375                        "enum has no Text variant for text content".into(),
376                    ));
377                }
378            }
379        };
380
381        let variant = &enum_def.variants[text_variant_idx];
382        wip = wip.select_nth_variant(text_variant_idx)?;
383
384        // Handle the variant based on its kind
385        match variant.data.kind {
386            StructKind::TupleStruct => {
387                // Newtype variant like Text(String) - navigate to field 0
388                wip = wip.begin_nth_field(0)?;
389                wip = self.set_string_value(wip, text)?;
390                wip = wip.end()?;
391            }
392            StructKind::Unit => {
393                // Unit variant - nothing to set (unusual for text variant but handle it)
394            }
395            _ => {
396                // For other kinds, try direct set (may fail)
397                wip = self.set_string_value(wip, text)?;
398            }
399        }
400
401        Ok(wip)
402    }
403
404    /// Deserialize RawMarkup by capturing raw source from the parser.
405    fn deserialize_raw_markup(
406        &mut self,
407        wip: Partial<'de, BORROW>,
408    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
409        // Must be at a NodeStart
410        let event = self.parser.peek_event_or_eof("NodeStart for RawMarkup")?;
411        if !matches!(event, DomEvent::NodeStart { .. }) {
412            return Err(DomDeserializeError::TypeMismatch {
413                expected: "NodeStart for RawMarkup",
414                got: format!("{event:?}"),
415            });
416        }
417
418        // Consume the NodeStart
419        self.parser
420            .next_event()
421            .map_err(DomDeserializeError::Parser)?;
422
423        // Try to capture raw - if not supported, fall back to error
424        let raw = self
425            .parser
426            .capture_raw_node()
427            .map_err(DomDeserializeError::Parser)?
428            .ok_or_else(|| {
429                DomDeserializeError::Unsupported("parser does not support raw capture".into())
430            })?;
431
432        // Set via the vtable's parse function
433        self.set_string_value(wip, raw)
434    }
435
436    /// Deserialize a scalar value (string, number, bool, etc.).
437    ///
438    /// # Parser State Contract
439    ///
440    /// **Entry:** Parser is positioned at either:
441    /// - A `Text` event (inline text content), or
442    /// - A `NodeStart` event (element wrapping the text content)
443    ///
444    /// **Exit:** All events for this scalar have been consumed:
445    /// - If entry was `Text`: just that text event
446    /// - If entry was `NodeStart`: through the closing `NodeEnd`
447    ///
448    /// # XML Data Model
449    ///
450    /// In XML, scalars can appear as:
451    /// - Attribute values (handled elsewhere)
452    /// - Text content: `<parent>text here</parent>`
453    /// - Element with text: `<field>value</field>` (element is consumed)
454    fn deserialize_scalar(
455        &mut self,
456        wip: Partial<'de, BORROW>,
457    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
458        trace!("deserialize_scalar called");
459        let event = self.parser.peek_event_or_eof("Text or NodeStart")?;
460        trace!(event = ?event, "peeked event in deserialize_scalar");
461        match event {
462            DomEvent::Text(_) => {
463                trace!("deserialize_scalar: matched Text arm");
464                let text = self.parser.expect_text()?;
465                // Use set_string_value_with_proxy for format-specific proxy support
466                self.set_string_value_with_proxy(wip, text)
467            }
468            DomEvent::NodeStart { .. } => {
469                trace!("deserialize_scalar: matched NodeStart arm");
470                let _tag = self.parser.expect_node_start()?;
471                trace!(tag = %_tag, "deserialize_scalar: consumed NodeStart");
472
473                loop {
474                    let event = self
475                        .parser
476                        .peek_event_or_eof("Attribute or ChildrenStart or NodeEnd")?;
477                    trace!(event = ?event, "deserialize_scalar: in attr loop");
478                    match event {
479                        DomEvent::Attribute { .. } => {
480                            let AttributeRecord {
481                                name: _name,
482                                value: _value,
483                                namespace: _namespace,
484                            } = self.parser.expect_attribute()?;
485                            trace!(name = %_name, "deserialize_scalar: consumed Attribute");
486                        }
487                        DomEvent::ChildrenStart => {
488                            self.parser.expect_children_start()?;
489                            trace!("deserialize_scalar: consumed ChildrenStart");
490                            break;
491                        }
492                        DomEvent::NodeEnd => {
493                            self.parser.expect_node_end()?;
494                            trace!("deserialize_scalar: void element, returning empty string");
495                            // Use set_string_value_with_proxy for format-specific proxy support
496                            return self.set_string_value_with_proxy(wip, Cow::Borrowed(""));
497                        }
498                        other => {
499                            trace!(other = ?other, "deserialize_scalar: unexpected event in attr loop");
500                            return Err(DomDeserializeError::TypeMismatch {
501                                expected: "Attribute or ChildrenStart or NodeEnd",
502                                got: format!("{other:?}"),
503                            });
504                        }
505                    }
506                }
507
508                trace!("deserialize_scalar: starting text content loop");
509                let mut text_content = String::new();
510                loop {
511                    let event = self.parser.peek_event_or_eof("Text or ChildrenEnd")?;
512                    trace!(event = ?event, "deserialize_scalar: in text content loop");
513                    match event {
514                        DomEvent::Text(_) => {
515                            let text = self.parser.expect_text()?;
516                            trace!(text = %text, "deserialize_scalar: got text");
517                            text_content.push_str(&text);
518                        }
519                        DomEvent::ChildrenEnd => {
520                            trace!("deserialize_scalar: got ChildrenEnd, breaking text loop");
521                            break;
522                        }
523                        DomEvent::NodeStart { .. } => {
524                            trace!("deserialize_scalar: skipping nested NodeStart");
525                            self.parser
526                                .skip_node()
527                                .map_err(DomDeserializeError::Parser)?;
528                        }
529                        DomEvent::Comment(_) => {
530                            let _comment = self.parser.expect_comment()?;
531                        }
532                        other => {
533                            return Err(DomDeserializeError::TypeMismatch {
534                                expected: "Text or ChildrenEnd",
535                                got: format!("{other:?}"),
536                            });
537                        }
538                    }
539                }
540
541                trace!("deserialize_scalar: consuming ChildrenEnd");
542                self.parser.expect_children_end()?;
543                trace!("deserialize_scalar: consuming NodeEnd");
544                self.parser.expect_node_end()?;
545                trace!(text_content = %text_content, "deserialize_scalar: setting string value");
546
547                // Use set_string_value_with_proxy for format-specific proxy support
548                self.set_string_value_with_proxy(wip, Cow::Owned(text_content))
549            }
550            other => Err(DomDeserializeError::TypeMismatch {
551                expected: "Text or NodeStart",
552                got: format!("{other:?}"),
553            }),
554        }
555    }
556
557    /// Deserialize a list (Vec, slice, etc.) from repeated child elements.
558    ///
559    /// # Parser State Contract
560    ///
561    /// **Entry:** Parser is positioned inside an element, after `ChildrenStart`.
562    /// Child elements will be deserialized as list items.
563    ///
564    /// **Exit:** Parser is positioned at `ChildrenEnd` (peeked, not consumed).
565    /// The caller is responsible for consuming `ChildrenEnd` and `NodeEnd`.
566    ///
567    /// # Note
568    ///
569    /// This is used for "wrapped" list semantics where a parent element contains
570    /// the list items. For "flat" list semantics (items directly as siblings),
571    /// see the flat sequence handling in `deserialize_struct_innards`.
572    fn deserialize_list(
573        &mut self,
574        mut wip: Partial<'de, BORROW>,
575    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
576        wip = wip.init_list()?;
577
578        loop {
579            let event = self.parser.peek_event_or_eof("child or ChildrenEnd")?;
580            if matches!(event, DomEvent::ChildrenEnd) {
581                break;
582            }
583
584            wip = wip.begin_list_item()?.deserialize_with(self)?.end()?;
585        }
586
587        Ok(wip)
588    }
589
590    /// Deserialize a set type (HashSet, BTreeSet, etc.).
591    ///
592    /// Works the same as lists: each child element becomes a set item.
593    fn deserialize_set(
594        &mut self,
595        mut wip: Partial<'de, BORROW>,
596    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
597        wip = wip.init_set()?;
598
599        loop {
600            let event = self.parser.peek_event_or_eof("child or ChildrenEnd")?;
601            if matches!(event, DomEvent::ChildrenEnd) {
602                break;
603            }
604
605            wip = wip.begin_set_item()?.deserialize_with(self)?.end()?;
606        }
607
608        Ok(wip)
609    }
610
611    /// Deserialize a map type (HashMap, BTreeMap, etc.).
612    ///
613    /// In XML, maps use a **wrapped** model:
614    /// - The field name becomes a wrapper element
615    /// - Each child element becomes a map entry (tag = key, content = value)
616    ///
617    /// Example: `<data><alpha>1</alpha><beta>2</beta></data>` -> {"alpha": 1, "beta": 2}
618    ///
619    /// # Parser State Contract
620    ///
621    /// **Entry:** Parser is positioned at the wrapper element's `NodeStart`.
622    ///
623    /// **Exit:** Parser has consumed through the wrapper element's `NodeEnd`.
624    fn deserialize_map(
625        &mut self,
626        mut wip: Partial<'de, BORROW>,
627    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
628        // Consume the wrapper element's NodeStart
629        let event = self.parser.peek_event_or_eof("NodeStart for map wrapper")?;
630        match event {
631            DomEvent::NodeStart { .. } => {
632                trace!("map wrapper element");
633                let _ = self.parser.expect_node_start()?;
634            }
635            other => {
636                return Err(DomDeserializeError::TypeMismatch {
637                    expected: "NodeStart for map wrapper",
638                    got: format!("{other:?}"),
639                });
640            }
641        }
642
643        // Skip attributes on the wrapper element
644        loop {
645            let event = self
646                .parser
647                .peek_event_or_eof("Attribute or ChildrenStart or NodeEnd")?;
648            match event {
649                DomEvent::Attribute { .. } => {
650                    self.parser.expect_attribute()?;
651                }
652                DomEvent::ChildrenStart => {
653                    self.parser.expect_children_start()?;
654                    break;
655                }
656                DomEvent::NodeEnd => {
657                    // Empty map (void element)
658                    self.parser.expect_node_end()?;
659                    return Ok(wip.init_map()?);
660                }
661                other => {
662                    return Err(DomDeserializeError::TypeMismatch {
663                        expected: "Attribute or ChildrenStart or NodeEnd",
664                        got: format!("{other:?}"),
665                    });
666                }
667            }
668        }
669
670        wip = wip.init_map()?;
671
672        // Now parse map entries from children
673        loop {
674            let event = self.parser.peek_event_or_eof("child or ChildrenEnd")?;
675            match event {
676                DomEvent::ChildrenEnd => break,
677                DomEvent::NodeStart { tag, .. } => {
678                    let key = tag.clone();
679                    trace!(key = %key, "map entry");
680
681                    // Set the key (element name)
682                    wip = wip.begin_key()?;
683                    wip = self.set_string_value(wip, key)?;
684                    wip = wip.end()?;
685
686                    // Deserialize the value (element content)
687                    wip = wip.begin_value()?.deserialize_with(self)?.end()?;
688                }
689                DomEvent::Text(_) | DomEvent::Comment(_) => {
690                    // Skip whitespace text and comments between map entries
691                    if matches!(event, DomEvent::Text(_)) {
692                        self.parser.expect_text()?;
693                    } else {
694                        self.parser.expect_comment()?;
695                    }
696                }
697                _ => {
698                    return Err(DomDeserializeError::TypeMismatch {
699                        expected: "map entry element",
700                        got: format!("{event:?}"),
701                    });
702                }
703            }
704        }
705
706        // Consume wrapper's ChildrenEnd and NodeEnd
707        self.parser.expect_children_end()?;
708        self.parser.expect_node_end()?;
709
710        Ok(wip)
711    }
712
713    /// Deserialize an Option type.
714    ///
715    /// # Parser State Contract
716    ///
717    /// **Entry:** Parser is positioned where the optional value would be.
718    ///
719    /// **Exit:** If value was present, all events for the value have been consumed.
720    /// If value was absent, no events consumed.
721    ///
722    /// # None Detection
723    ///
724    /// The option is `None` if the next event is `ChildrenEnd` or `NodeEnd`
725    /// (indicating no content). Otherwise, the inner value is deserialized.
726    fn deserialize_option(
727        &mut self,
728        mut wip: Partial<'de, BORROW>,
729    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
730        let event = self.parser.peek_event_or_eof("value")?;
731        if matches!(event, DomEvent::ChildrenEnd | DomEvent::NodeEnd) {
732            wip = wip.set_default()?;
733        } else {
734            wip = wip.begin_some()?.deserialize_with(self)?.end()?;
735        }
736        Ok(wip)
737    }
738
739    /// Deserialize a pointer type (Box, Arc, Rc, etc.).
740    ///
741    /// # Parser State Contract
742    ///
743    /// **Entry:** Parser is positioned at the value that the pointer will wrap.
744    ///
745    /// **Exit:** All events for the inner value have been consumed.
746    ///
747    /// # Pointer Actions
748    ///
749    /// Uses `facet_dessert::begin_pointer` to determine how to handle the pointer:
750    /// - `HandleAsScalar`: Treat as scalar (e.g., `Box<str>`)
751    /// - `SliceBuilder`: Build a slice (e.g., `Arc<[T]>`)
752    /// - `SizedPointee`: Regular pointer to sized type
753    fn deserialize_pointer(
754        &mut self,
755        wip: Partial<'de, BORROW>,
756    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
757        use facet_dessert::{PointerAction, begin_pointer};
758
759        let (wip, action) = begin_pointer(wip)?;
760
761        match action {
762            PointerAction::HandleAsScalar => self.deserialize_scalar(wip),
763            PointerAction::SliceBuilder => Ok(self.deserialize_list(wip)?.end()?),
764            PointerAction::SizedPointee => Ok(wip.deserialize_with(self)?.end()?),
765        }
766    }
767
768    /// Set a string value on the current partial, parsing it to the appropriate type.
769    ///
770    /// # Parser State Contract
771    ///
772    /// **Entry/Exit:** No parser state change. The string value is passed as an argument.
773    ///
774    /// # Type Handling
775    ///
776    /// Delegates to `facet_dessert::set_string_value` which handles parsing the string
777    /// into the appropriate scalar type (String, &str, integers, floats, bools, etc.).
778    pub(crate) fn set_string_value(
779        &mut self,
780        wip: Partial<'de, BORROW>,
781        value: Cow<'de, str>,
782    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
783        Ok(facet_dessert::set_string_value(
784            wip,
785            value,
786            self.parser.current_span(),
787        )?)
788    }
789
790    /// Set a string value, handling field-level proxy conversion if present.
791    ///
792    /// If the field has a proxy attribute (e.g., `#[facet(proxy = PointsProxy)]`),
793    /// this will:
794    /// 1. Begin custom deserialization (push a frame for the proxy type)
795    /// 2. Set the string value into the proxy type
796    /// 3. End the frame (which converts proxy -> target via TryFrom)
797    ///
798    /// If no proxy is present, it just calls `set_string_value` directly.
799    ///
800    /// This method supports format-specific proxies: if the parser returns a format
801    /// namespace (e.g., "xml"), fields with `#[facet(xml::proxy = ...)]` will use
802    /// that proxy instead of the format-agnostic one.
803    pub(crate) fn set_string_value_with_proxy(
804        &mut self,
805        mut wip: Partial<'de, BORROW>,
806        value: Cow<'de, str>,
807    ) -> Result<Partial<'de, BORROW>, DomDeserializeError<P::Error>> {
808        // Check if the field has a proxy (format-specific or format-agnostic)
809        let format_ns = self.parser.format_namespace();
810        let field_proxy = wip
811            .parent_field()
812            .and_then(|f| f.effective_proxy(format_ns));
813
814        if field_proxy.is_some() {
815            // Use custom deserialization through the field-level proxy
816            // The format-aware version will select the right proxy
817            wip = wip.begin_custom_deserialization_with_format(format_ns)?;
818            wip = self.set_string_value(wip, value)?;
819            wip = wip.end()?;
820            Ok(wip)
821        } else if wip.shape().effective_proxy(format_ns).is_some() {
822            // The target shape has a container-level proxy
823            // Use begin_custom_deserialization_from_shape_with_format
824            let (new_wip, _) =
825                wip.begin_custom_deserialization_from_shape_with_format(format_ns)?;
826            wip = new_wip;
827            wip = self.set_string_value(wip, value)?;
828            wip = wip.end()?;
829            Ok(wip)
830        } else {
831            self.set_string_value(wip, value)
832        }
833    }
834}