1use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Error, Debug)]
13pub enum Error {
14 #[error("Failed to connect to Kubernetes cluster: {0}")]
16 ConnectionError(String),
17
18 #[error("Configuration error: {0}")]
20 ConfigurationError(String),
21
22 #[error("Deployment failed: {0}")]
24 DeploymentError(String),
25
26 #[error("Rollback failed: {0}")]
28 RollbackError(String),
29
30 #[error("Scaling failed: {0}")]
32 ScalingError(String),
33
34 #[error("Service operation failed: {0}")]
36 ServiceError(String),
37
38 #[error("Secret operation failed: {0}")]
40 SecretError(String),
41
42 #[error("Namespace operation failed: {0}")]
44 NamespaceError(String),
45
46 #[error("ConfigMap operation failed: {0}")]
48 ConfigMapError(String),
49
50 #[error("Ingress operation failed: {0}")]
52 IngressError(String),
53
54 #[error("Gateway operation failed: {0}")]
56 GatewayError(String),
57
58 #[error("Validation error: {0}")]
60 ValidationError(String),
61
62 #[error("NetworkPolicy operation failed: {0}")]
64 NetworkPolicyError(String),
65
66 #[error("Job operation failed: {0}")]
68 JobError(String),
69
70 #[error("CronJob operation failed: {0}")]
72 CronJobError(String),
73
74 #[error("PersistentVolumeClaim operation failed: {0}")]
76 PvcError(String),
77
78 #[error("HorizontalPodAutoscaler operation failed: {0}")]
80 HpaError(String),
81
82 #[error("Resource not found: {kind} '{name}' in namespace '{namespace}'")]
84 ResourceNotFound {
85 kind: String,
87 name: String,
89 namespace: String,
91 },
92
93 #[error("Resource already exists: {kind} '{name}' in namespace '{namespace}'")]
95 ResourceAlreadyExists {
96 kind: String,
98 name: String,
100 namespace: String,
102 },
103
104 #[error("Timeout waiting for {resource} to be ready after {timeout_secs}s: {details}")]
106 Timeout {
107 resource: String,
109 timeout_secs: u64,
111 details: String,
113 },
114
115 #[error("Pod '{pod_name}' failed: {reason}")]
117 PodFailed {
118 pod_name: String,
120 reason: String,
122 },
123
124 #[error(
126 "Container '{container}' in pod '{pod}' crashed: exit code {exit_code}, restarts: {restarts}"
127 )]
128 ContainerCrashed {
129 pod: String,
131 container: String,
133 exit_code: i32,
135 restarts: i32,
137 },
138
139 #[error("Failed to pull image '{image}': {reason}")]
141 ImagePullError {
142 image: String,
144 reason: String,
146 },
147
148 #[error("Invalid resource specification: {0}")]
150 InvalidSpec(String),
151
152 #[error("Serialization error: {0}")]
154 SerializationError(String),
155
156 #[error("Kubernetes API error: {0}")]
158 KubeError(#[from] kube::Error),
159
160 #[error("Internal error: {0}")]
162 Internal(String),
163}
164
165impl Error {
166 pub fn connection<S: Into<String>>(msg: S) -> Self {
168 Self::ConnectionError(msg.into())
169 }
170
171 pub fn config<S: Into<String>>(msg: S) -> Self {
173 Self::ConfigurationError(msg.into())
174 }
175
176 pub fn deployment<S: Into<String>>(msg: S) -> Self {
178 Self::DeploymentError(msg.into())
179 }
180
181 pub fn rollback<S: Into<String>>(msg: S) -> Self {
183 Self::RollbackError(msg.into())
184 }
185
186 pub fn scaling<S: Into<String>>(msg: S) -> Self {
188 Self::ScalingError(msg.into())
189 }
190
191 pub fn service<S: Into<String>>(msg: S) -> Self {
193 Self::ServiceError(msg.into())
194 }
195
196 pub fn secret<S: Into<String>>(msg: S) -> Self {
198 Self::SecretError(msg.into())
199 }
200
201 pub fn namespace<S: Into<String>>(msg: S) -> Self {
203 Self::NamespaceError(msg.into())
204 }
205
206 pub fn configmap<S: Into<String>>(msg: S) -> Self {
208 Self::ConfigMapError(msg.into())
209 }
210
211 pub fn ingress<S: Into<String>>(msg: S) -> Self {
213 Self::IngressError(msg.into())
214 }
215
216 pub fn gateway<S: Into<String>>(msg: S) -> Self {
218 Self::GatewayError(msg.into())
219 }
220
221 pub fn validation<S: Into<String>>(msg: S) -> Self {
223 Self::ValidationError(msg.into())
224 }
225
226 pub fn network_policy<S: Into<String>>(msg: S) -> Self {
228 Self::NetworkPolicyError(msg.into())
229 }
230
231 pub fn job<S: Into<String>>(msg: S) -> Self {
233 Self::JobError(msg.into())
234 }
235
236 pub fn cronjob<S: Into<String>>(msg: S) -> Self {
238 Self::CronJobError(msg.into())
239 }
240
241 pub fn pvc<S: Into<String>>(msg: S) -> Self {
243 Self::PvcError(msg.into())
244 }
245
246 pub fn hpa<S: Into<String>>(msg: S) -> Self {
248 Self::HpaError(msg.into())
249 }
250
251 pub fn not_found<S: Into<String>>(kind: S, name: S, namespace: S) -> Self {
253 Self::ResourceNotFound {
254 kind: kind.into(),
255 name: name.into(),
256 namespace: namespace.into(),
257 }
258 }
259
260 pub fn already_exists<S: Into<String>>(kind: S, name: S, namespace: S) -> Self {
262 Self::ResourceAlreadyExists {
263 kind: kind.into(),
264 name: name.into(),
265 namespace: namespace.into(),
266 }
267 }
268
269 pub fn timeout<S: Into<String>>(resource: S, timeout_secs: u64, details: S) -> Self {
271 Self::Timeout {
272 resource: resource.into(),
273 timeout_secs,
274 details: details.into(),
275 }
276 }
277
278 pub fn pod_failed<S: Into<String>>(pod_name: S, reason: S) -> Self {
280 Self::PodFailed {
281 pod_name: pod_name.into(),
282 reason: reason.into(),
283 }
284 }
285
286 pub fn container_crashed<S: Into<String>>(
288 pod: S,
289 container: S,
290 exit_code: i32,
291 restarts: i32,
292 ) -> Self {
293 Self::ContainerCrashed {
294 pod: pod.into(),
295 container: container.into(),
296 exit_code,
297 restarts,
298 }
299 }
300
301 pub fn image_pull<S1: Into<String>, S2: Into<String>>(image: S1, reason: S2) -> Self {
303 Self::ImagePullError {
304 image: image.into(),
305 reason: reason.into(),
306 }
307 }
308
309 pub fn invalid_spec<S: Into<String>>(msg: S) -> Self {
311 Self::InvalidSpec(msg.into())
312 }
313
314 pub fn internal<S: Into<String>>(msg: S) -> Self {
316 Self::Internal(msg.into())
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_error_construction() {
326 let err = Error::connection("test connection failed");
327 assert!(err.to_string().contains("test connection failed"));
328
329 let err = Error::not_found("Deployment", "my-app", "default");
330 assert!(err.to_string().contains("my-app"));
331 assert!(err.to_string().contains("default"));
332
333 let err = Error::timeout("deployment/my-app", 120, "pods not ready");
334 assert!(err.to_string().contains("120s"));
335 }
336
337 #[test]
338 fn test_container_crashed_error() {
339 let err = Error::container_crashed("my-pod", "app", 137, 3);
340 let msg = err.to_string();
341 assert!(msg.contains("my-pod"));
342 assert!(msg.contains("app"));
343 assert!(msg.contains("137"));
344 assert!(msg.contains("3"));
345 }
346
347 #[test]
348 fn test_image_pull_error() {
349 let err = Error::image_pull("registry.io/myapp:v1", "ImagePullBackOff");
350 let msg = err.to_string();
351 assert!(msg.contains("registry.io/myapp:v1"));
352 assert!(msg.contains("ImagePullBackOff"));
353 }
354}