facet-path 0.46.0

Path tracking for navigating Facet type structures
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
//!
//! [![Coverage Status](https://coveralls.io/repos/github/facet-rs/facet-path/badge.svg?branch=main)](https://coveralls.io/github/facet-rs/facet?branch=main)
//! [![crates.io](https://img.shields.io/crates/v/facet-path.svg)](https://crates.io/crates/facet-path)
//! [![documentation](https://docs.rs/facet-path/badge.svg)](https://docs.rs/facet-path)
//! [![MIT/Apache-2.0 licensed](https://img.shields.io/crates/l/facet-path.svg)](./LICENSE)
//! [![Discord](https://img.shields.io/discord/1379550208551026748?logo=discord&label=discord)](https://discord.gg/JhD7CwCJ8F)
//!
//!
//! Path tracking for navigating Facet type structures.
//!
//! This crate provides lightweight path tracking that records navigation steps through a Facet type hierarchy. When an error occurs during serialization or deserialization, the path can be used to produce helpful error messages showing exactly where in the data structure the problem occurred.
//!
//! ## Features
//!
//! - Lightweight `PathStep` enum that stores indices, not strings
//! - Reconstruct human-readable paths by replaying steps against a `Shape`
//! - Optional `pretty` feature for rich error rendering with `facet-pretty`
//!
//! ## Usage
//!
//! ```rust
//! use facet::Facet;
//! use facet_path::{Path, PathStep};
//!
//! #[derive(Facet)]
//! struct Outer {
//!     items: Vec<Inner>,
//! }
//!
//! #[derive(Facet)]
//! struct Inner {
//!     name: String,
//!     value: u32,
//! }
//!
//! // Build a path during traversal
//! let mut path = Path::new(<Outer as Facet>::SHAPE);
//! path.push(PathStep::Field(0));      // "items"
//! path.push(PathStep::Index(2));       // [2]
//! path.push(PathStep::Field(0));      // "name"
//!
//! // Format the path as a human-readable string
//! let formatted = path.format();
//! assert_eq!(formatted, "items[2].name");
//! ```
//!
//! ## Feature Flags
//!
//! - `std` (default): Enables standard library support
//! - `alloc`: Enables heap allocation without full std
//! - `pretty`: Enables rich error rendering with `facet-pretty`
//!
//!
//!
#![doc = include_str!("../readme-footer.md")]

extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use core::fmt::Write;

use facet_core::{Def, Field, Shape, Type, UserType};

pub mod access;
pub use access::PathAccessError;

pub mod walk;
pub use walk::{ShapeVisitor, VisitDecision, WalkStatus, walk_shape};

/// A single step in a path through a type structure.
///
/// Each step records an index that can be used to navigate
/// back through a [`Shape`] to reconstruct field names and types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PathStep {
    /// Navigate to a struct field by index
    Field(u32),
    /// Navigate to a list/array element by index
    Index(u32),
    /// Navigate to an enum variant by index
    Variant(u32),
    /// Navigate into a map key at a specific entry index.
    /// The entry index distinguishes paths for different map keys' inner frames.
    MapKey(u32),
    /// Navigate into a map value at a specific entry index.
    /// The entry index distinguishes paths for different map values' inner frames.
    MapValue(u32),
    /// Navigate into `Some` of an Option
    OptionSome,
    /// Navigate through a pointer/reference
    Deref,
    /// Navigate into a transparent inner type (e.g., `NonZero<T>` -> T)
    Inner,
    /// Navigate into a proxy type (e.g., `Inner` with `#[facet(proxy = InnerProxy)]`)
    ///
    /// This step distinguishes a proxy frame from its parent in the deferred
    /// processing path, so both can be stored without path collisions.
    Proxy,
}

/// A path through a type structure, recorded as a series of steps.
///
/// This is a lightweight representation that only stores indices.
/// The actual field names and type information can be reconstructed
/// by replaying these steps against the original [`Shape`].
#[derive(Debug, Clone)]
pub struct Path {
    /// The root [`Shape`] from which this path originates.
    pub shape: &'static Shape,

    /// The sequence of [`PathStep`]s representing navigation through the type structure.
    pub steps: Vec<PathStep>,
}

impl PartialEq for Path {
    fn eq(&self, other: &Self) -> bool {
        // Compare shapes by pointer address (they're static references)
        core::ptr::eq(self.shape, other.shape) && self.steps == other.steps
    }
}

impl Eq for Path {}

impl PartialOrd for Path {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Path {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        // Compare shapes by pointer address first, then by steps
        let shape_cmp =
            (self.shape as *const Shape as usize).cmp(&(other.shape as *const Shape as usize));
        if shape_cmp != core::cmp::Ordering::Equal {
            return shape_cmp;
        }
        self.steps.cmp(&other.steps)
    }
}

