1use thiserror::Error;
2
3#[derive(Debug, Error)]
5pub enum SdkError {
6 #[error("Authentication failed: {0}")]
7 Auth(String),
8
9 #[error("Access denied: {resource}/{action}")]
10 AccessDenied { resource: String, action: String },
11
12 #[error("{entity_type} not found: {entity_id}")]
13 NotFound { entity_type: String, entity_id: String },
14
15 #[error("Rate limited; retry after {retry_after_ms}ms")]
16 RateLimit { retry_after_ms: u64 },
17
18 #[error("Service unavailable: {0}")]
19 Unavailable(String),
20
21 #[error("Request timed out after {timeout_ms}ms")]
22 Timeout { timeout_ms: u64 },
23
24 #[error("Validation error on field `{field}`: {constraint}")]
25 Validation { field: String, constraint: String },
26
27 #[error("Configuration error: {0}")]
28 Config(String),
29
30 #[error("HTTP error {status}: {body}")]
31 Http { status: u16, body: String },
32
33 #[error("JSON error: {0}")]
34 Serialization(#[from] serde_json::Error),
35
36 #[error("Invalid URL: {0}")]
37 InvalidUrl(#[from] url::ParseError),
38
39 #[error("Internal error: {0}")]
40 Internal(String),
41
42 #[cfg(feature = "http")]
43 #[error("Reqwest error: {0}")]
44 Reqwest(#[from] reqwest::Error),
45}
46
47pub type SdkResult<T> = Result<T, SdkError>;
49
50impl SdkError {
51 pub fn is_retriable(&self) -> bool {
53 matches!(self, SdkError::Unavailable(_) | SdkError::RateLimit { .. })
54 }
55
56 pub fn from_http(status: u16, body: &str) -> Self {
58 match status {
59 401 | 403 => SdkError::Auth(body.to_string()),
60 404 => SdkError::NotFound {
61 entity_type: "entity".into(),
62 entity_id: "unknown".into(),
63 },
64 429 => SdkError::RateLimit { retry_after_ms: 1000 },
65 503 => SdkError::Unavailable(body.to_string()),
66 408 | 504 => SdkError::Timeout { timeout_ms: 30000 },
67 400 | 422 => SdkError::Validation {
68 field: "input".into(),
69 constraint: body.to_string(),
70 },
71 _ => SdkError::Http {
72 status,
73 body: body.to_string(),
74 },
75 }
76 }
77}