Skip to main content

pipedash_plugin_api/
error.rs

1use std::time::Duration;
2
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6#[non_exhaustive]
7pub enum PluginError {
8    #[error("Authentication failed: {0}")]
9    AuthenticationFailed(String),
10
11    #[error("Permission denied: {0}")]
12    PermissionDenied(String),
13
14    #[error("API error: {0}")]
15    ApiError(String),
16
17    #[error("Invalid configuration: {0}")]
18    InvalidConfig(String),
19
20    #[error("Pipeline not found: {0}")]
21    PipelineNotFound(String),
22
23    #[error("Provider not supported: {0}")]
24    ProviderNotSupported(String),
25
26    #[error("Network error: {0}")]
27    NetworkError(String),
28
29    #[error("Serialization error: {0}")]
30    SerializationError(String),
31
32    #[error("Database error: {0}")]
33    DatabaseError(String),
34
35    #[error("Operation not supported: {0}")]
36    NotSupported(String),
37
38    #[error("Internal error: {0}")]
39    Internal(String),
40
41    #[error("Rate limited: {message}")]
42    RateLimited {
43        message: String,
44        retry_after: Option<Duration>,
45    },
46
47    #[error("Service unavailable: {message}")]
48    ServiceUnavailable {
49        message: String,
50        retry_after: Option<Duration>,
51    },
52
53    #[error("TLS error: {0}")]
54    Tls(String),
55}
56
57pub type PluginResult<T> = Result<T, PluginError>;
58
59impl PluginError {
60    pub fn is_transient(&self) -> bool {
61        matches!(
62            self,
63            Self::NetworkError(_) | Self::ServiceUnavailable { .. } | Self::RateLimited { .. }
64        )
65    }
66
67    pub fn retry_after(&self) -> Option<Duration> {
68        match self {
69            Self::RateLimited { retry_after, .. }
70            | Self::ServiceUnavailable { retry_after, .. } => *retry_after,
71            _ => None,
72        }
73    }
74}
75
76impl From<serde_json::Error> for PluginError {
77    fn from(err: serde_json::Error) -> Self {
78        PluginError::SerializationError(err.to_string())
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn transient_covers_network_unavailable_and_rate_limit_only() {
88        assert!(PluginError::NetworkError("x".into()).is_transient());
89        assert!(PluginError::RateLimited {
90            message: "x".into(),
91            retry_after: None
92        }
93        .is_transient());
94        assert!(PluginError::ServiceUnavailable {
95            message: "x".into(),
96            retry_after: None
97        }
98        .is_transient());
99        assert!(!PluginError::ApiError("x".into()).is_transient());
100        assert!(!PluginError::AuthenticationFailed("x".into()).is_transient());
101        assert!(!PluginError::Tls("x".into()).is_transient());
102    }
103}