rstmt_core/
error.rs

1/*
2    Appellation: error <module>
3    Contrib: @FL03
4*/
5//! this module defines the [`Error`] type and provides a type alias for a
6//! [`Result`](core::result::Result) with an [`Error`].
7#[cfg(feature = "alloc")]
8use alloc::{boxed::Box, string::String};
9
10/// a type alias for a [`Result`](core::result::Result) with a [`Error`] type
11pub type Result<T = ()> = core::result::Result<T, Error>;
12
13/// The [`Error`] enum represents various errors that can occur in the application.
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum Error {
17    #[error("Attempted to use an invalid accidental")]
18    InvalidAccidental,
19    #[error("Attempted to name an invalid pitch class: {0}")]
20    InvalidPitchClass(isize),
21    #[error("Mismatched pitch classes, received {0} while expecting {1}")]
22    MismatchedPitchClasses(isize, isize),
23    #[error("Unable to parse the string into the configured type")]
24    FromStrParseError,
25    #[error("Unable to parse the string into the desired pitch class: {0}")]
26    InvalidPitchClassParse(&'static str),
27    #[error("Mismatched symbols")]
28    MismatchedSymbols,
29    #[error("Invalid Chord")]
30    InvalidChord,
31    #[cfg(feature = "alloc")]
32    #[error("Invalid Intervals: {0}")]
33    IncompatibleIntervals(String),
34    #[error("Invalid Note")]
35    InvalidNote,
36    // external errors
37    #[error(transparent)]
38    AnyError(#[from] anyhow::Error),
39    #[error(transparent)]
40    #[cfg(feature = "serde_json")]
41    JsonError(#[from] serde_json::Error),
42    // core errors
43    #[error(transparent)]
44    AddrParseError(#[from] core::net::AddrParseError),
45    #[error("The impossible has occurred")]
46    Infallible(#[from] core::convert::Infallible),
47    #[error(transparent)]
48    FmtError(#[from] core::fmt::Error),
49    #[error(transparent)]
50    Utf8Error(#[from] core::str::Utf8Error),
51    // std-based errors
52    #[cfg(feature = "std")]
53    #[error(transparent)]
54    IOError(#[from] std::io::Error),
55    // alloc-based errors
56    #[cfg(feature = "alloc")]
57    #[error(transparent)]
58    BoxError(#[from] Box<dyn core::error::Error + Send + Sync>),
59    #[cfg(feature = "alloc")]
60    #[error("Unknown Error: {0}")]
61    Unknown(String),
62}
63
64impl Error {
65    /// creates a boxed error from the provided error
66    #[cfg(feature = "alloc")]
67    pub fn boxed<E>(err: E) -> Self
68    where
69        E: core::error::Error + Send + Sync + 'static,
70    {
71        Error::BoxError(Box::new(err))
72    }
73    /// creates a boxed error from the provided error
74    #[cfg(feature = "alloc")]
75    pub fn unknown<E>(err: E) -> Self
76    where
77        E: alloc::string::ToString,
78    {
79        Error::Unknown(err.to_string())
80    }
81}
82
83#[cfg(feature = "alloc")]
84impl From<&str> for Error {
85    fn from(s: &str) -> Self {
86        Error::unknown(s)
87    }
88}
89
90#[cfg(feature = "alloc")]
91impl From<String> for Error {
92    fn from(s: String) -> Self {
93        Error::Unknown(s)
94    }
95}
96
97#[doc(hidden)]
98pub mod custom {
99
100    #[cfg(feature = "alloc")]
101    use alloc::boxed::Box;
102
103    #[derive(
104        Clone,
105        Copy,
106        Debug,
107        Default,
108        Eq,
109        Hash,
110        Ord,
111        PartialEq,
112        PartialOrd,
113        strum::AsRefStr,
114        strum::Display,
115        strum::EnumCount,
116        strum::EnumString,
117        strum::VariantNames,
118    )]
119    #[cfg_attr(
120        feature = "serde",
121        derive(serde::Deserialize, serde::Serialize),
122        serde(rename_all = "snake_case")
123    )]
124    #[non_exhaustive]
125    pub enum ErrorKind {
126        InvalidAccidental,
127        ChordError,
128        NoteError,
129        PitchError,
130        InvalidPitchClass,
131        MismatchedPitchClasses,
132        FromStrParseError,
133        InvalidPitchClassParse,
134        MismatchedSymbols,
135        InvalidChord,
136        IncompatibleIntervals,
137        InvalidNote,
138        Utf8Error,
139        AnyError,
140        JsonError,
141        AddrParseError,
142        Infallible,
143        FmtError,
144        None,
145        IOError,
146        #[default]
147        Unknown,
148    }
149
150    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
151    pub struct ErrorBase<E = Box<dyn core::error::Error + 'static>> {
152        pub(crate) kind: ErrorKind,
153        pub(crate) source: Option<E>,
154    }
155
156    impl<E> ErrorBase<E>
157    where
158        E: core::error::Error,
159    {
160        pub const fn new(kind: ErrorKind, source: Option<E>) -> Self {
161            Self { kind, source }
162        }
163
164        pub const fn unknown(source: E) -> Self {
165            Self {
166                kind: ErrorKind::Unknown,
167                source: Some(source),
168            }
169        }
170        /// consumes the current error to create another of the given kind
171        pub fn with_kind(self, kind: ErrorKind) -> ErrorBase<E> {
172            ErrorBase {
173                kind,
174                source: self.source,
175            }
176        }
177        /// consumes the current error to create another with the given message
178        pub fn with_source<E2>(self, source: E2) -> ErrorBase<E2>
179        where
180            E2: core::error::Error,
181        {
182            ErrorBase {
183                kind: self.kind,
184                source: Some(source),
185            }
186        }
187    }
188
189    impl<E> core::fmt::Display for ErrorBase<E>
190    where
191        E: 'static + core::error::Error,
192    {
193        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
194            if let Some(source) = &self.source {
195                write!(f, "{}: {}", self.kind, source)
196            } else {
197                write!(f, "{}", self.kind)
198            }
199        }
200    }
201
202    impl<E> core::error::Error for ErrorBase<E>
203    where
204        E: 'static + core::error::Error,
205    {
206        fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
207            self.source
208                .as_ref()
209                .map(|e| e as &(dyn core::error::Error + 'static))
210        }
211    }
212
213    impl<T> From<Option<T>> for ErrorBase
214    where
215        T: core::error::Error + 'static,
216    {
217        fn from(opt: Option<T>) -> Self {
218            match opt {
219                Some(err) => Self {
220                    kind: ErrorKind::Unknown,
221                    source: Some(Box::new(err)),
222                },
223                None => Self {
224                    kind: ErrorKind::None,
225                    source: None,
226                },
227            }
228        }
229    }
230
231    impl From<core::str::Utf8Error> for ErrorBase {
232        fn from(err: core::str::Utf8Error) -> Self {
233            Self {
234                kind: ErrorKind::Utf8Error,
235                #[cfg(feature = "alloc")]
236                source: Some(Box::new(err)),
237            }
238        }
239    }
240
241    #[cfg(feature = "serde_json")]
242    impl From<serde_json::Error> for ErrorBase {
243        fn from(err: serde_json::Error) -> Self {
244            Self {
245                kind: ErrorKind::JsonError,
246                source: Some(Box::new(err)),
247            }
248        }
249    }
250}