hemmer_provider_sdk/
error.rs

1//! Error types for the Hemmer Provider SDK.
2
3use thiserror::Error;
4
5/// Errors that can occur when implementing a provider.
6#[derive(Debug, Error)]
7pub enum ProviderError {
8    /// The requested resource was not found.
9    #[error("Resource not found: {0}")]
10    NotFound(String),
11
12    /// A validation error occurred.
13    #[error("Validation error: {0}")]
14    Validation(String),
15
16    /// An internal SDK error occurred.
17    #[error("SDK error: {0}")]
18    Sdk(String),
19
20    /// A configuration error occurred.
21    #[error("Configuration error: {0}")]
22    Configuration(String),
23
24    /// The requested resource type is unknown.
25    #[error("Unknown resource type: {0}")]
26    UnknownResource(String),
27
28    /// A serialization/deserialization error occurred.
29    #[error("Serialization error: {0}")]
30    Serialization(#[from] serde_json::Error),
31
32    /// A gRPC transport error occurred.
33    #[error("Transport error: {0}")]
34    Transport(#[from] tonic::transport::Error),
35
36    /// Resource already exists (create conflict).
37    #[error("Resource already exists: {0}")]
38    AlreadyExists(String),
39
40    /// Permission denied (authentication/authorization failure).
41    #[error("Permission denied: {0}")]
42    PermissionDenied(String),
43
44    /// Quota or rate limit exceeded.
45    #[error("Resource exhausted: {0}")]
46    ResourceExhausted(String),
47
48    /// Service temporarily unavailable.
49    #[error("Service unavailable: {0}")]
50    Unavailable(String),
51
52    /// Operation timed out.
53    #[error("Deadline exceeded: {0}")]
54    DeadlineExceeded(String),
55
56    /// Operation failed due to current state (precondition not met).
57    #[error("Failed precondition: {0}")]
58    FailedPrecondition(String),
59
60    /// Operation not implemented.
61    #[error("Unimplemented: {0}")]
62    Unimplemented(String),
63
64    /// Invalid request from client.
65    #[error("Invalid request: {0}")]
66    InvalidRequest(String),
67}
68
69impl ProviderError {
70    /// Get the error message as a string.
71    ///
72    /// Returns a reference to the error message for any variant.
73    pub fn message(&self) -> &str {
74        // thiserror's Display implementation formats the message
75        // For structured access, we match on each variant
76        match self {
77            Self::NotFound(msg) => msg,
78            Self::Validation(msg) => msg,
79            Self::Sdk(msg) => msg,
80            Self::Configuration(msg) => msg,
81            Self::UnknownResource(msg) => msg,
82            Self::Serialization(_err) => "serialization error (see Debug output)",
83            Self::Transport(_err) => "transport error (see Debug output)",
84            Self::AlreadyExists(msg) => msg,
85            Self::PermissionDenied(msg) => msg,
86            Self::ResourceExhausted(msg) => msg,
87            Self::Unavailable(msg) => msg,
88            Self::DeadlineExceeded(msg) => msg,
89            Self::FailedPrecondition(msg) => msg,
90            Self::Unimplemented(msg) => msg,
91            Self::InvalidRequest(msg) => msg,
92        }
93    }
94
95    // Compatibility aliases for generator v0.3.5
96
97    /// Alias for [`ProviderError::Configuration`] for generator compatibility.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use hemmer_provider_sdk::ProviderError;
103    ///
104    /// let err = ProviderError::ConfigurationError("invalid config".to_string());
105    /// assert_eq!(err.message(), "invalid config");
106    /// ```
107    #[allow(non_snake_case)]
108    pub fn ConfigurationError(msg: String) -> Self {
109        Self::Configuration(msg)
110    }
111
112    /// Alias for [`ProviderError::Sdk`] for generator compatibility.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use hemmer_provider_sdk::ProviderError;
118    ///
119    /// let err = ProviderError::SdkError("sdk error".to_string());
120    /// assert_eq!(err.message(), "sdk error");
121    /// ```
122    #[allow(non_snake_case)]
123    pub fn SdkError(msg: String) -> Self {
124        Self::Sdk(msg)
125    }
126}
127
128impl From<ProviderError> for tonic::Status {
129    fn from(err: ProviderError) -> Self {
130        match err {
131            ProviderError::NotFound(msg) => tonic::Status::not_found(msg),
132            ProviderError::Validation(msg) => tonic::Status::invalid_argument(msg),
133            ProviderError::Configuration(msg) => tonic::Status::failed_precondition(msg),
134            ProviderError::UnknownResource(msg) => tonic::Status::not_found(msg),
135            ProviderError::Sdk(msg) => tonic::Status::internal(msg),
136            ProviderError::Serialization(err) => {
137                tonic::Status::invalid_argument(format!("Serialization error: {}", err))
138            },
139            ProviderError::Transport(err) => {
140                tonic::Status::unavailable(format!("Transport error: {}", err))
141            },
142            ProviderError::AlreadyExists(msg) => tonic::Status::already_exists(msg),
143            ProviderError::PermissionDenied(msg) => tonic::Status::permission_denied(msg),
144            ProviderError::ResourceExhausted(msg) => tonic::Status::resource_exhausted(msg),
145            ProviderError::Unavailable(msg) => tonic::Status::unavailable(msg),
146            ProviderError::DeadlineExceeded(msg) => tonic::Status::deadline_exceeded(msg),
147            ProviderError::FailedPrecondition(msg) => tonic::Status::failed_precondition(msg),
148            ProviderError::Unimplemented(msg) => tonic::Status::unimplemented(msg),
149            ProviderError::InvalidRequest(msg) => tonic::Status::invalid_argument(msg),
150        }
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_error_display() {
160        let err = ProviderError::NotFound("resource-123".to_string());
161        assert_eq!(format!("{}", err), "Resource not found: resource-123");
162
163        let err = ProviderError::Validation("invalid input".to_string());
164        assert_eq!(format!("{}", err), "Validation error: invalid input");
165
166        let err = ProviderError::UnknownResource("custom_resource".to_string());
167        assert_eq!(format!("{}", err), "Unknown resource type: custom_resource");
168    }
169
170    #[test]
171    fn test_error_to_status() {
172        let err = ProviderError::NotFound("test".to_string());
173        let status: tonic::Status = err.into();
174        assert_eq!(status.code(), tonic::Code::NotFound);
175
176        let err = ProviderError::Validation("test".to_string());
177        let status: tonic::Status = err.into();
178        assert_eq!(status.code(), tonic::Code::InvalidArgument);
179
180        let err = ProviderError::Configuration("test".to_string());
181        let status: tonic::Status = err.into();
182        assert_eq!(status.code(), tonic::Code::FailedPrecondition);
183
184        let err = ProviderError::Sdk("test".to_string());
185        let status: tonic::Status = err.into();
186        assert_eq!(status.code(), tonic::Code::Internal);
187    }
188
189    #[test]
190    fn test_new_error_variants_display() {
191        let err = ProviderError::AlreadyExists("bucket-123".to_string());
192        assert_eq!(format!("{}", err), "Resource already exists: bucket-123");
193
194        let err = ProviderError::PermissionDenied("access forbidden".to_string());
195        assert_eq!(format!("{}", err), "Permission denied: access forbidden");
196
197        let err = ProviderError::ResourceExhausted("quota exceeded".to_string());
198        assert_eq!(format!("{}", err), "Resource exhausted: quota exceeded");
199
200        let err = ProviderError::Unavailable("service down".to_string());
201        assert_eq!(format!("{}", err), "Service unavailable: service down");
202
203        let err = ProviderError::DeadlineExceeded("timeout".to_string());
204        assert_eq!(format!("{}", err), "Deadline exceeded: timeout");
205
206        let err = ProviderError::FailedPrecondition("state mismatch".to_string());
207        assert_eq!(format!("{}", err), "Failed precondition: state mismatch");
208
209        let err = ProviderError::Unimplemented("feature not available".to_string());
210        assert_eq!(format!("{}", err), "Unimplemented: feature not available");
211    }
212
213    #[test]
214    fn test_new_error_variants_to_status() {
215        let err = ProviderError::AlreadyExists("test".to_string());
216        let status: tonic::Status = err.into();
217        assert_eq!(status.code(), tonic::Code::AlreadyExists);
218
219        let err = ProviderError::PermissionDenied("test".to_string());
220        let status: tonic::Status = err.into();
221        assert_eq!(status.code(), tonic::Code::PermissionDenied);
222
223        let err = ProviderError::ResourceExhausted("test".to_string());
224        let status: tonic::Status = err.into();
225        assert_eq!(status.code(), tonic::Code::ResourceExhausted);
226
227        let err = ProviderError::Unavailable("test".to_string());
228        let status: tonic::Status = err.into();
229        assert_eq!(status.code(), tonic::Code::Unavailable);
230
231        let err = ProviderError::DeadlineExceeded("test".to_string());
232        let status: tonic::Status = err.into();
233        assert_eq!(status.code(), tonic::Code::DeadlineExceeded);
234
235        let err = ProviderError::FailedPrecondition("test".to_string());
236        let status: tonic::Status = err.into();
237        assert_eq!(status.code(), tonic::Code::FailedPrecondition);
238
239        let err = ProviderError::Unimplemented("test".to_string());
240        let status: tonic::Status = err.into();
241        assert_eq!(status.code(), tonic::Code::Unimplemented);
242    }
243
244    #[test]
245    fn test_invalid_request_variant() {
246        let err = ProviderError::InvalidRequest("bad request".to_string());
247        assert_eq!(format!("{}", err), "Invalid request: bad request");
248
249        let status: tonic::Status = err.into();
250        assert_eq!(status.code(), tonic::Code::InvalidArgument);
251    }
252
253    #[test]
254    fn test_message_method() {
255        let err = ProviderError::NotFound("resource-123".to_string());
256        assert_eq!(err.message(), "resource-123");
257
258        let err = ProviderError::Configuration("invalid config".to_string());
259        assert_eq!(err.message(), "invalid config");
260
261        let err = ProviderError::InvalidRequest("bad request".to_string());
262        assert_eq!(err.message(), "bad request");
263    }
264
265    #[test]
266    fn test_compatibility_aliases() {
267        // Test ConfigurationError alias
268        let err = ProviderError::ConfigurationError("config error".to_string());
269        assert_eq!(err.message(), "config error");
270        assert_eq!(format!("{}", err), "Configuration error: config error");
271
272        // Test SdkError alias
273        let err = ProviderError::SdkError("sdk error".to_string());
274        assert_eq!(err.message(), "sdk error");
275        assert_eq!(format!("{}", err), "SDK error: sdk error");
276    }
277}