Skip to main content

facet_path/
lib.rs

1#![warn(missing_docs)]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![doc = include_str!("../README.md")]
4
5extern crate alloc;
6
7use alloc::string::String;
8use alloc::vec::Vec;
9use core::fmt::Write;
10
11use facet_core::{Def, Field, Shape, Type, UserType};
12
13pub mod access;
14pub use access::PathAccessError;
15
16pub mod walk;
17pub use walk::{ShapeVisitor, VisitDecision, WalkStatus, walk_shape};
18
19/// A single step in a path through a type structure.
20///
21/// Each step records an index that can be used to navigate
22/// back through a [`Shape`] to reconstruct field names and types.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub enum PathStep {
25    /// Navigate to a struct field by index
26    Field(u32),
27    /// Navigate to a list/array element by index
28    Index(u32),
29    /// Navigate to an enum variant by index
30    Variant(u32),
31    /// Navigate into a map key at a specific entry index.
32    /// The entry index distinguishes paths for different map keys' inner frames.
33    MapKey(u32),
34    /// Navigate into a map value at a specific entry index.
35    /// The entry index distinguishes paths for different map values' inner frames.
36    MapValue(u32),
37    /// Navigate into `Some` of an Option
38    OptionSome,
39    /// Navigate through a pointer/reference
40    Deref,
41    /// Navigate into a transparent inner type (e.g., `NonZero<T>` -> T)
42    Inner,
43    /// Navigate into a proxy type (e.g., `Inner` with `#[facet(proxy = InnerProxy)]`)
44    ///
45    /// This step distinguishes a proxy frame from its parent in the deferred
46    /// processing path, so both can be stored without path collisions.
47    Proxy,
48}
49
50/// A path through a type structure, recorded as a series of steps.
51///
52/// This is a lightweight representation that only stores indices.
53/// The actual field names and type information can be reconstructed
54/// by replaying these steps against the original [`Shape`].
55#[derive(Debug, Clone)]
56pub struct Path {
57    /// The root [`Shape`] from which this path originates.
58    pub shape: &'static Shape,
59
60    /// The sequence of [`PathStep`]s representing navigation through the type structure.
61    pub steps: Vec<PathStep>,
62}
63
64impl PartialEq for Path {
65    fn eq(&self, other: &Self) -> bool {
66        // Compare shapes by pointer address (they're static references)
67        core::ptr::eq(self.shape, other.shape) && self.steps == other.steps
68    }
69}
70
71impl Eq for Path {}
72
73impl PartialOrd for Path {
74    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
75        Some(self.cmp(other))
76    }
77}
78
79impl Ord for Path {
80    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
81        // Compare shapes by pointer address first, then by steps
82        let shape_cmp =
83            (self.shape as *const Shape as usize).cmp(&(other.shape as *const Shape as usize));
84        if shape_cmp != core::cmp::Ordering::Equal {
85            return shape_cmp;
86        }
87        self.steps.cmp(&other.steps)
88    }
89}
90
91impl core::hash::Hash for Path {
92    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
93        // Hash the shape pointer address
94        (self.shape as *const Shape as usize).hash(state);
95        self.steps.hash(state);
96    }
97}
98
99impl Path {
100    /// Create a new empty path.
101    pub const fn new(shape: &'static Shape) -> Self {
102        Self {
103            shape,
104            steps: Vec::new(),
105        }
106    }
107
108    /// Create a path with pre-allocated capacity.
109    pub fn with_capacity(shape: &'static Shape, capacity: usize) -> Self {
110        Self {
111            shape,
112            steps: Vec::with_capacity(capacity),
113        }
114    }
115
116    /// Push a step onto the path.
117    pub fn push(&mut self, step: PathStep) {
118        self.steps.push(step);
119    }
120
121    /// Pop the last step from the path.
122    pub fn pop(&mut self) -> Option<PathStep> {
123        self.steps.pop()
124    }
125
126    /// Get the steps in this path.
127    pub fn steps(&self) -> &[PathStep] {
128        &self.steps
129    }
130
131    /// Get the length of this path.
132    pub const fn len(&self) -> usize {
133        self.steps.len()
134    }
135
136    /// Check if this path is empty.
137    pub const fn is_empty(&self) -> bool {
138        self.steps.is_empty()
139    }
140
141    /// Format this path as a human-readable string using the stored root shape.
142    ///
143    /// Returns a path like `outer.inner.items[3].name`.
144    pub fn format(&self) -> String {
145        self.format_with_shape(self.shape)
146    }
147
148    /// Format this path as a human-readable string by walking the given shape.
149    ///
150    /// Returns a path like `outer.inner.items[3].name`.
151    pub fn format_with_shape(&self, shape: &'static Shape) -> String {
152        let mut result = String::new();
153        let mut current_shape = shape;
154        let mut current_variant_idx: Option<usize> = None;
155
156        for step in &self.steps {
157            match step {
158                PathStep::Field(idx) => {
159                    let idx = *idx as usize;
160                    if let Some(field_name) =
161                        get_field_name_with_variant(current_shape, idx, current_variant_idx)
162                    {
163                        if !result.is_empty() {
164                            result.push('.');
165                        }
166                        result.push_str(field_name);
167                    }
168                    if let Some(field_shape) =
169                        get_field_shape_with_variant(current_shape, idx, current_variant_idx)
170                    {
171                        current_shape = field_shape;
172                    }
173                    current_variant_idx = None;
174                }
175                PathStep::Index(idx) => {
176                    write!(result, "[{}]", idx).unwrap();
177                    if let Some(elem_shape) = get_element_shape(current_shape) {
178                        current_shape = elem_shape;
179                    }
180                    current_variant_idx = None;
181                }
182                PathStep::Variant(idx) => {
183                    let idx = *idx as usize;
184                    if let Some(variant_name) = get_variant_name(current_shape, idx) {
185                        result.push_str("::");
186                        result.push_str(variant_name);
187                    }
188                    // Don't advance current_shape — the next Field step will
189                    // use the variant index to look up fields within the enum.
190                    current_variant_idx = Some(idx);
191                }
192                PathStep::MapKey(idx) => {
193                    write!(result, "[key#{}]", idx).unwrap();
194                    if let Some(key_shape) = get_map_key_shape(current_shape) {
195                        current_shape = key_shape;
196                    }
197                    current_variant_idx = None;
198                }
199                PathStep::MapValue(idx) => {
200                    write!(result, "[value#{}]", idx).unwrap();
201                    if let Some(value_shape) = get_map_value_shape(current_shape) {
202                        current_shape = value_shape;
203                    }
204                    current_variant_idx = None;
205                }
206                PathStep::OptionSome => {
207                    result.push_str("::Some");
208                    if let Some(inner_shape) = get_option_inner_shape(current_shape) {
209                        current_shape = inner_shape;
210                    }
211                    current_variant_idx = None;
212                }
213                PathStep::Deref => {
214                    if let Some(inner_shape) = get_pointer_inner_shape(current_shape) {
215                        current_shape = inner_shape;
216                    }
217                    current_variant_idx = None;
218                }
219                PathStep::Inner => {
220                    if let Some(inner_shape) = get_inner_shape(current_shape) {
221                        current_shape = inner_shape;
222                    }
223                    current_variant_idx = None;
224                }
225                PathStep::Proxy => {
226                    if let Some(proxy_def) = current_shape.effective_proxy(None) {
227                        current_shape = proxy_def.shape;
228                    }
229                    current_variant_idx = None;
230                }
231            }
232        }
233
234        if result.is_empty() {
235            result.push_str("<root>");
236        }
237
238        result
239    }
240
241    /// Resolve the field at the end of this path, if the path ends at a struct field.
242    ///
243    /// This navigates through the given shape following each step in the path,
244    /// and returns the [`Field`] if the final step is a `PathStep::Field`.
245    ///
246    /// This is useful for accessing field metadata like attributes when handling
247    /// errors that occur at a specific field location.
248    ///
249    /// # Returns
250    ///
251    /// - `Some(&Field)` if the path ends at a struct field
252    /// - `None` if the path is empty, doesn't end at a field, or navigation fails
253    pub fn resolve_leaf_field(&self, shape: &'static Shape) -> Option<&'static Field> {
254        if self.steps.is_empty() {
255            return None;
256        }
257
258        let mut current_shape = shape;
259        let mut current_variant_idx: Option<usize> = None;
260
261        // Navigate through all steps except the last one
262        for step in &self.steps[..self.steps.len() - 1] {
263            match step {
264                PathStep::Field(idx) => {
265                    let idx = *idx as usize;
266                    current_shape =
267                        get_field_shape_with_variant(current_shape, idx, current_variant_idx)?;
268                    current_variant_idx = None;
269                }
270                PathStep::Index(_) => {
271                    current_shape = get_element_shape(current_shape)?;
272                    current_variant_idx = None;
273                }
274                PathStep::Variant(idx) => {
275                    // Remember the variant for the next field lookup
276                    current_variant_idx = Some(*idx as usize);
277                }
278                PathStep::MapKey(_) => {
279                    current_shape = get_map_key_shape(current_shape)?;
280                    current_variant_idx = None;
281                }
282                PathStep::MapValue(_) => {
283                    current_shape = get_map_value_shape(current_shape)?;
284                    current_variant_idx = None;
285                }
286                PathStep::OptionSome => {
287                    current_shape = get_option_inner_shape(current_shape)?;
288                    current_variant_idx = None;
289                }
290                PathStep::Deref => {
291                    current_shape = get_pointer_inner_shape(current_shape)?;
292                    current_variant_idx = None;
293                }
294                PathStep::Inner => {
295                    current_shape = get_inner_shape(current_shape)?;
296                    current_variant_idx = None;
297                }
298                PathStep::Proxy => {
299                    let proxy_def = current_shape.effective_proxy(None)?;
300                    current_shape = proxy_def.shape;
301                    current_variant_idx = None;
302                }
303            }
304        }
305
306        // Check if the last step is a field
307        if let Some(PathStep::Field(idx)) = self.steps.last() {
308            let idx = *idx as usize;
309            return get_field_with_variant(current_shape, idx, current_variant_idx);
310        }
311
312        None
313    }
314}
315
316impl core::fmt::Display for Path {
317    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
318        f.write_str(&self.format())
319    }
320}
321
322/// Get the field at the given index, handling both structs and enum variants.
323fn get_field_with_variant(
324    shape: &Shape,
325    idx: usize,
326    variant_idx: Option<usize>,
327) -> Option<&'static Field> {
328    match shape.ty {
329        Type::User(UserType::Struct(sd)) => sd.fields.get(idx),
330        Type::User(UserType::Enum(ed)) => {
331            let variant_idx = variant_idx?;
332            let variant = ed.variants.get(variant_idx)?;
333            variant.data.fields.get(idx)
334        }
335        _ => None,
336    }
337}
338
339/// Get the shape of a field at the given index, handling both structs and enum variants.
340fn get_field_shape_with_variant(
341    shape: &Shape,
342    idx: usize,
343    variant_idx: Option<usize>,
344) -> Option<&'static Shape> {
345    get_field_with_variant(shape, idx, variant_idx).map(|f| f.shape())
346}
347
348/// Get the name of a field at the given index, handling both structs and enum variants.
349fn get_field_name_with_variant(
350    shape: &Shape,
351    idx: usize,
352    variant_idx: Option<usize>,
353) -> Option<&'static str> {
354    get_field_with_variant(shape, idx, variant_idx).map(|f| f.name)
355}
356
357/// Get the element shape for a list/array.
358const fn get_element_shape(shape: &Shape) -> Option<&'static Shape> {
359    match shape.def {
360        Def::List(ld) => Some(ld.t()),
361        Def::Array(ad) => Some(ad.t()),
362        Def::Slice(sd) => Some(sd.t()),
363        _ => None,
364    }
365}
366
367/// Get the name of a variant at the given index.
368fn get_variant_name(shape: &Shape, idx: usize) -> Option<&'static str> {
369    match shape.ty {
370        Type::User(UserType::Enum(ed)) => ed.variants.get(idx).map(|v| v.name),
371        _ => None,
372    }
373}
374
375/// Get the key shape for a map.
376const fn get_map_key_shape(shape: &Shape) -> Option<&'static Shape> {
377    match shape.def {
378        Def::Map(md) => Some(md.k()),
379        _ => None,
380    }
381}
382
383/// Get the value shape for a map.
384const fn get_map_value_shape(shape: &Shape) -> Option<&'static Shape> {
385    match shape.def {
386        Def::Map(md) => Some(md.v()),
387        _ => None,
388    }
389}
390
391/// Get the inner shape for an Option.
392const fn get_option_inner_shape(shape: &Shape) -> Option<&'static Shape> {
393    match shape.def {
394        Def::Option(od) => Some(od.t()),
395        _ => None,
396    }
397}
398
399/// Get the inner shape for a pointer.
400const fn get_pointer_inner_shape(shape: &Shape) -> Option<&'static Shape> {
401    match shape.def {
402        Def::Pointer(pd) => pd.pointee(),
403        _ => None,
404    }
405}
406
407/// Get the inner shape for a transparent type (e.g., `NonZero<T>`).
408const fn get_inner_shape(shape: &Shape) -> Option<&'static Shape> {
409    shape.inner
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415
416    #[test]
417    fn test_path_step_size() {
418        // PathStep should be 8 bytes (discriminant + u32, aligned)
419        assert_eq!(core::mem::size_of::<PathStep>(), 8);
420    }
421}