impl core::hash::Hash for Path {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        // Hash the shape pointer address
        (self.shape as *const Shape as usize).hash(state);
        self.steps.hash(state);
    }
}

impl Path {
    /// Create a new empty path.
    pub const fn new(shape: &'static Shape) -> Self {
        Self {
            shape,
            steps: Vec::new(),
        }
    }

    /// Create a path with pre-allocated capacity.
    pub fn with_capacity(shape: &'static Shape, capacity: usize) -> Self {
        Self {
            shape,
            steps: Vec::with_capacity(capacity),
        }
    }

    /// Push a step onto the path.
    pub fn push(&mut self, step: PathStep) {
        self.steps.push(step);
    }

    /// Pop the last step from the path.
    pub fn pop(&mut self) -> Option<PathStep> {
        self.steps.pop()
    }

    /// Get the steps in this path.
    pub fn steps(&self) -> &[PathStep] {
        &self.steps
    }

    /// Get the length of this path.
    pub const fn len(&self) -> usize {
        self.steps.len()
    }

    /// Check if this path is empty.
    pub const fn is_empty(&self) -> bool {
        self.steps.is_empty()
    }

    /// Format this path as a human-readable string using the stored root shape.
    ///
    /// Returns a path like `outer.inner.items[3].name`.
    pub fn format(&self) -> String {
        self.format_with_shape(self.shape)
    }

    /// Format this path as a human-readable string by walking the given shape.
    ///
    /// Returns a path like `outer.inner.items[3].name`.
    pub fn format_with_shape(&self, shape: &'static Shape) -> String {
        let mut result = String::new();
        let mut current_shape = shape;
        let mut current_variant_idx: Option<usize> = None;

        for step in &self.steps {
            match step {
                PathStep::Field(idx) => {
                    let idx = *idx as usize;
                    if let Some(field_name) =
                        get_field_name_with_variant(current_shape, idx, current_variant_idx)
                    {
                        if !result.is_empty() {
                            result.push('.');
                        }
                        result.push_str(field_name);
                    }
                    if let Some(field_shape) =
                        get_field_shape_with_variant(current_shape, idx, current_variant_idx)
                    {
                        current_shape = field_shape;
                    }
                    current_variant_idx = None;
                }
                PathStep::Index(idx) => {
                    write!(result, "[{}]", idx).unwrap();
                    if let Some(elem_shape) = get_element_shape(current_shape) {
                        current_shape = elem_shape;
                    }
                    current_variant_idx = None;
                }
                PathStep::Variant(idx) => {
                    let idx = *idx as usize;
                    if let Some(variant_name) = get_variant_name(current_shape, idx) {
                        result.push_str("::");
                        result.push_str(variant_name);
                    }
                    // Don't advance current_shape — the next Field step will
                    // use the variant index to look up fields within the enum.
                    current_variant_idx = Some(idx);
                }
                PathStep::MapKey(idx) => {
                    write!(result, "[key#{}]", idx).unwrap();
                    if let Some(key_shape) = get_map_key_shape(current_shape) {
                        current_shape = key_shape;
                    }
                    current_variant_idx = None;
                }
                PathStep::MapValue(idx) => {
                    write!(result, "[value#{}]", idx).unwrap();
                    if let Some(value_shape) = get_map_value_shape(current_shape) {
                        current_shape = value_shape;
                    }
                    current_variant_idx = None;
                }
                PathStep::OptionSome => {
                    result.push_str("::Some");
                    if let Some(inner_shape) = get_option_inner_shape(current_shape) {
                        current_shape = inner_shape;
                    }
                    current_variant_idx = None;
                }
                PathStep::Deref => {
                    if let Some(inner_shape) = get_pointer_inner_shape(current_shape) {
                        current_shape = inner_shape;
                    }
                    current_variant_idx = None;
                }
                PathStep::Inner => {
                    if let Some(inner_shape) = get_inner_shape(current_shape) {
                        current_shape = inner_shape;
                    }
                    current_variant_idx = None;
                }
                PathStep::Proxy => {
                    if let Some(proxy_def) = current_shape.effective_proxy(None) {
                        current_shape = proxy_def.shape;
                    }
                    current_variant_idx = None;
                }
            }
        }

        if result.is_empty() {
            result.push_str("<root>");
        }

        result
    }

