Skip to main content

mitosis/
error.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
5#[serde(rename_all = "snake_case")]
6pub enum ErrorCode {
7    NotApfs,
8    /// No clone back end works for this file system or setting.
9    BackendUnsupported,
10    CrossVolume,
11    /// `MITOSIS_HOME` points at an existing folder that Mitosis did not create.
12    ForeignHome,
13    NoMatchingPostgres,
14    MissingExtension,
15    TemplateNotFound,
16    TemplateBuildFailed,
17    CloneNotFound,
18    CapReached,
19    /// A RAM or disk budget is used up.
20    BudgetFull,
21    LowDisk,
22    PortsExhausted,
23    PortInUse,
24    PostgresStartFailed,
25    AlreadyRunning,
26    DaemonUnavailable,
27    ProtocolMismatch,
28    InvalidRequest,
29    Internal,
30}
31
32#[derive(Serialize, Deserialize, Clone, Debug)]
33pub struct MitosisError {
34    pub code: ErrorCode,
35    pub message: String,
36    pub hint: Option<String>,
37}
38
39impl MitosisError {
40    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
41        MitosisError {
42            code,
43            message: message.into(),
44            hint: None,
45        }
46    }
47
48    pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
49        self.hint = Some(hint.into());
50        self
51    }
52}
53
54impl fmt::Display for MitosisError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(f, "{}", self.message)?;
57        if let Some(hint) = &self.hint {
58            write!(f, "\nhint: {hint}")?;
59        }
60        Ok(())
61    }
62}
63
64impl std::error::Error for MitosisError {}
65
66impl From<std::io::Error> for MitosisError {
67    fn from(e: std::io::Error) -> Self {
68        MitosisError::new(ErrorCode::Internal, e.to_string())
69    }
70}
71
72impl From<rusqlite::Error> for MitosisError {
73    fn from(e: rusqlite::Error) -> Self {
74        MitosisError::new(ErrorCode::Internal, e.to_string())
75    }
76}
77
78impl From<serde_json::Error> for MitosisError {
79    fn from(e: serde_json::Error) -> Self {
80        MitosisError::new(ErrorCode::Internal, e.to_string())
81    }
82}
83
84pub type Result<T> = std::result::Result<T, MitosisError>;
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn display_with_and_without_hint() {
92        let e = MitosisError::new(ErrorCode::Internal, "boom");
93        assert_eq!(e.to_string(), "boom");
94        assert_eq!(
95            e.with_hint("try again").to_string(),
96            "boom\nhint: try again"
97        );
98    }
99
100    #[test]
101    fn code_serializes_snake_case() {
102        assert_eq!(
103            serde_json::to_string(&ErrorCode::NoMatchingPostgres).unwrap(),
104            "\"no_matching_postgres\""
105        );
106    }
107}