1use thiserror::Error;
4
5#[derive(Debug, Error)]
11pub enum CommonError {
12 #[error("I/O error: {0}")]
17 Io(#[from] std::io::Error),
18
19 #[error("configuration error: {0}")]
24 Config(String),
25
26 #[error("not found: {0}")]
31 NotFound(String),
32
33 #[error("already exists: {0}")]
37 AlreadyExists(String),
38
39 #[error("invalid state: {0}")]
45 InvalidState(String),
46
47 #[error("timeout: {0}")]
51 Timeout(String),
52
53 #[error("permission denied: {0}")]
57 PermissionDenied(String),
58
59 #[error("internal error: {0}")]
64 Internal(String),
65}
66
67impl CommonError {
68 #[must_use]
70 pub fn config(msg: impl Into<String>) -> Self {
71 Self::Config(msg.into())
72 }
73
74 #[must_use]
76 pub fn not_found(resource: impl Into<String>) -> Self {
77 Self::NotFound(resource.into())
78 }
79
80 #[must_use]
82 pub fn already_exists(resource: impl Into<String>) -> Self {
83 Self::AlreadyExists(resource.into())
84 }
85
86 #[must_use]
88 pub fn invalid_state(msg: impl Into<String>) -> Self {
89 Self::InvalidState(msg.into())
90 }
91
92 #[must_use]
94 pub fn timeout(msg: impl Into<String>) -> Self {
95 Self::Timeout(msg.into())
96 }
97
98 #[must_use]
100 pub fn permission_denied(resource: impl Into<String>) -> Self {
101 Self::PermissionDenied(resource.into())
102 }
103
104 #[must_use]
106 pub fn internal(msg: impl Into<String>) -> Self {
107 Self::Internal(msg.into())
108 }
109
110 #[must_use]
112 pub const fn is_io(&self) -> bool {
113 matches!(self, Self::Io(_))
114 }
115
116 #[must_use]
118 pub const fn is_not_found(&self) -> bool {
119 matches!(self, Self::NotFound(_))
120 }
121
122 #[must_use]
124 pub const fn is_already_exists(&self) -> bool {
125 matches!(self, Self::AlreadyExists(_))
126 }
127
128 #[must_use]
130 pub const fn is_timeout(&self) -> bool {
131 matches!(self, Self::Timeout(_))
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_io_error_conversion() {
141 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
142 let common_err: CommonError = io_err.into();
143 assert!(common_err.is_io());
144 assert!(common_err.to_string().contains("I/O error"));
145 }
146
147 #[test]
148 fn test_not_found_error() {
149 let err = CommonError::not_found("container abc123");
150 assert!(err.is_not_found());
151 assert_eq!(err.to_string(), "not found: container abc123");
152 }
153
154 #[test]
155 fn test_already_exists_error() {
156 let err = CommonError::already_exists("network bridge0");
157 assert!(err.is_already_exists());
158 assert_eq!(err.to_string(), "already exists: network bridge0");
159 }
160
161 #[test]
162 fn test_config_error() {
163 let err = CommonError::config("invalid port number");
164 assert_eq!(err.to_string(), "configuration error: invalid port number");
165 }
166
167 #[test]
168 fn test_invalid_state_error() {
169 let err = CommonError::invalid_state("container is not running");
170 assert_eq!(err.to_string(), "invalid state: container is not running");
171 }
172
173 #[test]
174 fn test_timeout_error() {
175 let err = CommonError::timeout("connection timed out after 30s");
176 assert!(err.is_timeout());
177 assert_eq!(err.to_string(), "timeout: connection timed out after 30s");
178 }
179
180 #[test]
181 fn test_permission_denied_error() {
182 let err = CommonError::permission_denied("/var/run/docker.sock");
183 assert_eq!(err.to_string(), "permission denied: /var/run/docker.sock");
184 }
185
186 #[test]
187 fn test_internal_error() {
188 let err = CommonError::internal("unexpected null pointer");
189 assert_eq!(err.to_string(), "internal error: unexpected null pointer");
190 }
191}