Skip to main content

facet_reflect/
spanned.rs

1//! Types for tracking source span information during deserialization.
2
3use core::{fmt, mem};
4
5use facet_core::{
6    Def, Facet, FieldBuilder, Shape, StructKind, TypeOpsDirect, type_ops_direct, vtable_direct,
7};
8
9/// Source span with offset and length.
10///
11/// This type tracks a byte offset and length within a source document,
12/// useful for error reporting that can point back to the original source.
13///
14/// To use span tracking in your own types, define a wrapper struct with
15/// `#[facet(metadata_container)]` and a span field marked with `#[facet(metadata = "span")]`:
16///
17/// ```rust
18/// use facet::Facet;
19/// use facet_reflect::Span;
20///
21/// #[derive(Debug, Clone, Facet)]
22/// #[facet(metadata_container)]
23/// pub struct Spanned<T> {
24///     pub value: T,
25///     #[facet(metadata = "span")]
26///     pub span: Option<Span>,
27/// }
28/// ```
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
30pub struct Span {
31    /// Byte offset from start of source (max 4GB).
32    pub offset: u32,
33    /// Length in bytes (max 4GB).
34    pub len: u32,
35}
36
37impl fmt::Display for Span {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        // use half-closed notation
40        write!(f, "[{}..{})", self.offset, self.offset + self.len)
41    }
42}
43
44impl Span {
45    /// Create a new span with the given offset and length.
46    ///
47    /// Values larger than `u32::MAX` are saturated.
48    pub const fn new(offset: usize, len: usize) -> Self {
49        Self {
50            offset: if offset > u32::MAX as usize {
51                u32::MAX
52            } else {
53                offset as u32
54            },
55            len: if len > u32::MAX as usize {
56                u32::MAX
57            } else {
58                len as u32
59            },
60        }
61    }
62
63    /// Check if this span is unknown (zero offset and length).
64    pub const fn is_unknown(&self) -> bool {
65        self.offset == 0 && self.len == 0
66    }
67
68    /// Get the end offset (offset + len).
69    pub const fn end(&self) -> usize {
70        self.offset as usize + self.len as usize
71    }
72}
73
74// SAFETY: Span is a simple struct with two u32 fields, properly laid out
75unsafe impl Facet<'_> for Span {
76    const SHAPE: &'static Shape = &const {
77        static FIELDS: [facet_core::Field; 2] = [
78            FieldBuilder::new(
79                "offset",
80                facet_core::shape_of::<u32>,
81                mem::offset_of!(Span, offset),
82            )
83            .build(),
84            FieldBuilder::new(
85                "len",
86                facet_core::shape_of::<u32>,
87                mem::offset_of!(Span, len),
88            )
89            .build(),
90        ];
91
92        const VTABLE: facet_core::VTableDirect = vtable_direct!(Span => Debug, PartialEq);
93        const TYPE_OPS: TypeOpsDirect = type_ops_direct!(Span => Default, Clone);
94
95        Shape::builder_for_sized::<Span>("Span")
96            .vtable_direct(&VTABLE)
97            .type_ops_direct(&TYPE_OPS)
98            .ty(facet_core::Type::struct_builder(StructKind::Struct, &FIELDS).build())
99            .def(Def::Undefined)
100            .build()
101    };
102}
103
104/// Extract the inner value shape from a metadata container.
105///
106/// For a struct marked with `#[facet(metadata_container)]`, this returns
107/// the shape of the first non-metadata field (the actual value being wrapped).
108///
109/// This is useful when you need to look through a metadata wrapper (like
110/// a user-defined `Spanned<T>` or `Documented<T>`) to determine the actual type
111/// being wrapped, such as when matching untagged enum variants against scalar values.
112///
113/// Returns `None` if the shape is not a metadata container or has no value fields.
114pub fn get_metadata_container_value_shape(shape: &Shape) -> Option<&'static Shape> {
115    use facet_core::{Type, UserType};
116
117    if !shape.is_metadata_container() {
118        return None;
119    }
120
121    if let Type::User(UserType::Struct(struct_def)) = &shape.ty {
122        // Find the first non-metadata field (the actual value)
123        struct_def
124            .fields
125            .iter()
126            .find(|f| !f.is_metadata())
127            .map(|f| f.shape.get())
128    } else {
129        None
130    }
131}