    /// Resolve the field at the end of this path, if the path ends at a struct field.
    ///
    /// This navigates through the given shape following each step in the path,
    /// and returns the [`Field`] if the final step is a `PathStep::Field`.
    ///
    /// This is useful for accessing field metadata like attributes when handling
    /// errors that occur at a specific field location.
    ///
    /// # Returns
    ///
    /// - `Some(&Field)` if the path ends at a struct field
    /// - `None` if the path is empty, doesn't end at a field, or navigation fails
    pub fn resolve_leaf_field(&self, shape: &'static Shape) -> Option<&'static Field> {
        if self.steps.is_empty() {
            return None;
        }

        let mut current_shape = shape;
        let mut current_variant_idx: Option<usize> = None;

        // Navigate through all steps except the last one
        for step in &self.steps[..self.steps.len() - 1] {
            match step {
                PathStep::Field(idx) => {
                    let idx = *idx as usize;
                    current_shape =
                        get_field_shape_with_variant(current_shape, idx, current_variant_idx)?;
                    current_variant_idx = None;
                }
                PathStep::Index(_) => {
                    current_shape = get_element_shape(current_shape)?;
                    current_variant_idx = None;
                }
                PathStep::Variant(idx) => {
                    // Remember the variant for the next field lookup
                    current_variant_idx = Some(*idx as usize);
                }
                PathStep::MapKey(_) => {
                    current_shape = get_map_key_shape(current_shape)?;
                    current_variant_idx = None;
                }
                PathStep::MapValue(_) => {
                    current_shape = get_map_value_shape(current_shape)?;
                    current_variant_idx = None;
                }
                PathStep::OptionSome => {
                    current_shape = get_option_inner_shape(current_shape)?;
                    current_variant_idx = None;
                }
                PathStep::Deref => {
                    current_shape = get_pointer_inner_shape(current_shape)?;
                    current_variant_idx = None;
                }
                PathStep::Inner => {
                    current_shape = get_inner_shape(current_shape)?;
                    current_variant_idx = None;
                }
                PathStep::Proxy => {
                    let proxy_def = current_shape.effective_proxy(None)?;
                    current_shape = proxy_def.shape;
                    current_variant_idx = None;
                }
            }
        }

        // Check if the last step is a field
        if let Some(PathStep::Field(idx)) = self.steps.last() {
            let idx = *idx as usize;
            return get_field_with_variant(current_shape, idx, current_variant_idx);
        }

        None
    }
}

impl core::fmt::Display for Path {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&self.format())
    }
}

/// Get the field at the given index, handling both structs and enum variants.
fn get_field_with_variant(
    shape: &Shape,
    idx: usize,
    variant_idx: Option<usize>,
) -> Option<&'static Field> {
    match shape.ty {
        Type::User(UserType::Struct(sd)) => sd.fields.get(idx),
        Type::User(UserType::Enum(ed)) => {
            let variant_idx = variant_idx?;
            let variant = ed.variants.get(variant_idx)?;
            variant.data.fields.get(idx)
        }
        _ => None,
    }
}

/// Get the shape of a field at the given index, handling both structs and enum variants.
fn get_field_shape_with_variant(
    shape: &Shape,
    idx: usize,
    variant_idx: Option<usize>,
) -> Option<&'static Shape> {
    get_field_with_variant(shape, idx, variant_idx).map(|f| f.shape())
}

/// Get the name of a field at the given index, handling both structs and enum variants.
fn get_field_name_with_variant(
    shape: &Shape,
    idx: usize,
    variant_idx: Option<usize>,
) -> Option<&'static str> {
    get_field_with_variant(shape, idx, variant_idx).map(|f| f.name)
}

/// Get the element shape for a list/array.
const fn get_element_shape(shape: &Shape) -> Option<&'static Shape> {
    match shape.def {
        Def::List(ld) => Some(ld.t()),
        Def::Array(ad) => Some(ad.t()),
        Def::Slice(sd) => Some(sd.t()),
        _ => None,
    }
}

/// Get the name of a variant at the given index.
fn get_variant_name(shape: &Shape, idx: usize) -> Option<&'static str> {
    match shape.ty {
        Type::User(UserType::Enum(ed)) => ed.variants.get(idx).map(|v| v.name),
        _ => None,
    }
}

/// Get the key shape for a map.
const fn get_map_key_shape(shape: &Shape) -> Option<&'static Shape> {
    match shape.def {
        Def::Map(md) => Some(md.k()),
        _ => None,
    }
}

/// Get the value shape for a map.
const fn get_map_value_shape(shape: &Shape) -> Option<&'static Shape> {
    match shape.def {
        Def::Map(md) => Some(md.v()),
        _ => None,
    }
}

/// Get the inner shape for an Option.
const fn get_option_inner_shape(shape: &Shape) -> Option<&'static Shape> {
    match shape.def {
        Def::Option(od) => Some(od.t()),
        _ => None,
    }
}

/// Get the inner shape for a pointer.
const fn get_pointer_inner_shape(shape: &Shape) -> Option<&'static Shape> {
    match shape.def {
        Def::Pointer(pd) => pd.pointee(),
        _ => None,
    }
}

/// Get the inner shape for a transparent type (e.g., `NonZero<T>`).
const fn get_inner_shape(shape: &Shape) -> Option<&'static Shape> {
    shape.inner
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_path_step_size() {
        // PathStep should be 8 bytes (discriminant + u32, aligned)
        assert_eq!(core::mem::size_of::<PathStep>(), 8);
    }
}