mecomp_core/
errors.rs

1use std::{fmt::Debug, path::PathBuf};
2
3use mecomp_storage::errors::Error;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7/// An error in the UDP stack.
8#[derive(Error, Debug)]
9#[cfg(feature = "rpc")]
10pub enum UdpError {
11    #[error("IO error: {0}")]
12    IO(#[from] std::io::Error),
13    #[error("Ciborium deserialization error: {0}")]
14    CiboriumDeserialization(#[from] ciborium::de::Error<std::io::Error>),
15    #[error("Ciborium serialization error: {0}")]
16    CiboriumSerialization(#[from] ciborium::ser::Error<std::io::Error>),
17}
18
19/// Errors that can occur with finding the config or data directories.
20#[derive(Error, Debug)]
21pub enum DirectoryError {
22    #[error("Unable to find the config directory for mecomp.")]
23    Config,
24    #[error("Unable to find the data directory for mecomp.")]
25    Data,
26}
27
28/// Errors that can occur when importing or exporting playlists or dynamic playlists
29#[derive(Error, Debug, Deserialize, Serialize, PartialEq, Eq)]
30pub enum BackupError {
31    #[error("The file \"{0}\" does not exist")]
32    FileNotFound(PathBuf),
33    #[error("The file \"{0}\" has the wrong extension, expected: {1}")]
34    WrongExtension(PathBuf, String),
35    #[error("{0} is a directory, not a file")]
36    PathIsDirectory(PathBuf),
37    #[error("CSV error: {0}")]
38    CsvError(String),
39    #[error("IO Error: {0}")]
40    IoError(String),
41    #[error("Invalid playlist format")]
42    InvalidDynamicPlaylistFormat,
43    #[error("Error parsing dynamic playlist query in record {1}: {0}")]
44    InvalidDynamicPlaylistQuery(String, usize),
45    #[error("Error parsing playlist name in line {0}, name is empty or already set")]
46    PlaylistNameInvalidOrAlreadySet(usize),
47    #[error(
48        "Out of {0} entries, no valid songs were found in the playlist, consult the logs for more information"
49    )]
50    NoValidSongs(usize),
51    #[error("No valid playlists were found in the csv file.")]
52    NoValidPlaylists,
53}
54
55impl From<csv::Error> for BackupError {
56    #[inline]
57    fn from(value: csv::Error) -> Self {
58        Self::CsvError(format!("{value}"))
59    }
60}
61
62impl From<std::io::Error> for BackupError {
63    #[inline]
64    fn from(e: std::io::Error) -> Self {
65        Self::IoError(e.to_string())
66    }
67}
68
69/// Errors that can occur with the library.
70#[derive(Error, Debug)]
71pub enum LibraryError {
72    #[error("Database error: {0}")]
73    Database(#[from] Error),
74    #[error("IO error: {0}")]
75    IO(#[from] std::io::Error),
76    #[error("Decoder error: {0}")]
77    #[cfg(feature = "audio")]
78    Decoder(#[from] rodio::decoder::DecoderError),
79    #[error("UdpError: {0}")]
80    #[cfg(feature = "rpc")]
81    Udp(#[from] UdpError),
82}
83
84#[derive(Error, Debug, Deserialize, Serialize, PartialEq, Eq)]
85pub enum SerializableLibraryError {
86    #[error("Database error: {0}")]
87    Database(String),
88    #[error("IO error: {0}")]
89    IO(String),
90    #[error("Decoder error: {0}")]
91    Decoder(String),
92    #[error("Library Rescan already in progress.")]
93    RescanInProgress,
94    #[error("Library Analysis already in progress.")]
95    AnalysisInProgress,
96    #[error("Collection Reclustering already in progress.")]
97    ReclusterInProgress,
98    #[error("UdpError: {0}")]
99    #[cfg(feature = "rpc")]
100    Udp(String),
101    #[error("Backup Error: {0}")]
102    BackupError(#[from] BackupError),
103}
104
105impl From<Error> for SerializableLibraryError {
106    #[inline]
107    fn from(e: Error) -> Self {
108        Self::Database(e.to_string())
109    }
110}
111
112impl From<std::io::Error> for SerializableLibraryError {
113    #[inline]
114    fn from(e: std::io::Error) -> Self {
115        Self::IO(e.to_string())
116    }
117}
118
119#[cfg(feature = "audio")]
120impl From<rodio::decoder::DecoderError> for SerializableLibraryError {
121    #[inline]
122    fn from(e: rodio::decoder::DecoderError) -> Self {
123        Self::Decoder(e.to_string())
124    }
125}
126
127#[cfg(feature = "rpc")]
128impl From<UdpError> for SerializableLibraryError {
129    #[inline]
130    fn from(e: UdpError) -> Self {
131        Self::Udp(e.to_string())
132    }
133}
134
135impl From<LibraryError> for SerializableLibraryError {
136    #[inline]
137    fn from(e: LibraryError) -> Self {
138        match e {
139            LibraryError::Database(e) => Self::Database(e.to_string()),
140            LibraryError::IO(e) => Self::IO(e.to_string()),
141            #[cfg(feature = "audio")]
142            LibraryError::Decoder(e) => Self::Decoder(e.to_string()),
143            #[cfg(feature = "rpc")]
144            LibraryError::Udp(e) => Self::Udp(e.to_string()),
145        }
146    }
147}
148
149/// An error that occurs connecting a client to the daemon
150#[derive(Error, Debug)]
151#[error("failed to connect to daemon on port {port} after {retries} retries")]
152pub struct ConnectionError {
153    pub port: u16,
154    pub retries: u64,
155}
156
157impl ConnectionError {
158    #[inline]
159    #[must_use]
160    pub const fn new(port: u16, retries: u64) -> Self {
161        Self { port, retries }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use pretty_assertions::assert_str_eq;
169    use rstest::rstest;
170
171    #[rstest]
172    #[case(
173        LibraryError::from(Error::NoId),
174        "Database error: Item is missing an Id."
175    )]
176    #[case(LibraryError::from(std::io::Error::other("test")), "IO error: test")]
177    #[case(
178        LibraryError::from(rodio::decoder::DecoderError::DecodeError("test")),
179        "Decoder error: test"
180    )]
181    fn test_serializable_library_error(#[case] input: LibraryError, #[case] expected: String) {
182        let actual = SerializableLibraryError::from(input).to_string();
183        assert_str_eq!(actual, expected);
184    }
185
186    #[rstest]
187    #[case(Error::NoId, LibraryError::Database(Error::NoId).into())]
188    #[case(std::io::Error::other("test"), LibraryError::IO(std::io::Error::other("test")).into())]
189    #[case(rodio::decoder::DecoderError::DecodeError("test"), LibraryError::Decoder(rodio::decoder::DecoderError::DecodeError("test")).into())]
190    fn test_serializable_library_error_from<T: Into<SerializableLibraryError>>(
191        #[case] from: T,
192        #[case] to: SerializableLibraryError,
193    ) {
194        let actual = from.into();
195        assert_eq!(actual, to);
196    }
197}