dsync_hasezoey/
error.rs

1// TODO: change backtrace implementation to be by thiserror, if possible once features become stable
2// error_generic_member_access https://github.com/rust-lang/rust/issues/99301
3// provide_any https://github.com/rust-lang/rust/issues/96024
4
5use std::{backtrace::Backtrace, io::Error as ioError, path::Path};
6
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Macro to not repeat having to do multiple implementations of a [ErrorInner] variant with the same string type
10macro_rules! fn_string {
11    ($fn_name:ident, $fortype:expr) => {
12        #[doc = concat!("Create a new [Self] as [", stringify!($fortype), "]")]
13        pub fn $fn_name<M>(msg: M) -> Self
14        where
15            M: Into<String>,
16        {
17            return Self::new($fortype(msg.into()));
18        }
19    };
20}
21
22/// Error type for libytdlr, contains a backtrace, wrapper around [ErrorInner]
23#[derive(Debug)]
24pub struct Error {
25    /// The actual error
26    source: ErrorEnum,
27    #[cfg(feature = "backtrace")]
28    /// The backtrace for the error
29    backtrace: Backtrace,
30}
31
32impl Error {
33    /// Construct a new [Error] instance based on [ErrorInner]
34    pub fn new(source: ErrorEnum) -> Self {
35        Self {
36            source,
37            #[cfg(feature = "backtrace")]
38            backtrace: Backtrace::capture(),
39        }
40    }
41
42    #[cfg(feature = "backtrace")]
43    /// Get the backtrace that is stored
44    pub fn get_backtrace(&self) -> &Backtrace {
45        &self.backtrace
46    }
47
48    fn_string!(other, ErrorEnum::Other);
49    fn_string!(
50        unsupported_schema_format,
51        ErrorEnum::UnsupportedSchemaFormat
52    );
53    fn_string!(unsupported_type, ErrorEnum::UnsupportedType);
54    fn_string!(no_file_signature, ErrorEnum::NoFileSignature);
55
56    /// Create a custom [ioError] with this [Error] wrapped around with a [Path] attached
57    pub fn custom_ioerror_path<M, P>(kind: std::io::ErrorKind, msg: M, path: P) -> Self
58    where
59        M: Into<String>,
60        P: AsRef<Path>,
61    {
62        return Self::new(ErrorEnum::IoError(
63            ioError::new(kind, msg.into()),
64            format_path(path.as_ref().to_string_lossy().to_string()),
65        ));
66    }
67
68    pub fn not_a_directory<M, P>(msg: M, path: P) -> Self
69    where
70        M: Into<String>,
71        P: AsRef<Path>,
72    {
73        return Self::new(ErrorEnum::NotADirectory(
74            msg.into(),
75            path.as_ref().to_string_lossy().to_string(),
76        ));
77    }
78}
79
80impl std::fmt::Display for Error {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        self.source.fmt(f)
83    }
84}
85
86impl std::error::Error for Error {
87    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
88        return self.source.source();
89    }
90}
91
92// implement all From<> variants that ErrorInner also implements
93impl<T> From<T> for Error
94where
95    T: Into<ErrorEnum>,
96{
97    fn from(value: T) -> Self {
98        Self::new(value.into())
99    }
100}
101
102/// Error type for "yt-downloader-rust", implements all Error types that could happen in this lib
103#[derive(thiserror::Error, Debug)]
104pub enum ErrorEnum {
105    /// Wrapper Variant for [`std::io::Error`]
106    /// Argument 1 (String) is up to the implementation to set, commonly the path
107    #[error("IoError: {0}; {1}")]
108    IoError(std::io::Error, String),
109    /// Variant for when a directory path was expected but did not exist yet or was not a directory
110    /// TODO: replace with io::ErrorKind::NotADirectory once stable <https://github.com/rust-lang/rust/issues/86442>
111    #[error("NotADirectory: {0}; Path: \"{1}\"")]
112    NotADirectory(String, String),
113    /// Variant for unsupported diesel schema formats
114    #[error("UnsupportedSchemaFormat: {0}")]
115    UnsupportedSchemaFormat(String),
116    /// Variant for unsupported sql types
117    #[error("UnsupportedType: {0}")]
118    UnsupportedType(String),
119    /// Variant for when "has_file_signature" is `false`
120    #[error("NoFileSignature: {0}")]
121    NoFileSignature(String),
122
123    /// Variant for Other messages
124    #[error("Other: {0}")]
125    Other(String),
126}
127
128/// Helper function to keep consistent formatting
129#[inline]
130fn format_path(msg: String) -> String {
131    format!("Path \"{}\"", msg)
132}
133
134/// Trait to map [std::io::Error] into [Error]
135pub trait IOErrorToError<T> {
136    /// Map a [std::io::Error] to [Error] with a [std::path::Path] attached
137    fn attach_path_err<P: AsRef<Path>>(self, path: P) -> Result<T>;
138
139    /// Map a [std::io::Error] to [Error] with a [std::path::Path] and message attached
140    fn attach_path_msg<P: AsRef<Path>, M: AsRef<str>>(self, path: P, msg: M) -> Result<T>;
141}
142
143impl<T> IOErrorToError<T> for std::result::Result<T, std::io::Error> {
144    fn attach_path_err<P: AsRef<Path>>(self, path: P) -> Result<T> {
145        return match self {
146            Ok(v) => Ok(v),
147            Err(e) => Err(crate::Error::new(ErrorEnum::IoError(
148                e,
149                format_path(path.as_ref().to_string_lossy().to_string()),
150            ))),
151        };
152    }
153
154    fn attach_path_msg<P: AsRef<Path>, M: AsRef<str>>(self, path: P, msg: M) -> Result<T> {
155        match self {
156            Ok(v) => Ok(v),
157            Err(e) => Err(crate::Error::new(ErrorEnum::IoError(
158                e,
159                format!(
160                    "{msg} {path}",
161                    msg = msg.as_ref(),
162                    path = format_path(path.as_ref().to_string_lossy().to_string())
163                ),
164            ))),
165        }
166    }
167}