Skip to main content

facet_dessert/
lib.rs

1//! Sweet helpers for facet deserialization.
2//!
3//! This crate provides common setter functions for handling string, bytes, and scalar values
4//! when deserializing into facet types. It's used by both `facet-format` and `facet-dom`.
5
6extern crate alloc;
7
8use std::borrow::Cow;
9
10use facet_core::{Def, KnownPointer, Type, UserType};
11use facet_reflect::{Partial, ReflectError, Span};
12
13/// Result of checking if a pointer type needs special handling.
14pub enum PointerAction {
15    /// Pointer to str (`Cow<str>`, `&str`, `Arc<str>`, `Box<str>`, `Rc<str>`) - should be handled as a scalar/string.
16    /// `set_string_value` already handles these via `begin_smart_ptr` internally.
17    HandleAsScalar,
18    /// Smart pointer with slice builder (`Arc<[T]>`, `Box<[T]>`) - deserialize as list, then end().
19    SliceBuilder,
20    /// Smart pointer with sized pointee (`Arc<T>`, `Box<T>`) - deserialize inner, then end().
21    SizedPointee,
22}
23
24/// Prepare a smart pointer for deserialization.
25///
26/// This handles the common logic of:
27/// 1. Detecting string pointers (`Cow<str>`, `&str`, `Arc<str>`, `Box<str>`, `Rc<str>`) which should be handled as scalars
28/// 2. Calling `begin_smart_ptr()` for other pointer types
29/// 3. Detecting whether it's a slice builder or sized pointee
30///
31/// Returns the prepared `Partial` and an action indicating what the caller should do next.
32///
33/// # Usage
34/// ```ignore
35/// match begin_pointer(wip)? {
36///     (wip, PointerAction::HandleAsScalar) => {
37///         // Handle as scalar/string - set_string_value handles all str pointers
38///         deserialize_scalar(wip)
39///     }
40///     (wip, PointerAction::SliceBuilder) => {
41///         // Arc<[T]>, Box<[T]>, etc - deserialize list items
42///         let wip = deserialize_list(wip)?;
43///         wip.end()
44///     }
45///     (wip, PointerAction::SizedPointee) => {
46///         // Arc<T>, Box<T> - deserialize inner
47///         let wip = deserialize_into(wip)?;
48///         wip.end()
49///     }
50/// }
51/// ```
52pub fn begin_pointer<'input, const BORROW: bool>(
53    mut wip: Partial<'input, BORROW>,
54) -> Result<(Partial<'input, BORROW>, PointerAction), DessertError> {
55    let shape = wip.shape();
56    let ptr_def = match &shape.def {
57        Def::Pointer(ptr_def) => ptr_def,
58        _ => {
59            return Err(DessertError::Reflect {
60                error: ReflectError::OperationFailed {
61                    shape,
62                    operation: "begin_pointer requires a pointer type",
63                },
64                span: None,
65            });
66        }
67    };
68
69    // All string pointers (Cow<str>, &str, Arc<str>, Box<str>, Rc<str>) - handle as scalar
70    // set_string_value handles begin_smart_ptr internally for Arc/Box/Rc
71    if ptr_def
72        .pointee()
73        .is_some_and(|p| p.type_identifier == "str")
74    {
75        return Ok((wip, PointerAction::HandleAsScalar));
76    }
77
78    // Regular smart pointer (Box, Arc, Rc) with non-str pointee
79    wip = wip.begin_smart_ptr()?;
80
81    // Check if begin_smart_ptr set up a slice builder (for Arc<[T]>, Rc<[T]>, Box<[T]>)
82    let action = if wip.is_building_smart_ptr_slice() {
83        PointerAction::SliceBuilder
84    } else {
85        PointerAction::SizedPointee
86    };
87
88    Ok((wip, action))
89}
90
91/// Error type for dessert operations.
92#[derive(Debug)]
93pub enum DessertError {
94    /// A reflection error occurred.
95    Reflect {
96        /// The underlying reflection error.
97        error: ReflectError,
98        /// Optional span where the error occurred.
99        span: Option<Span>,
100    },
101    /// Cannot borrow from input.
102    CannotBorrow {
103        /// Message explaining why borrowing failed.
104        message: Cow<'static, str>,
105    },
106}
107
108impl std::fmt::Display for DessertError {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            DessertError::Reflect { error, span } => {
112                if let Some(span) = span {
113                    write!(f, "{} at {:?}", error, span)
114                } else {
115                    write!(f, "{}", error)
116                }
117            }
118            DessertError::CannotBorrow { message } => write!(f, "{}", message),
119        }
120    }
121}
122
123impl std::error::Error for DessertError {
124    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
125        match self {
126            DessertError::Reflect { error, .. } => Some(error),
127            DessertError::CannotBorrow { .. } => None,
128        }
129    }
130}
131
132impl From<ReflectError> for DessertError {
133    fn from(error: ReflectError) -> Self {
134        DessertError::Reflect { error, span: None }
135    }
136}
137
138/// Set a string value, handling `Option<T>`, parseable types, enums, and string types.
139///
140/// This function handles:
141/// 1. `Option<T>` - unwraps to Some and recurses
142/// 2. Types with `parse_from_str` (numbers, bools, etc.)
143/// 3. Enums - selects variant by name (externally tagged) or by discriminant (numeric)
144/// 4. Transparent structs (newtypes) - recurses into inner type
145/// 5. String types (`&str`, `Cow<str>`, `String`)
146pub fn set_string_value<'input, const BORROW: bool>(
147    mut wip: Partial<'input, BORROW>,
148    s: Cow<'input, str>,
149    span: Option<Span>,
150) -> Result<Partial<'input, BORROW>, DessertError> {
151    let shape = wip.shape();
152
153    if matches!(&shape.def, Def::Option(_)) {
154        wip = wip.begin_some()?;
155        wip = set_string_value(wip, s, span)?;
156        wip = wip.end()?;
157        return Ok(wip);
158    }
159
160    if shape.vtable.has_parse() {
161        wip = wip.parse_from_str(s.as_ref())?;
162        return Ok(wip);
163    }
164
165    // Handle enums by selecting a variant by name (or by discriminant for numeric enums)
166    if let Type::User(UserType::Enum(enum_def)) = &shape.ty {
167        // For numeric enums (e.g., #[repr(u8)]), try parsing the string as a discriminant
168        if shape.is_numeric()
169            && let Ok(discriminant) = s.parse::<i64>()
170        {
171            wip = wip.select_variant(discriminant)?;
172            return Ok(wip);
173        }
174        // Fall through to try name-based lookup
175
176        // Try to find a variant by effective name (respects #[facet(rename = "...")])
177        if let Some((_, variant)) = enum_def
178            .variants
179            .iter()
180            .enumerate()
181            .find(|(_, v)| v.effective_name() == s.as_ref())
182        {
183            wip = wip.select_variant_named(variant.effective_name())?;
184            return Ok(wip);
185        }
186
187        // No variant found - return an error
188        return Err(DessertError::Reflect {
189            error: ReflectError::OperationFailed {
190                shape,
191                operation: "no matching enum variant found for string value",
192            },
193            span,
194        });
195    }
196
197    // Handle transparent structs (newtypes) by unwrapping to the inner type
198    if shape.is_transparent() {
199        wip = wip.begin_nth_field(0)?;
200        wip = set_string_value(wip, s, span)?;
201        wip = wip.end()?;
202        return Ok(wip);
203    }
204
205    set_string_value_inner(wip, s, span)
206}
207
208fn set_string_value_inner<'input, const BORROW: bool>(
209    mut wip: Partial<'input, BORROW>,
210    s: Cow<'input, str>,
211    span: Option<Span>,
212) -> Result<Partial<'input, BORROW>, DessertError> {
213    let shape = wip.shape();
214
215    let reflect_err = |e: ReflectError| DessertError::Reflect { error: e, span };
216
217    if let Def::Pointer(ptr_def) = shape.def
218        && matches!(ptr_def.known, Some(KnownPointer::SharedReference))
219        && ptr_def
220            .pointee()
221            .is_some_and(|p| p.type_identifier == "str")
222    {
223        if !BORROW {
224            return Err(DessertError::CannotBorrow {
225                message: "cannot deserialize into &str when borrowing is disabled - use String or Cow<str> instead".into(),
226            });
227        }
228        match s {
229            Cow::Borrowed(borrowed) => {
230                wip = wip.set(borrowed).map_err(&reflect_err)?;
231                return Ok(wip);
232            }
233            Cow::Owned(_) => {
234                return Err(DessertError::CannotBorrow {
235                    message: "cannot borrow &str from string containing escape sequences - use String or Cow<str> instead".into(),
236                });
237            }
238        }
239    }
240
241    if let Def::Pointer(ptr_def) = shape.def
242        && matches!(ptr_def.known, Some(KnownPointer::Cow))
243        && ptr_def
244            .pointee()
245            .is_some_and(|p| p.type_identifier == "str")
246    {
247        wip = wip.set(s).map_err(&reflect_err)?;
248        return Ok(wip);
249    }
250
251    // Arc<str>, Box<str>, Rc<str> - use begin_smart_ptr + set(String) + end()
252    if let Def::Pointer(ptr_def) = shape.def
253        && matches!(
254            ptr_def.known,
255            Some(KnownPointer::Arc | KnownPointer::Box | KnownPointer::Rc)
256        )
257        && ptr_def
258            .pointee()
259            .is_some_and(|p| p.type_identifier == "str")
260    {
261        wip = wip.begin_smart_ptr().map_err(&reflect_err)?;
262        wip = wip.set(s.into_owned()).map_err(&reflect_err)?;
263        wip = wip.end().map_err(&reflect_err)?;
264        return Ok(wip);
265    }
266
267    wip = wip.set(s.into_owned()).map_err(&reflect_err)?;
268    Ok(wip)
269}
270
271/// Set a bytes value with proper handling for borrowed vs owned data.
272///
273/// This handles `&[u8]`, `Cow<[u8]>`, and `Vec<u8>` appropriately based on
274/// whether borrowing is enabled and whether the data is borrowed or owned.
275pub fn set_bytes_value<'input, const BORROW: bool>(
276    mut wip: Partial<'input, BORROW>,
277    b: Cow<'input, [u8]>,
278    span: Option<Span>,
279) -> Result<Partial<'input, BORROW>, DessertError> {
280    let shape = wip.shape();
281
282    let reflect_err = |e: ReflectError| DessertError::Reflect { error: e, span };
283
284    let is_byte_slice = |pointee: &facet_core::Shape| matches!(pointee.def, Def::Slice(slice_def) if slice_def.t.type_identifier == "u8");
285
286    if let Def::Pointer(ptr_def) = shape.def
287        && matches!(ptr_def.known, Some(KnownPointer::SharedReference))
288        && ptr_def.pointee().is_some_and(is_byte_slice)
289    {
290        if !BORROW {
291            return Err(DessertError::CannotBorrow {
292                message: "cannot deserialize into &[u8] when borrowing is disabled - use Vec<u8> or Cow<[u8]> instead".into(),
293            });
294        }
295        match b {
296            Cow::Borrowed(borrowed) => {
297                wip = wip.set(borrowed).map_err(&reflect_err)?;
298                return Ok(wip);
299            }
300            Cow::Owned(_) => {
301                return Err(DessertError::CannotBorrow {
302                    message:
303                        "cannot borrow &[u8] from owned bytes - use Vec<u8> or Cow<[u8]> instead"
304                            .into(),
305                });
306            }
307        }
308    }
309
310    if let Def::Pointer(ptr_def) = shape.def
311        && matches!(ptr_def.known, Some(KnownPointer::Cow))
312        && ptr_def.pointee().is_some_and(is_byte_slice)
313    {
314        wip = wip.set(b).map_err(&reflect_err)?;
315        return Ok(wip);
316    }
317
318    wip = wip.set(b.into_owned()).map_err(&reflect_err)?;
319    Ok(wip)
320}