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, 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 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
155/// Get the name of a field at the given index.
156fn get_field_name(shape: &Shape, idx: usize) -> Option<&'static str> {
157    match shape.ty {
158        Type::User(UserType::Struct(sd)) => sd.fields.get(idx).map(|f| f.name),
159        Type::User(UserType::Enum(_)) => {
160            // For enums, we'd need the variant to get field names
161            None
162        }
163        _ => None,
164    }
165}
166
167/// Get the shape of a field at the given index.
168fn get_field_shape(shape: &Shape, idx: usize) -> Option<&'static Shape> {
169    match shape.ty {
170        Type::User(UserType::Struct(sd)) => sd.fields.get(idx).map(|f| f.shape()),
171        _ => None,
172    }
173}
174
175/// Get the element shape for a list/array.
176fn get_element_shape(shape: &Shape) -> Option<&'static Shape> {
177    match shape.def {
178        Def::List(ld) => Some(ld.t()),
179        Def::Array(ad) => Some(ad.t()),
180        Def::Slice(sd) => Some(sd.t()),
181        _ => None,
182    }
183}
184
185/// Get the name of a variant at the given index.
186fn get_variant_name(shape: &Shape, idx: usize) -> Option<&'static str> {
187    match shape.ty {
188        Type::User(UserType::Enum(ed)) => ed.variants.get(idx).map(|v| v.name),
189        _ => None,
190    }
191}
192
193/// Get the "shape" for a variant - returns the first field's shape if present.
194fn get_variant_shape(shape: &Shape, idx: usize) -> Option<&'static Shape> {
195    match shape.ty {
196        Type::User(UserType::Enum(ed)) => {
197            let variant = ed.variants.get(idx)?;
198            if variant.data.kind == StructKind::Unit {
199                None
200            } else {
201                variant.data.fields.first().map(|f| f.shape())
202            }
203        }
204        _ => None,
205    }
206}
207
208/// Get the key shape for a map.
209fn get_map_key_shape(shape: &Shape) -> Option<&'static Shape> {
210    match shape.def {
211        Def::Map(md) => Some(md.k()),
212        _ => None,
213    }
214}
215
216/// Get the value shape for a map.
217fn get_map_value_shape(shape: &Shape) -> Option<&'static Shape> {
218    match shape.def {
219        Def::Map(md) => Some(md.v()),
220        _ => None,
221    }
222}
223
224/// Get the inner shape for an Option.
225fn get_option_inner_shape(shape: &Shape) -> Option<&'static Shape> {
226    match shape.def {
227        Def::Option(od) => Some(od.t()),
228        _ => None,
229    }
230}
231
232/// Get the inner shape for a pointer.
233fn get_pointer_inner_shape(shape: &Shape) -> Option<&'static Shape> {
234    match shape.def {
235        Def::Pointer(pd) => pd.pointee(),
236        _ => None,
237    }
238}
239
240#[cfg(feature = "pretty")]
241pub mod pretty;
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_path_step_size() {
249        // PathStep should be 8 bytes (discriminant + u32, aligned)
250        assert_eq!(core::mem::size_of::<PathStep>(), 8);
251    }
252}