Skip to main content

rushdown/
error.rs

1//! Custom error types for the rushdown library.
2
3extern crate alloc;
4
5use alloc::boxed::Box;
6use alloc::format;
7use alloc::string::String;
8use core::fmt::{self, Debug, Formatter};
9use core::result::Result as CoreResult;
10use core::{error::Error as CoreError, fmt::Display};
11
12use crate::ast::NodeRef;
13
14#[cfg(feature = "std")]
15use std::backtrace::{Backtrace, BacktraceStatus};
16
17/// Alias for a Result type that uses [`Error`].
18pub type Result<T> = CoreResult<T, Error>;
19
20/// Error type that can represent either an internal [`Error`] or a user-defined callback
21/// error.
22#[derive(Debug)]
23#[non_exhaustive]
24pub enum CallbackError<E: CoreError + 'static> {
25    /// Internal error from the rushdown library.
26    Internal(Error),
27
28    /// User-defined callback error.
29    Callback(E),
30}
31
32impl<E: CoreError + 'static> Display for CallbackError<E> {
33    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
34        match self {
35            CallbackError::Internal(err) => write!(f, "{}", err),
36            CallbackError::Callback(err) => {
37                write!(f, "{}", err)
38            }
39        }
40    }
41}
42
43impl<E: CoreError + 'static> CoreError for CallbackError<E> {
44    fn source(&self) -> Option<&(dyn CoreError + 'static)> {
45        match self {
46            CallbackError::Internal(err) => Some(err),
47            CallbackError::Callback(err) => Some(err),
48        }
49    }
50}
51
52/// Custom error type for the rushdown library.
53#[non_exhaustive]
54pub enum Error {
55    /// Invalid node reference error.
56    InvalidNodeRef {
57        noderef: NodeRef,
58        description: String,
59        #[cfg(feature = "std")]
60        backtrace: Backtrace,
61    },
62    /// Invalid node operation error.
63    InvalidNodeOperation {
64        message: String,
65        description: String,
66        #[cfg(feature = "std")]
67        backtrace: Backtrace,
68    },
69    /// I/O error.
70    Io {
71        message: String,
72        description: String,
73        source: Option<Box<dyn CoreError + 'static>>,
74        #[cfg(feature = "std")]
75        backtrace: Backtrace,
76    },
77}
78
79impl Error {
80    /// Creates a new invalid node reference error.
81    pub fn invalid_node_ref(node_ref: NodeRef) -> Self {
82        Error::InvalidNodeRef {
83            noderef: node_ref,
84            description: format!("invalid node reference: {}", node_ref),
85            #[cfg(feature = "std")]
86            backtrace: Backtrace::capture(),
87        }
88    }
89
90    /// Creates a new invalid operation error with a message.
91    pub fn invalid_node_operation(message: String) -> Self {
92        Error::InvalidNodeOperation {
93            message: message.clone(),
94            description: format!("invalid operation: {}", message),
95            #[cfg(feature = "std")]
96            backtrace: Backtrace::capture(),
97        }
98    }
99
100    /// Creates a new I/O error with an optional source error.
101    pub fn io<S>(m: S, source: Option<Box<dyn CoreError + 'static>>) -> Self
102    where
103        S: Into<String>,
104    {
105        let message = m.into();
106        Error::Io {
107            message: message.clone(),
108            description: format!("io error: {}", message),
109            source,
110            #[cfg(feature = "std")]
111            backtrace: Backtrace::capture(),
112        }
113    }
114
115    /// Returns the backtrace associated with the error, if available.
116    /// This is only available when the `std` feature is enabled.
117    #[cfg(feature = "std")]
118    pub fn backtrace(&self) -> Option<&Backtrace> {
119        match self {
120            Error::InvalidNodeRef { backtrace, .. } => Some(backtrace),
121            Error::InvalidNodeOperation { backtrace, .. } => Some(backtrace),
122            Error::Io { backtrace, .. } => Some(backtrace),
123        }
124    }
125}
126
127impl Display for Error {
128    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
129        match self {
130            Error::InvalidNodeRef { description, .. } => write!(f, "{}", description),
131            Error::InvalidNodeOperation { description, .. } => write!(f, "{}", description),
132            Error::Io { description, .. } => write!(f, "{}", description),
133        }
134    }
135}
136
137impl Debug for Error {
138    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
139        match self {
140            Error::InvalidNodeRef {
141                description,
142                #[cfg(feature = "std")]
143                backtrace,
144                ..
145            } => {
146                write!(f, "{}", description)?;
147                #[cfg(feature = "std")]
148                {
149                    write!(f, "{}", format_backtrace(backtrace))?;
150                }
151            }
152            Error::InvalidNodeOperation {
153                description,
154                #[cfg(feature = "std")]
155                backtrace,
156                ..
157            } => {
158                write!(f, "{}", description)?;
159                #[cfg(feature = "std")]
160                {
161                    write!(f, "{}", format_backtrace(backtrace))?;
162                }
163            }
164            Error::Io {
165                description,
166                #[cfg(feature = "std")]
167                backtrace,
168                ..
169            } => {
170                write!(f, "{}", description)?;
171                #[cfg(feature = "std")]
172                {
173                    write!(f, "{}", format_backtrace(backtrace))?;
174                }
175            }
176        }
177        if let Some(source) = self.source() {
178            writeln!(f, "Caused by: {:?}", source)?;
179        }
180        Ok(())
181    }
182}
183
184impl CoreError for Error {
185    fn source(&self) -> Option<&(dyn CoreError + 'static)> {
186        {
187            match self {
188                Error::Io { source, .. } => source.as_deref(),
189                _ => None,
190            }
191        }
192    }
193}
194
195#[cfg(feature = "std")]
196fn format_backtrace(backtrace: &Backtrace) -> String {
197    match backtrace.status() {
198        BacktraceStatus::Captured => format!("\nstack backtrace:\n{}", backtrace),
199        _ => String::new(),
200    }
201}