facet_path/
lib.rs

1#![warn(missing_docs)]
2#![cfg_attr(not(feature = "std"), no_std)]
3//! See README.md for documentation.
4
5//! Path tracking for navigating Facet type structures.
6
7extern crate alloc;
8
9use alloc::string::String;
10use alloc::vec::Vec;
11use core::fmt::Write;
12
13use facet_core::{Def, Field, Shape, StructKind, Type, UserType};
14
15/// A single step in a path through a type structure.
16///
17/// Each step records an index that can be used to navigate
18/// back through a [`Shape`] to reconstruct field names and types.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PathStep {
21    /// Navigate to a struct field by index
22    Field(u32),
23    /// Navigate to a list/array element by index
24    Index(u32),
25    /// Navigate to an enum variant by index
26    Variant(u32),
27    /// Navigate into a map key
28    MapKey,
29    /// Navigate into a map value
30    MapValue,
31    /// Navigate into `Some` of an Option
32    OptionSome,
33    /// Navigate through a pointer/reference
34    Deref,
35}
36
37/// A path through a type structure, recorded as a series of steps.
38///
39/// This is a lightweight representation that only stores indices.
40/// The actual field names and type information can be reconstructed
41/// by replaying these steps against the original [`Shape`].
42#[derive(Debug, Clone, Default)]
43pub struct Path {
44    steps: Vec<PathStep>,
45}
46
47impl Path {
48    /// Create a new empty path.
49    pub const fn new() -> Self {
50        Self { steps: Vec::new() }
51    }
52
53    /// Create a path with pre-allocated capacity.
54    pub fn with_capacity(capacity: usize) -> Self {
55        Self {
56            steps: Vec::with_capacity(capacity),
57        }
58    }
59
60    /// Push a step onto the path.
61    pub fn push(&mut self, step: PathStep) {
62        self.steps.push(step);
63    }
64
65    /// Pop the last step from the path.
66    pub fn pop(&mut self) -> Option<PathStep> {
67        self.steps.pop()
68    }
69
70    /// Get the steps in this path.
71    pub fn steps(&self) -> &[PathStep] {
72        &self.steps
73    }
74
75    /// Get the length of this path.
76    pub fn len(&self) -> usize {
77        self.steps.len()
78    }
79
80    /// Check if this path is empty.
81    pub fn is_empty(&self) -> bool {
82        self.steps.is_empty()
83    }
84
85    /// Format this path as a human-readable string by walking the given shape.
86    ///
87    /// Returns a path like `outer.inner.items[3].name`.
88    pub fn format_with_shape(&self, shape: &'static Shape) -> String {
89        let mut result = String::new();
90        let mut current_shape = shape;
91
92        for step in &self.steps {
93            match step {
94                PathStep::Field(idx) => {
95                    let idx = *idx as usize;
96                    if let Some(field_name) = get_field_name(current_shape, idx) {
97                        if !result.is_empty() {
98                            result.push('.');
99                        }
100                        result.push_str(field_name);
101                        if let Some(field_shape) = get_field_shape(current_shape, idx) {
102                            current_shape = field_shape;
103                        }
104                    }
105                }
106                PathStep::Index(idx) => {
107                    write!(result, "[{}]", idx).unwrap();
108                    if let Some(elem_shape) = get_element_shape(current_shape) {
109                        current_shape = elem_shape;
110                    }
111                }
112                PathStep::Variant(idx) => {
113                    let idx = *idx as usize;
114                    if let Some(variant_name) = get_variant_name(current_shape, idx) {
115                        result.push_str("::");
116                        result.push_str(variant_name);
117                        if let Some(variant_shape) = get_variant_shape(current_shape, idx) {
118                            current_shape = variant_shape;
119                        }
120                    }
121                }
122                PathStep::MapKey => {
123                    result.push_str("[key]");
124                    if let Some(key_shape) = get_map_key_shape(current_shape) {
125                        current_shape = key_shape;
126                    }
127                }
128                PathStep::MapValue => {
129                    result.push_str("[value]");
130                    if let Some(value_shape) = get_map_value_shape(current_shape) {
131                        current_shape = value_shape;
132                    }
133                }
134                PathStep::OptionSome => {
135                    if let Some(inner_shape) = get_option_inner_shape(current_shape) {
136                        current_shape = inner_shape;
137                    }
138                }
139                PathStep::Deref => {
140                    if let Some(inner_shape) = get_pointer_inner_shape(current_shape) {
141                        current_shape = inner_shape;
142                    }
143                }
144            }
145        }
146
147        if result.is_empty() {
148            result.push_str("<root>");
149        }
150
151        result
152    }
153
154    /// Resolve the field at the end of this path, if the path ends at a struct field.
155    ///
156    /// This navigates through the given shape following each step in the path,
157    /// and returns the [`Field`] if the final step is a `PathStep::Field`.
158    ///
159    /// This is useful for accessing field metadata like attributes when handling
160    /// errors that occur at a specific field location.
161    ///
162    /// # Returns
163    ///
164    /// - `Some(&Field)` if the path ends at a struct field
165    /// - `None` if the path is empty, doesn't end at a field, or navigation fails
166    pub fn resolve_leaf_field(&self, shape: &'static Shape) -> Option<&'static Field> {
167        if self.steps.is_empty() {
168            return None;
169        }
170
171        let mut current_shape = shape;
172        let mut current_variant_idx: Option<usize> = None;
173
174        // Navigate through all steps except the last one
175        for step in &self.steps[..self.steps.len() - 1] {
176            match step {
177                PathStep::Field(idx) => {
178                    let idx = *idx as usize;
179                    current_shape =
180                        get_field_shape_with_variant(current_shape, idx, current_variant_idx)?;
181                    current_variant_idx = None;
182                }
183                PathStep::Index(_) => {
184                    current_shape = get_element_shape(current_shape)?;
185                    current_variant_idx = None;
186                }
187                PathStep::Variant(idx) => {
188                    // Remember the variant for the next field lookup
189                    current_variant_idx = Some(*idx as usize);
190                }
191                PathStep::MapKey => {
192                    current_shape = get_map_key_shape(current_shape)?;
193                    current_variant_idx = None;
194                }
195                PathStep::MapValue => {
196                    current_shape = get_map_value_shape(current_shape)?;
197                    current_variant_idx = None;
198                }
199                PathStep::OptionSome => {
200                    current_shape = get_option_inner_shape(current_shape)?;
201                    current_variant_idx = None;
202                }
203                PathStep::Deref => {
204                    current_shape = get_pointer_inner_shape(current_shape)?;
205                    current_variant_idx = None;
206                }
207            }
208        }
209
210        // Check if the last step is a field
211        if let Some(PathStep::Field(idx)) = self.steps.last() {
212            let idx = *idx as usize;
213            return get_field_with_variant(current_shape, idx, current_variant_idx);
214        }
215
216        None
217    }
218}
219
220/// Get the field at the given index, handling both structs and enum variants.
221fn get_field_with_variant(
222    shape: &Shape,
223    idx: usize,
224    variant_idx: Option<usize>,
225) -> Option<&'static Field> {
226    match shape.ty {
227        Type::User(UserType::Struct(sd)) => sd.fields.get(idx),
228        Type::User(UserType::Enum(ed)) => {
229            let variant_idx = variant_idx?;
230            let variant = ed.variants.get(variant_idx)?;
231            variant.data.fields.get(idx)
232        }
233        _ => None,
234    }
235}
236
237/// Get the shape of a field at the given index, handling both structs and enum variants.
238fn get_field_shape_with_variant(
239    shape: &Shape,
240    idx: usize,
241    variant_idx: Option<usize>,
242) -> Option<&'static Shape> {
243    get_field_with_variant(shape, idx, variant_idx).map(|f| f.shape())
244}
245
246/// Get the name of a field at the given index.
247fn get_field_name(shape: &Shape, idx: usize) -> Option<&'static str> {
248    match shape.ty {
249        Type::User(UserType::Struct(sd)) => sd.fields.get(idx).map(|f| f.name),
250        Type::User(UserType::Enum(_)) => {
251            // For enums, we'd need the variant to get field names
252            None
253        }
254        _ => None,
255    }
256}
257
258/// Get the shape of a field at the given index.
259fn get_field_shape(shape: &Shape, idx: usize) -> Option<&'static Shape> {
260    match shape.ty {
261        Type::User(UserType::Struct(sd)) => sd.fields.get(idx).map(|f| f.shape()),
262        _ => None,
263    }
264}
265
266/// Get the element shape for a list/array.
267fn get_element_shape(shape: &Shape) -> Option<&'static Shape> {
268    match shape.def {
269        Def::List(ld) => Some(ld.t()),
270        Def::Array(ad) => Some(ad.t()),
271        Def::Slice(sd) => Some(sd.t()),
272        _ => None,
273    }
274}
275
276/// Get the name of a variant at the given index.
277fn get_variant_name(shape: &Shape, idx: usize) -> Option<&'static str> {
278    match shape.ty {
279        Type::User(UserType::Enum(ed)) => ed.variants.get(idx).map(|v| v.name),
280        _ => None,
281    }
282}
283
284/// Get the "shape" for a variant - returns the first field's shape if present.
285fn get_variant_shape(shape: &Shape, idx: usize) -> Option<&'static Shape> {
286    match shape.ty {
287        Type::User(UserType::Enum(ed)) => {
288            let variant = ed.variants.get(idx)?;
289            if variant.data.kind == StructKind::Unit {
290                None
291            } else {
292                variant.data.fields.first().map(|f| f.shape())
293            }
294        }
295        _ => None,
296    }
297}
298
299/// Get the key shape for a map.
300fn get_map_key_shape(shape: &Shape) -> Option<&'static Shape> {
301    match shape.def {
302        Def::Map(md) => Some(md.k()),
303        _ => None,
304    }
305}
306
307/// Get the value shape for a map.
308fn get_map_value_shape(shape: &Shape) -> Option<&'static Shape> {
309    match shape.def {
310        Def::Map(md) => Some(md.v()),
311        _ => None,
312    }
313}
314
315/// Get the inner shape for an Option.
316fn get_option_inner_shape(shape: &Shape) -> Option<&'static Shape> {
317    match shape.def {
318        Def::Option(od) => Some(od.t()),
319        _ => None,
320    }
321}
322
323/// Get the inner shape for a pointer.
324fn get_pointer_inner_shape(shape: &Shape) -> Option<&'static Shape> {
325    match shape.def {
326        Def::Pointer(pd) => pd.pointee(),
327        _ => None,
328    }
329}
330
331#[cfg(feature = "pretty")]
332pub mod pretty;
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn test_path_step_size() {
340        // PathStep should be 8 bytes (discriminant + u32, aligned)
341        assert_eq!(core::mem::size_of::<PathStep>(), 8);
342    }
343}