Skip to main content

wami_core/error/
helpers.rs

1//! Error Helper Functions
2//!
3//! Provides convenient helper functions for creating common error types.
4
5use crate::error::{AmiError, Result};
6
7/// Helper functions for creating common AmiError variants
8impl AmiError {
9    /// Create a "Resource not found" error with a formatted message
10    ///
11    /// # Example
12    ///
13    /// ```rust
14    /// use wami_core::error::AmiError;
15    ///
16    /// let error = AmiError::resource_not_found("User", "alice");
17    /// assert!(error.to_string().contains("User: alice"));
18    /// ```
19    pub fn resource_not_found(resource_type: &str, resource_id: &str) -> Self {
20        AmiError::ResourceNotFound {
21            resource: format!("{}: {}", resource_type, resource_id),
22        }
23    }
24
25    /// Create a "Permission denied" error with action and resource context
26    ///
27    /// # Example
28    ///
29    /// ```rust
30    /// use wami_core::error::AmiError;
31    ///
32    /// let error = AmiError::permission_denied("delete", "User: alice");
33    /// assert!(error.to_string().contains("Cannot delete"));
34    /// ```
35    pub fn permission_denied(action: &str, resource: &str) -> Self {
36        AmiError::PermissionDenied {
37            reason: format!("Cannot {} on {}", action, resource),
38        }
39    }
40
41    /// Create an "Access denied" error with a detailed message
42    ///
43    /// # Example
44    ///
45    /// ```rust
46    /// use wami_core::error::AmiError;
47    ///
48    /// let error = AmiError::access_denied("User alice does not have permission to delete users");
49    /// ```
50    pub fn access_denied(message: impl Into<String>) -> Self {
51        AmiError::AccessDenied {
52            message: message.into(),
53        }
54    }
55
56    /// Create an "Invalid parameter" error
57    ///
58    /// # Example
59    ///
60    /// ```rust
61    /// use wami_core::error::AmiError;
62    ///
63    /// let error = AmiError::invalid_parameter("User name cannot be empty");
64    /// ```
65    pub fn invalid_parameter(message: impl Into<String>) -> Self {
66        AmiError::InvalidParameter {
67            message: message.into(),
68        }
69    }
70
71    /// Create a "Resource already exists" error
72    ///
73    /// # Example
74    ///
75    /// ```rust
76    /// use wami_core::error::AmiError;
77    ///
78    /// let error = AmiError::resource_exists("User: alice");
79    /// ```
80    pub fn resource_exists(resource: impl Into<String>) -> Self {
81        AmiError::ResourceExists {
82            resource: resource.into(),
83        }
84    }
85
86    /// Create a "Resource limit exceeded" error
87    ///
88    /// # Example
89    ///
90    /// ```rust
91    /// use wami_core::error::AmiError;
92    ///
93    /// let error = AmiError::resource_limit_exceeded("AccessKey", 2);
94    /// ```
95    pub fn resource_limit_exceeded(resource_type: &str, limit: usize) -> Self {
96        AmiError::ResourceLimitExceeded {
97            resource_type: resource_type.to_string(),
98            limit,
99        }
100    }
101
102    /// Create an "Operation not supported" error
103    ///
104    /// # Example
105    ///
106    /// ```rust
107    /// use wami_core::error::AmiError;
108    ///
109    /// let error = AmiError::operation_not_supported("batch_delete");
110    /// ```
111    pub fn operation_not_supported(operation: impl Into<String>) -> Self {
112        AmiError::OperationNotSupported {
113            operation: operation.into(),
114        }
115    }
116}
117
118/// Extension trait for Option to provide convenient error handling
119///
120/// # Example
121///
122/// ```rust
123/// use wami_core::error::{Result, OptionExt};
124///
125/// fn example() -> Result<String> {
126///     let user: Option<String> = Some("alice".to_string());
127///     let result = user.or_not_found("User", "alice")?;
128///     Ok(result)
129/// }
130/// ```
131#[allow(clippy::result_large_err)]
132pub trait OptionExt<T> {
133    /// Convert None to a ResourceNotFound error
134    fn or_not_found(self, resource_type: &str, resource_id: &str) -> Result<T>;
135}
136
137impl<T> OptionExt<T> for Option<T> {
138    fn or_not_found(self, resource_type: &str, resource_id: &str) -> Result<T> {
139        self.ok_or_else(|| AmiError::resource_not_found(resource_type, resource_id))
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_resource_not_found() {
149        let error = AmiError::resource_not_found("User", "alice");
150        assert!(matches!(error, AmiError::ResourceNotFound { .. }));
151        assert!(error.to_string().contains("User: alice"));
152    }
153
154    #[test]
155    fn test_permission_denied() {
156        let error = AmiError::permission_denied("delete", "User: alice");
157        assert!(matches!(error, AmiError::PermissionDenied { .. }));
158        assert!(error.to_string().contains("Cannot delete"));
159    }
160
161    #[test]
162    fn test_or_not_found_some() {
163        let option: Option<String> = Some("value".to_string());
164        let result = option.or_not_found("Resource", "id");
165        assert!(result.is_ok());
166        assert_eq!(result.unwrap(), "value");
167    }
168
169    #[test]
170    fn test_or_not_found_none() {
171        let option: Option<String> = None;
172        let result = option.or_not_found("User", "alice");
173        assert!(result.is_err());
174        assert!(matches!(
175            result.unwrap_err(),
176            AmiError::ResourceNotFound { .. }
177        ));
178    }
179}