adk_rust_mcp_common/
error.rs1use thiserror::Error;
18
19#[derive(Debug, Error)]
24pub enum Error {
25 #[error(transparent)]
27 Config(#[from] ConfigError),
28
29 #[error(transparent)]
31 Gcs(#[from] GcsError),
32
33 #[error(transparent)]
35 Auth(#[from] AuthError),
36
37 #[error("API error for {endpoint} (HTTP {status_code}): {message}")]
42 Api {
43 endpoint: String,
45 status_code: u16,
47 message: String,
49 },
50
51 #[error("Validation error: {0}")]
53 Validation(String),
54
55 #[error(transparent)]
57 Io(#[from] std::io::Error),
58
59 #[error("FFmpeg error: {0}")]
61 Ffmpeg(String),
62
63 #[error("Operation timed out after {0} seconds")]
65 Timeout(u64),
66}
67
68impl Error {
69 pub fn api(endpoint: impl Into<String>, status_code: u16, message: impl Into<String>) -> Self {
91 Error::Api {
92 endpoint: endpoint.into(),
93 status_code,
94 message: message.into(),
95 }
96 }
97
98 pub fn validation(message: impl Into<String>) -> Self {
109 Error::Validation(message.into())
110 }
111
112 pub fn ffmpeg(message: impl Into<String>) -> Self {
123 Error::Ffmpeg(message.into())
124 }
125
126 pub fn timeout(seconds: u64) -> Self {
137 Error::Timeout(seconds)
138 }
139}
140
141#[derive(Debug, Error)]
146pub enum ConfigError {
147 #[error("Required environment variable {0} is not set")]
149 MissingEnvVar(String),
150
151 #[error("Invalid value for {0}: {1}")]
153 InvalidValue(String, String),
154}
155
156impl ConfigError {
157 pub fn missing_env_var(name: impl Into<String>) -> Self {
159 ConfigError::MissingEnvVar(name.into())
160 }
161
162 pub fn invalid_value(name: impl Into<String>, reason: impl Into<String>) -> Self {
164 ConfigError::InvalidValue(name.into(), reason.into())
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum GcsOperation {
171 Upload,
173 Download,
175 Exists,
177 Delete,
179}
180
181impl std::fmt::Display for GcsOperation {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 match self {
184 GcsOperation::Upload => write!(f, "upload"),
185 GcsOperation::Download => write!(f, "download"),
186 GcsOperation::Exists => write!(f, "exists"),
187 GcsOperation::Delete => write!(f, "delete"),
188 }
189 }
190}
191
192#[derive(Debug, Error)]
197pub enum GcsError {
198 #[error("Invalid GCS URI: {0}")]
200 InvalidUri(String),
201
202 #[error("GCS {operation} failed for {uri}: {message}")]
204 OperationFailed {
205 uri: String,
207 operation: GcsOperation,
209 message: String,
211 },
212
213 #[error("GCS authentication error: {0}")]
215 AuthError(String),
216}
217
218impl GcsError {
219 pub fn invalid_uri(uri: impl Into<String>) -> Self {
221 GcsError::InvalidUri(uri.into())
222 }
223
224 pub fn operation_failed(
246 uri: impl Into<String>,
247 operation: GcsOperation,
248 message: impl Into<String>,
249 ) -> Self {
250 GcsError::OperationFailed {
251 uri: uri.into(),
252 operation,
253 message: message.into(),
254 }
255 }
256
257 pub fn auth_error(message: impl Into<String>) -> Self {
259 GcsError::AuthError(message.into())
260 }
261}
262
263#[derive(Debug, Error)]
268pub enum AuthError {
269 #[error("ADC not configured. Run 'gcloud auth application-default login' or set GOOGLE_APPLICATION_CREDENTIALS")]
271 NotConfigured,
272
273 #[error("Token refresh failed: {0}")]
275 RefreshFailed(String),
276}
277
278impl AuthError {
279 pub fn refresh_failed(message: impl Into<String>) -> Self {
281 AuthError::RefreshFailed(message.into())
282 }
283}
284
285pub type Result<T> = std::result::Result<T, Error>;
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_api_error_includes_endpoint_and_status() {
294 let err = Error::api("https://vertex.googleapis.com/v1/generate", 500, "Internal error");
295 let msg = err.to_string();
296 assert!(msg.contains("vertex.googleapis.com"), "Should contain endpoint");
297 assert!(msg.contains("500"), "Should contain status code");
298 assert!(msg.contains("Internal error"), "Should contain message");
299 }
300
301 #[test]
302 fn test_gcs_error_includes_uri_and_operation() {
303 let err = GcsError::operation_failed(
304 "gs://my-bucket/path/file.txt",
305 GcsOperation::Upload,
306 "Access denied",
307 );
308 let msg = err.to_string();
309 assert!(msg.contains("gs://my-bucket"), "Should contain URI");
310 assert!(msg.contains("upload"), "Should contain operation type");
311 assert!(msg.contains("Access denied"), "Should contain message");
312 }
313
314 #[test]
315 fn test_config_error_includes_var_name() {
316 let err = ConfigError::missing_env_var("PROJECT_ID");
317 let msg = err.to_string();
318 assert!(msg.contains("PROJECT_ID"), "Should contain variable name");
319 }
320
321 #[test]
322 fn test_error_from_config_error() {
323 let config_err = ConfigError::missing_env_var("TEST_VAR");
324 let err: Error = config_err.into();
325 assert!(matches!(err, Error::Config(_)));
326 }
327
328 #[test]
329 fn test_error_from_gcs_error() {
330 let gcs_err = GcsError::invalid_uri("invalid://uri");
331 let err: Error = gcs_err.into();
332 assert!(matches!(err, Error::Gcs(_)));
333 }
334
335 #[test]
336 fn test_error_from_auth_error() {
337 let auth_err = AuthError::NotConfigured;
338 let err: Error = auth_err.into();
339 assert!(matches!(err, Error::Auth(_)));
340 }
341
342 #[test]
343 fn test_error_from_io_error() {
344 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
345 let err: Error = io_err.into();
346 assert!(matches!(err, Error::Io(_)));
347 }
348
349 #[test]
350 fn test_timeout_error() {
351 let err = Error::timeout(300);
352 let msg = err.to_string();
353 assert!(msg.contains("300"), "Should contain timeout duration");
354 assert!(msg.contains("seconds"), "Should mention seconds");
355 }
356
357 #[test]
358 fn test_ffmpeg_error() {
359 let err = Error::ffmpeg("Invalid codec");
360 let msg = err.to_string();
361 assert!(msg.contains("FFmpeg"), "Should mention FFmpeg");
362 assert!(msg.contains("Invalid codec"), "Should contain message");
363 }
364
365 #[test]
366 fn test_validation_error() {
367 let err = Error::validation("prompt too long");
368 let msg = err.to_string();
369 assert!(msg.contains("Validation"), "Should mention validation");
370 assert!(msg.contains("prompt too long"), "Should contain message");
371 }
372
373 #[test]
374 fn test_gcs_operation_display() {
375 assert_eq!(GcsOperation::Upload.to_string(), "upload");
376 assert_eq!(GcsOperation::Download.to_string(), "download");
377 assert_eq!(GcsOperation::Exists.to_string(), "exists");
378 assert_eq!(GcsOperation::Delete.to_string(), "delete");
379 }
380}