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 BackendUnsupported,
10 CrossVolume,
11 ForeignHome,
13 NoMatchingPostgres,
14 MissingExtension,
15 TemplateNotFound,
16 TemplateBuildFailed,
17 CloneNotFound,
18 CapReached,
19 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}