Skip to main content

facet_reflect/
error.rs

1use facet_core::{Characteristic, EnumType, FieldError, Shape, TryFromError};
2use facet_path::Path;
3
4/// Error returned when materializing a HeapValue to the wrong type.
5///
6/// This is separate from `ReflectError` because HeapValue operations
7/// don't have path context - they operate on already-constructed values.
8#[derive(Debug, Clone)]
9pub struct ShapeMismatchError {
10    /// The shape that was expected (the target type).
11    pub expected: &'static Shape,
12    /// The shape that was actually found (the HeapValue's shape).
13    pub actual: &'static Shape,
14}
15
16impl core::fmt::Display for ShapeMismatchError {
17    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
18        write!(
19            f,
20            "shape mismatch: expected {}, got {}",
21            self.expected, self.actual
22        )
23    }
24}
25
26impl core::error::Error for ShapeMismatchError {}
27
28/// Error returned when allocating memory for a shape fails.
29///
30/// This is separate from `ReflectError` because allocation happens
31/// before reflection begins - we don't have a path yet.
32#[derive(Debug, Clone)]
33pub struct AllocError {
34    /// The shape we tried to allocate.
35    pub shape: &'static Shape,
36    /// What operation was being attempted.
37    pub operation: &'static str,
38}
39
40impl core::fmt::Display for AllocError {
41    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42        write!(f, "failed to allocate {}: {}", self.shape, self.operation)
43    }
44}
45
46impl core::error::Error for AllocError {}
47
48/// A kind-only version of Tracker
49#[allow(missing_docs)]
50#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
51#[non_exhaustive]
52pub enum TrackerKind {
53    Scalar,
54    Array,
55    Struct,
56    SmartPointer,
57    SmartPointerSlice,
58    Enum,
59    List,
60    Map,
61    Set,
62    Option,
63    Result,
64    DynamicValue,
65    Inner,
66}
67
68/// Error that occurred during reflection, with path context.
69#[derive(Clone)]
70pub struct ReflectError {
71    /// Path through the type structure where the error occurred.
72    pub path: Path,
73
74    /// The specific kind of error.
75    pub kind: ReflectErrorKind,
76}
77
78impl ReflectError {
79    /// Create a new ReflectError with path context.
80    #[inline]
81    pub fn new(kind: ReflectErrorKind, path: Path) -> Self {
82        Self { path, kind }
83    }
84}
85
86/// Specific kinds of reflection errors.
87#[derive(Clone)]
88#[non_exhaustive]
89pub enum ReflectErrorKind {
90    /// Tried to set an enum to a variant that does not exist
91    NoSuchVariant {
92        /// The enum definition containing all known variants.
93        enum_type: EnumType,
94    },
95
96    /// Tried to get the wrong shape out of a value — e.g. we were manipulating
97    /// a `String`, but `.get()` was called with a `u64` or something.
98    WrongShape {
99        /// The expected shape of the value.
100        expected: &'static Shape,
101        /// The actual shape of the value.
102        actual: &'static Shape,
103    },
104
105    /// Attempted to perform an operation that expected a struct or something
106    WasNotA {
107        /// The name of the expected type.
108        expected: &'static str,
109
110        /// The type we got instead
111        actual: &'static Shape,
112    },
113
114    /// A field was not initialized during build
115    UninitializedField {
116        /// The shape containing the field
117        shape: &'static Shape,
118        /// The name of the field that wasn't initialized
119        field_name: &'static str,
120    },
121
122    /// A scalar value was not initialized during build
123    UninitializedValue {
124        /// The scalar shape
125        shape: &'static Shape,
126    },
127
128    /// A field validation failed
129    ValidationFailed {
130        /// The shape containing the field
131        shape: &'static Shape,
132        /// The name of the field that failed validation
133        field_name: &'static str,
134        /// The validation error message
135        message: alloc::string::String,
136    },
137
138    /// An invariant of the reflection system was violated.
139    InvariantViolation {
140        /// The invariant that was violated.
141        invariant: &'static str,
142    },
143
144    /// Attempted to set a value to its default, but the value doesn't implement `Default`.
145    MissingCharacteristic {
146        /// The shape of the value that doesn't implement `Default`.
147        shape: &'static Shape,
148        /// The characteristic that is missing.
149        characteristic: Characteristic,
150    },
151
152    /// An operation failed for a given shape
153    OperationFailed {
154        /// The shape of the value for which the operation failed.
155        shape: &'static Shape,
156        /// The name of the operation that failed.
157        operation: &'static str,
158    },
159
160    #[cfg(feature = "alloc")]
161    /// An operation failed for a given shape with an owned operation message.
162    OperationFailedOwned {
163        /// The shape of the value for which the operation failed.
164        shape: &'static Shape,
165        /// The operation failure message.
166        operation: alloc::string::String,
167    },
168
169    /// Failed to parse a string value into the target type
170    ParseFailed {
171        /// The shape we were trying to parse into.
172        shape: &'static Shape,
173        /// The input string that failed to parse.
174        input: alloc::string::String,
175    },
176
177    /// An error occurred when attempting to access or modify a field.
178    FieldError {
179        /// The shape of the value containing the field.
180        shape: &'static Shape,
181        /// The specific error that occurred with the field.
182        field_error: FieldError,
183    },
184
185    /// Attempted to mutate struct fields on a type that is not POD (Plain Old Data).
186    ///
187    /// Field mutation through reflection requires the parent struct to be POD
188    /// (have no invariants). Mark the struct with `#[facet(pod)]` to enable
189    /// field mutation. Wholesale replacement via `Poke::set()` is always allowed.
190    NotPod {
191        /// The shape of the struct that is not POD.
192        shape: &'static Shape,
193    },
194
195    /// Indicates that we try to access a field on an `Arc<T>`, for example, and the field might exist
196    /// on the T, but you need to do begin_smart_ptr first when using the WIP API.
197    MissingPushPointee {
198        /// The smart pointer (`Arc<T>`, `Box<T>` etc.) shape on which field was caleld
199        shape: &'static Shape,
200    },
201
202    /// An unknown error occurred.
203    Unknown,
204
205    /// An error occured while putting
206    TryFromError {
207        /// The shape of the value being converted from.
208        src_shape: &'static Shape,
209
210        /// The shape of the value being converted to.
211        dst_shape: &'static Shape,
212
213        /// The inner error
214        inner: TryFromError,
215    },
216
217    /// A shape has a `default` attribute, but no implementation of the `Default` trait.
218    DefaultAttrButNoDefaultImpl {
219        /// The shape of the value that has a `default` attribute but no default implementation.
220        shape: &'static Shape,
221    },
222
223    /// The type is unsized
224    Unsized {
225        /// The shape for the type that is unsized
226        shape: &'static Shape,
227        /// The operation we were trying to perform
228        operation: &'static str,
229    },
230
231    /// Array not fully initialized during build
232    ArrayNotFullyInitialized {
233        /// The shape of the array
234        shape: &'static Shape,
235        /// The number of elements pushed
236        pushed_count: usize,
237        /// The expected array size
238        expected_size: usize,
239    },
240
241    /// Array index out of bounds
242    ArrayIndexOutOfBounds {
243        /// The shape of the array
244        shape: &'static Shape,
245        /// The index that was out of bounds
246        index: usize,
247        /// The array size
248        size: usize,
249    },
250
251    /// Invalid operation for the current state
252    InvalidOperation {
253        /// The operation that was attempted
254        operation: &'static str,
255        /// The reason why it failed
256        reason: &'static str,
257    },
258
259    /// Unexpected tracker state when performing a reflection operation
260    UnexpectedTracker {
261        /// User-friendly message including operation that was being
262        /// attempted
263        message: &'static str,
264
265        /// The current tracker set for this frame
266        current_tracker: TrackerKind,
267    },
268
269    /// No active frame in Partial
270    NoActiveFrame,
271
272    #[cfg(feature = "alloc")]
273    /// Error during custom deserialization
274    CustomDeserializationError {
275        /// Error message provided by the deserialize_with method
276        message: alloc::string::String,
277        /// Shape that was passed to deserialize_with
278        src_shape: &'static Shape,
279        /// the shape of the target type
280        dst_shape: &'static Shape,
281    },
282
283    #[cfg(feature = "alloc")]
284    /// A user-defined invariant check failed during build
285    UserInvariantFailed {
286        /// The error message from the invariant check
287        message: alloc::string::String,
288        /// The shape of the value that failed the invariant check
289        shape: &'static Shape,
290    },
291
292    #[cfg(feature = "alloc")]
293    /// Error during custom serialization
294    CustomSerializationError {
295        /// Error message provided by the serialize_with method
296        message: alloc::string::String,
297        /// Shape that was passed to serialize_with
298        src_shape: &'static Shape,
299        /// the shape of the target
300        dst_shape: &'static Shape,
301    },
302}
303
304impl core::fmt::Display for ReflectError {
305    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
306        write!(f, "{} at {}", self.kind, self.path)
307    }
308}
309
310impl core::fmt::Display for ReflectErrorKind {
311    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312        match self {
313            ReflectErrorKind::NoSuchVariant { enum_type } => {
314                write!(f, "No such variant in enum. Known variants: ")?;
315                for v in enum_type.variants {
316                    write!(f, ", {}", v.name)?;
317                }
318                write!(f, ", that's it.")
319            }
320            ReflectErrorKind::WrongShape { expected, actual } => {
321                write!(f, "Wrong shape: expected {expected}, but got {actual}")
322            }
323            ReflectErrorKind::WasNotA { expected, actual } => {
324                write!(f, "Wrong shape: expected {expected}, but got {actual}")
325            }
326            ReflectErrorKind::UninitializedField { shape, field_name } => {
327                write!(
328                    f,
329                    "Field '{shape}::{field_name}' was not initialized. \
330                    If you need to leave fields partially initialized and come back later, \
331                    use deferred mode (begin_deferred/finish_deferred)"
332                )
333            }
334            ReflectErrorKind::UninitializedValue { shape } => {
335                write!(
336                    f,
337                    "Value '{shape}' was not initialized. \
338                    If you need to leave values partially initialized and come back later, \
339                    use deferred mode (begin_deferred/finish_deferred)"
340                )
341            }
342            ReflectErrorKind::ValidationFailed {
343                shape,
344                field_name,
345                message,
346            } => {
347                write!(
348                    f,
349                    "Validation failed for field '{shape}::{field_name}': {message}"
350                )
351            }
352            ReflectErrorKind::InvariantViolation { invariant } => {
353                write!(f, "Invariant violation: {invariant}")
354            }
355            ReflectErrorKind::MissingCharacteristic {
356                shape,
357                characteristic,
358            } => write!(
359                f,
360                "{shape} does not implement characteristic {characteristic:?}",
361            ),
362            ReflectErrorKind::OperationFailed { shape, operation } => {
363                write!(f, "Operation failed on shape {shape}: {operation}")
364            }
365            #[cfg(feature = "alloc")]
366            ReflectErrorKind::OperationFailedOwned { shape, operation } => {
367                write!(f, "Operation failed on shape {shape}: {operation}")
368            }
369            ReflectErrorKind::ParseFailed { shape, input } => {
370                write!(f, "failed to parse \"{input}\" as {shape}")
371            }
372            ReflectErrorKind::FieldError { shape, field_error } => {
373                write!(f, "Field error for shape {shape}: {field_error}")
374            }
375            ReflectErrorKind::NotPod { shape } => {
376                write!(
377                    f,
378                    "Cannot mutate fields of '{shape}' - it is not POD (Plain Old Data). \
379                     Add #[facet(pod)] to the struct to enable field mutation. \
380                     (Wholesale replacement via Poke::set() is always allowed.)"
381                )
382            }
383            ReflectErrorKind::MissingPushPointee { shape } => {
384                write!(
385                    f,
386                    "Tried to access a field on smart pointer '{shape}', but you need to call .begin_smart_ptr() first to work with the value it points to (and pop it with .pop() later)"
387                )
388            }
389            ReflectErrorKind::Unknown => write!(f, "Unknown error"),
390            ReflectErrorKind::TryFromError {
391                src_shape,
392                dst_shape,
393                inner,
394            } => {
395                write!(
396                    f,
397                    "While trying to put {src_shape} into a {dst_shape}: {inner}"
398                )
399            }
400            ReflectErrorKind::DefaultAttrButNoDefaultImpl { shape } => write!(
401                f,
402                "Shape '{shape}' has a `default` attribute but no default implementation"
403            ),
404            ReflectErrorKind::Unsized { shape, operation } => write!(
405                f,
406                "Shape '{shape}' is unsized, can't perform operation {operation}"
407            ),
408            ReflectErrorKind::ArrayNotFullyInitialized {
409                shape,
410                pushed_count,
411                expected_size,
412            } => {
413                write!(
414                    f,
415                    "Array '{shape}' not fully initialized: expected {expected_size} elements, but got {pushed_count}"
416                )
417            }
418            ReflectErrorKind::ArrayIndexOutOfBounds { shape, index, size } => {
419                write!(
420                    f,
421                    "Array index {index} out of bounds for '{shape}' (array length is {size})"
422                )
423            }
424            ReflectErrorKind::InvalidOperation { operation, reason } => {
425                write!(f, "Invalid operation '{operation}': {reason}")
426            }
427            ReflectErrorKind::UnexpectedTracker {
428                message,
429                current_tracker,
430            } => {
431                write!(f, "{message}: current tracker is {current_tracker:?}")
432            }
433            ReflectErrorKind::NoActiveFrame => {
434                write!(f, "No active frame in Partial")
435            }
436            #[cfg(feature = "alloc")]
437            ReflectErrorKind::CustomDeserializationError {
438                message,
439                src_shape,
440                dst_shape,
441            } => {
442                write!(
443                    f,
444                    "Custom deserialization of shape '{src_shape}' into '{dst_shape}' failed: {message}"
445                )
446            }
447            #[cfg(feature = "alloc")]
448            ReflectErrorKind::CustomSerializationError {
449                message,
450                src_shape,
451                dst_shape,
452            } => {
453                write!(
454                    f,
455                    "Custom serialization of shape '{src_shape}' into '{dst_shape}' failed: {message}"
456                )
457            }
458            #[cfg(feature = "alloc")]
459            ReflectErrorKind::UserInvariantFailed { message, shape } => {
460                write!(f, "Invariant check failed for '{shape}': {message}")
461            }
462        }
463    }
464}
465
466impl core::fmt::Debug for ReflectError {
467    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
468        f.debug_struct("ReflectError")
469            .field("path", &self.path)
470            .field("kind", &self.kind)
471            .finish()
472    }
473}
474
475impl core::fmt::Debug for ReflectErrorKind {
476    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
477        // Use Display implementation for more readable output
478        write!(f, "ReflectErrorKind({self})")
479    }
480}
481
482impl core::error::Error for ReflectError {}
483impl core::error::Error for ReflectErrorKind {}
484
485impl From<AllocError> for ReflectError {
486    fn from(e: AllocError) -> Self {
487        ReflectError {
488            path: Path::new(e.shape),
489            kind: ReflectErrorKind::OperationFailed {
490                shape: e.shape,
491                operation: e.operation,
492            },
493        }
494    }
495}