1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! Error types for GCP HTTP client operations.
use std::time::Duration;
use thiserror::Error;
/// Result type alias using GcpError
pub type Result<T> = std::result::Result<T, GcpError>;
/// Errors that can occur during GCP API operations
#[derive(Debug, Error, Clone)]
pub enum GcpError {
/// Authentication failed
#[error("Authentication failed: {message}")]
Auth {
/// Error message from GCP
message: String,
},
/// Permission denied for the requested resource
#[error("Permission denied: {message}")]
PermissionDenied {
/// Error message from GCP
message: String,
/// Resource that was accessed
resource: String,
/// HTTP method used
method: String,
},
/// Resource not found
#[error("Resource not found: {resource}")]
NotFound {
/// Resource that was not found
resource: String,
/// HTTP method used
method: String,
},
/// Rate limit exceeded
#[error("Rate limited (retry after {retry_after:?}s)")]
RateLimited {
/// Seconds to wait before retry (from Retry-After header)
retry_after: Option<u64>,
/// Error message from GCP
message: String,
/// Resource that was accessed
resource: String,
},
/// GCP API is not enabled
#[error("API not enabled: {api}")]
ApiNotEnabled {
/// API that needs to be enabled
api: String,
/// Error message from GCP
message: String,
},
/// Quota exceeded
#[error("Quota exceeded: {message}")]
QuotaExceeded {
/// Error message from GCP
message: String,
/// Resource that was accessed
resource: String,
},
/// Invalid argument provided
#[error("Invalid argument: {message}")]
InvalidArgument {
/// Error message from GCP
message: String,
/// Field that was invalid (if available)
field: Option<String>,
},
/// Server error from GCP
#[error("Server error ({status}): {message}")]
ServerError {
/// HTTP status code
status: u16,
/// Error message from GCP
message: String,
/// Resource that was accessed
resource: String,
/// Whether this error is retryable
retryable: bool,
},
/// Operation timed out
#[error("Operation timeout after {timeout:?}")]
OperationTimeout {
/// Operation name
operation: String,
/// Timeout duration
timeout: Duration,
},
/// Operation failed
#[error("Operation failed: {message}")]
OperationFailed {
/// Operation name
operation: String,
/// Error message
message: String,
/// GCP error code (if available)
code: Option<String>,
},
/// Network error
#[error("Network error: {0}")]
Network(String),
/// Invalid response from GCP
#[error("Invalid response: {message}")]
InvalidResponse {
/// Error description
message: String,
/// Raw response body (for debugging)
body: Option<String>,
},
}
impl From<reqwest::Error> for GcpError {
fn from(err: reqwest::Error) -> Self {
Self::Network(err.to_string())
}
}
impl GcpError {
/// Returns true if this error is retryable
pub fn is_retryable(&self) -> bool {
matches!(
self,
Self::RateLimited { .. }
| Self::ServerError {
retryable: true,
..
}
| Self::Network(_)
)
}
/// Returns true if this is an authentication error
pub fn is_auth_error(&self) -> bool {
matches!(self, Self::Auth { .. })
}
/// Extract retry-after duration if present
pub fn retry_after(&self) -> Option<Duration> {
match self {
Self::RateLimited {
retry_after: Some(secs),
..
} => Some(Duration::from_secs(*secs)),
_ => None,
}
}
/// Get HTTP status code if available
pub fn status_code(&self) -> Option<u16> {
match self {
Self::Auth { .. } => Some(401),
Self::PermissionDenied { .. } => Some(403),
Self::NotFound { .. } => Some(404),
Self::RateLimited { .. } => Some(429),
Self::ServerError { status, .. } => Some(*status),
_ => None,
}
}
}