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}