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}