#[derive(Debug, thiserror::Error)] pub enum Kind { #[error("unable to decode: {0}")] Decode(#[from] rodio::decoder::DecoderError), #[error("invalid name")] InvalidName, #[error("invalid file path")] InvalidPath, #[error("unknown target track length")] UnknownLength, #[error("unable to read file: {0}")] File(#[from] std::io::Error), #[error("unable to fetch data: {0}")] Request(#[from] reqwest::Error), } #[derive(Debug, thiserror::Error)] #[error("{kind} (track: {track})")] pub struct Error { pub track: String, #[source] pub kind: Kind, } impl Error { pub fn is_timeout(&self) -> bool { if let Kind::Request(x) = &self.kind { x.is_timeout() } else { false } } } impl From<(T, E)> for Error where T: Into, Kind: From, { fn from((track, err): (T, E)) -> Self { Error { track: track.into(), kind: Kind::from(err), } } } pub trait Context { fn track(self, name: impl Into) -> Result; } impl Context for Result where (String, E): Into, E: Into, { fn track(self, name: impl Into) -> Result { self.map_err(|e| { let error = match e.into() { Kind::Request(e) => Kind::Request(e.without_url()), e => e, }; (name.into(), error).into() }) } }