api_claude/client/explicit_retry.rs
1//! Explicit retry builder and client monitoring types
2//!
3//! `ExplicitRetryBuilder`, `RateLimitInfo`, and `HealthStatus` for transparent
4//! client-side retry control and request monitoring.
5
6#[ allow( clippy::missing_inline_in_public_items ) ]
7mod private
8{
9 use super::super::implementation::orphan::Client;
10 #[ cfg( feature = "error-handling" ) ]
11 use crate::error::{ AnthropicError, AnthropicResult };
12 use std::time::Duration;
13
14 impl Client
15 {
16 /// Get explicit rate limit information for manual control
17 ///
18 /// # Governing Principle Compliance
19 ///
20 /// This method follows the "Thin Client, Rich API" principle by:
21 /// - **Information vs Action**: Provides rate limit data without making automatic decisions
22 /// - **Zero Automatic Behavior**: No hidden rate limiting or magic throttling
23 /// - **Explicit Control**: Developers can use rate limit information to make their own decisions
24 /// - **Transparent Operations**: All rate limit metrics are visible and accessible
25 ///
26 /// # Examples
27 ///
28 /// ```no_run
29 /// use api_claude::{ Client, Secret };
30 ///
31 /// # async fn example() -> Result< (), Box< dyn std::error::Error > > {
32 /// let secret = Secret::new( "sk-ant-api03-example".to_string() ).unwrap();
33 /// let client = Client::new( secret );
34 ///
35 /// // Check rate limit information for manual decision making
36 /// let rate_info = client.rate_limit_info();
37 /// if rate_info.remaining_requests() < 10 {
38 /// // Developer decides to wait or use alternative strategy
39 /// println!( "Rate limit approaching : {} requests remaining", rate_info.remaining_requests() );
40 /// }
41 /// # Ok( () )
42 /// # }
43 /// ```
44 pub fn rate_limit_info( &self ) -> RateLimitInfo
45 {
46 RateLimitInfo::new()
47 }
48
49 /// Get explicit health monitoring information for manual decision making
50 ///
51 /// # Governing Principle Compliance
52 ///
53 /// This method follows the "Thin Client, Rich API" principle by:
54 /// - **Information vs Action**: Provides health information without making automatic decisions
55 /// - **Zero Automatic Behavior**: No hidden health-based request blocking or filtering
56 /// - **Explicit Control**: Developers can use health information to make their own decisions
57 /// - **Transparent Operations**: All health data is visible and accessible
58 ///
59 /// # Examples
60 ///
61 /// ```no_run
62 /// use api_claude::{ Client, Secret };
63 ///
64 /// # async fn example() -> Result< (), Box< dyn std::error::Error > > {
65 /// let secret = Secret::new( "sk-ant-api03-example".to_string() ).unwrap();
66 /// let client = Client::new( secret );
67 ///
68 /// // Check health information for manual decision making
69 /// let health = client.health();
70 /// if health.consecutive_failures() > 5 {
71 /// // Developer decides to wait or use alternative strategy
72 /// println!( "High failure rate detected : {} failures", health.consecutive_failures() );
73 /// }
74 /// # Ok( () )
75 /// # }
76 /// ```
77 pub fn health( &self ) -> HealthStatus
78 {
79 HealthStatus::new()
80 }
81
82 /// Create an explicit retry builder for this client
83 ///
84 /// # Governing Principle Compliance
85 ///
86 /// This method follows the "Thin Client, Rich API" principle by:
87 /// - **Explicit Configuration**: All retry behavior must be explicitly configured by developers
88 /// - **Zero Automatic Behavior**: No hidden retry logic or magic thresholds
89 /// - **Transparent Configuration**: All retry parameters are explicitly specified
90 /// - **Information vs Action**: Provides retry capability without imposing retry decisions
91 ///
92 /// # Examples
93 ///
94 /// ```no_run
95 /// use api_claude::{ Client, Secret };
96 /// use std::time::Duration;
97 ///
98 /// # async fn example() -> Result< (), Box< dyn std::error::Error > > {
99 /// let secret = Secret::new( "sk-ant-api03-example".to_string() ).unwrap();
100 /// let client = Client::new( secret );
101 ///
102 /// // Explicit retry with manual configuration
103 /// let response = client
104 /// .explicit_retry()
105 /// .with_attempts( 3 )
106 /// .with_delay( Duration::from_secs( 1 ) )
107 /// .execute( | _client | async move {
108 /// // Your API operation here
109 /// Ok( "operation result".to_string() )
110 /// } )
111 /// .await?;
112 /// # Ok( () )
113 /// # }
114 /// ```
115 pub fn explicit_retry( &self ) -> ExplicitRetryBuilder< '_ >
116 {
117 ExplicitRetryBuilder::new( self )
118 }
119 }
120
121 /// Rate limit information for explicit control
122 ///
123 /// # Governing Principle Compliance
124 ///
125 /// This struct follows the "Thin Client, Rich API" principle by:
126 /// - **Information vs Action**: Provides rate limit data without making automatic decisions
127 /// - **Zero Automatic Behavior**: No hidden rate limiting or magic throttling thresholds
128 /// - **Explicit Control**: Developers can use this information to make their own timing decisions
129 /// - **Transparent Operations**: All rate limit metrics are visible and accessible
130 #[ derive( Debug, Clone ) ]
131 pub struct RateLimitInfo
132 {
133 remaining_requests : u32,
134 total_limit : u32,
135 reset_time : Option< std::time::SystemTime >,
136 window_duration : std::time::Duration,
137 }
138
139 impl RateLimitInfo
140 {
141 /// Create new rate limit info (currently returns placeholder data)
142 ///
143 /// # Note
144 ///
145 /// This is a placeholder implementation. In a real implementation, this would
146 /// track actual rate limit headers from API responses. For now, it provides the
147 /// interface for explicit rate limit information access without automatic behavior.
148 pub fn new() -> Self
149 {
150 Self
151 {
152 remaining_requests : 1000, // Placeholder
153 total_limit : 1000, // Placeholder
154 reset_time : None, // Placeholder
155 window_duration : std::time::Duration::from_secs( 3600 ), // Placeholder : 1 hour
156 }
157 }
158
159 /// Get remaining requests in current window
160 #[ inline ]
161 #[ must_use ]
162 pub fn remaining_requests( &self ) -> u32
163 {
164 self.remaining_requests
165 }
166
167 /// Get total rate limit for current window
168 #[ inline ]
169 #[ must_use ]
170 pub fn total_limit( &self ) -> u32
171 {
172 self.total_limit
173 }
174
175 /// Get time when rate limit window resets
176 #[ inline ]
177 #[ must_use ]
178 pub fn reset_time( &self ) -> Option< std::time::SystemTime >
179 {
180 self.reset_time
181 }
182
183 /// Get duration of rate limit window
184 #[ inline ]
185 #[ must_use ]
186 pub fn window_duration( &self ) -> std::time::Duration
187 {
188 self.window_duration
189 }
190
191 }
192
193 impl Default for RateLimitInfo
194 {
195 fn default() -> Self
196 {
197 Self::new()
198 }
199 }
200
201 impl RateLimitInfo
202 {
203 /// Calculate usage percentage (0.0 to 1.0)
204 #[ inline ]
205 #[ must_use ]
206 pub fn usage_percentage( &self ) -> f64
207 {
208 if self.total_limit == 0
209 {
210 0.0
211 }
212 else
213 {
214 let used = self.total_limit.saturating_sub( self.remaining_requests );
215 f64::from( used ) / f64::from( self.total_limit )
216 }
217 }
218
219 /// Check if rate limit is approaching based on explicit criteria
220 ///
221 /// # Arguments
222 ///
223 /// * `threshold_percentage` - Percentage threshold (0.0 to 1.0) for considering rate limit approaching
224 ///
225 /// # Returns
226 ///
227 /// Returns true if usage percentage exceeds the specified threshold (explicit developer-defined threshold)
228 #[ inline ]
229 #[ must_use ]
230 pub fn is_approaching_limit_with_threshold( &self, threshold_percentage : f64 ) -> bool
231 {
232 self.usage_percentage() >= threshold_percentage
233 }
234
235 /// Calculate suggested delay for manual rate control
236 ///
237 /// # Arguments
238 ///
239 /// * `desired_requests_per_minute` - Target request rate for manual pacing
240 ///
241 /// # Returns
242 ///
243 /// Returns suggested delay duration for achieving the desired rate (developer-controlled pacing)
244 #[ inline ]
245 #[ must_use ]
246 pub fn suggested_delay_for_rate( &self, desired_requests_per_minute : u32 ) -> std::time::Duration
247 {
248 if desired_requests_per_minute == 0
249 {
250 std::time::Duration::from_secs( 60 ) // Safe fallback
251 }
252 else
253 {
254 let seconds_per_request = 60.0 / f64::from( desired_requests_per_minute );
255 std::time::Duration::from_secs_f64( seconds_per_request )
256 }
257 }
258 }
259
260 /// Health status information for explicit monitoring
261 ///
262 /// # Governing Principle Compliance
263 ///
264 /// This struct follows the "Thin Client, Rich API" principle by:
265 /// - **Information vs Action**: Provides health data without making automatic decisions
266 /// - **Zero Automatic Behavior**: No hidden health-based logic or magic thresholds
267 /// - **Explicit Control**: Developers can use this information to make their own decisions
268 /// - **Transparent Operations**: All health metrics are visible and accessible
269 #[ derive( Debug, Clone ) ]
270 pub struct HealthStatus
271 {
272 consecutive_failures : u32,
273 total_requests : u64,
274 total_failures : u64,
275 last_error : Option< String >,
276 }
277
278 impl HealthStatus
279 {
280 /// Create new health status (currently returns placeholder data)
281 ///
282 /// # Note
283 ///
284 /// This is a placeholder implementation. In a real implementation, this would
285 /// track actual request metrics. For now, it provides the interface for explicit
286 /// health monitoring without automatic behavior.
287 pub fn new() -> Self
288 {
289 Self
290 {
291 consecutive_failures : 0,
292 total_requests : 0,
293 total_failures : 0,
294 last_error : None,
295 }
296 }
297
298 }
299
300 impl Default for HealthStatus
301 {
302 fn default() -> Self
303 {
304 Self::new()
305 }
306 }
307
308 impl HealthStatus
309 {
310 /// Get consecutive failures count
311 #[ inline ]
312 #[ must_use ]
313 pub fn consecutive_failures( &self ) -> u32
314 {
315 self.consecutive_failures
316 }
317
318 /// Get total requests count
319 #[ inline ]
320 #[ must_use ]
321 pub fn total_requests( &self ) -> u64
322 {
323 self.total_requests
324 }
325
326 /// Get total failures count
327 #[ inline ]
328 #[ must_use ]
329 pub fn total_failures( &self ) -> u64
330 {
331 self.total_failures
332 }
333
334 /// Get success rate (0.0 to 1.0)
335 #[ inline ]
336 #[ must_use ]
337 pub fn success_rate( &self ) -> f64
338 {
339 if self.total_requests == 0
340 {
341 1.0
342 }
343 else
344 {
345 let successes = self.total_requests.saturating_sub( self.total_failures );
346 successes as f64 / self.total_requests as f64
347 }
348 }
349
350 /// Get last error message if any
351 #[ inline ]
352 #[ must_use ]
353 pub fn last_error( &self ) -> Option< &str >
354 {
355 self.last_error.as_deref()
356 }
357
358 /// Check if the service appears healthy based on explicit criteria
359 ///
360 /// # Arguments
361 ///
362 /// * `max_consecutive_failures` - Maximum consecutive failures before considering unhealthy
363 /// * `min_success_rate` - Minimum success rate (0.0 to 1.0) before considering unhealthy
364 ///
365 /// # Returns
366 ///
367 /// Returns true if health criteria are met (explicit developer-defined thresholds)
368 #[ inline ]
369 #[ must_use ]
370 pub fn is_healthy_with_criteria( &self, max_consecutive_failures : u32, min_success_rate : f64 ) -> bool
371 {
372 self.consecutive_failures <= max_consecutive_failures && self.success_rate() >= min_success_rate
373 }
374 }
375
376 /// Type alias for retry predicate function
377 type RetryPredicate = Box< dyn Fn( &AnthropicError, u32 ) -> bool + Send + Sync >;
378
379 /// Builder for explicit retry operations
380 ///
381 /// # Governing Principle Compliance
382 ///
383 /// This builder follows the "Thin Client, Rich API" principle by:
384 /// - **Explicit Configuration**: All retry behavior must be explicitly configured by developers
385 /// - **Zero Magic**: No automatic retry decisions or hidden retry logic
386 /// - **Transparent Control**: Every retry parameter is visible and configurable
387 /// - **Information vs Action**: Provides retry information without making retry decisions
388 pub struct ExplicitRetryBuilder< 'a >
389 {
390 client : &'a Client,
391 max_attempts : Option< u32 >,
392 delay : Option< Duration >,
393 should_retry_fn : Option< RetryPredicate >,
394 }
395
396 impl core::fmt::Debug for ExplicitRetryBuilder< '_ >
397 {
398 fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result
399 {
400 f.debug_struct( "ExplicitRetryBuilder" )
401 .field( "max_attempts", &self.max_attempts )
402 .field( "delay", &self.delay )
403 .field( "has_custom_retry_fn", &self.should_retry_fn.is_some() )
404 .finish()
405 }
406 }
407
408 impl< 'a > ExplicitRetryBuilder< 'a >
409 {
410 /// Create new explicit retry builder
411 fn new( client : &'a Client ) -> Self
412 {
413 Self
414 {
415 client,
416 max_attempts : None,
417 delay : None,
418 should_retry_fn : None,
419 }
420 }
421
422 /// Set maximum number of retry attempts (explicit configuration required)
423 ///
424 /// # Arguments
425 ///
426 /// * `attempts` - Maximum number of attempts (must be >= 1)
427 #[ must_use ]
428 pub fn with_attempts( mut self, attempts : u32 ) -> Self
429 {
430 self.max_attempts = Some( attempts );
431 self
432 }
433
434 /// Set delay between retry attempts (explicit configuration required)
435 ///
436 /// # Arguments
437 ///
438 /// * `delay` - Duration to wait between attempts
439 #[ must_use ]
440 pub fn with_delay( mut self, delay : Duration ) -> Self
441 {
442 self.delay = Some( delay );
443 self
444 }
445
446 /// Set custom retry condition (explicit configuration required)
447 ///
448 /// # Arguments
449 ///
450 /// * `should_retry` - Function that determines if an error should be retried
451 #[ must_use ]
452 pub fn with_retry_condition< F >( mut self, should_retry : F ) -> Self
453 where
454 F : Fn( &AnthropicError, u32 ) -> bool + Send + Sync + 'static,
455 {
456 self.should_retry_fn = Some( Box::new( should_retry ) );
457 self
458 }
459
460 /// Execute operation with explicit retry configuration
461 ///
462 /// # Arguments
463 ///
464 /// * `operation` - Function that performs the operation to retry
465 ///
466 /// # Errors
467 ///
468 /// Returns the last error if all retry attempts fail, or validation errors if configuration is invalid
469 ///
470 /// # Panics
471 ///
472 /// This function contains an internal unwrap that is guaranteed safe due to program logic
473 ///
474 /// # Examples
475 ///
476 /// ```no_run
477 /// use api_claude::{ Client, Secret };
478 /// use std::time::Duration;
479 ///
480 /// # async fn example() -> Result< (), Box< dyn std::error::Error > > {
481 /// let secret = Secret::new( "sk-ant-api03-example".to_string() ).unwrap();
482 /// let client = Client::new( secret );
483 ///
484 /// // Execute with explicit retry configuration
485 /// let result = client
486 /// .explicit_retry()
487 /// .with_attempts( 3 )
488 /// .with_delay( Duration::from_secs( 2 ) )
489 /// .execute( | client | async move {
490 /// // Your API call here
491 /// Ok( "result".to_string() )
492 /// } )
493 /// .await?;
494 /// # Ok( () )
495 /// # }
496 /// ```
497 pub async fn execute< F, Fut, T >( self, operation : F ) -> AnthropicResult< T >
498 where
499 F : Fn( &Client ) -> Fut,
500 Fut : core::future::Future< Output = AnthropicResult< T > >,
501 {
502 // Validate configuration
503 let max_attempts = self.max_attempts
504 .ok_or_else( || AnthropicError::InvalidArgument( "max_attempts must be explicitly configured".to_string() ) )?;
505
506 let delay = self.delay
507 .ok_or_else( || AnthropicError::InvalidArgument( "delay must be explicitly configured".to_string() ) )?;
508
509 if max_attempts == 0
510 {
511 return Err( AnthropicError::InvalidArgument( "max_attempts must be >= 1".to_string() ) );
512 }
513
514 let should_retry = self.should_retry_fn.unwrap_or_else( || {
515 Box::new( | error : &AnthropicError, _attempt : u32 | -> bool {
516 // Default retry condition for common retryable errors
517 #[ cfg( feature = "error-handling" ) ]
518 {
519 match error
520 {
521 AnthropicError::RateLimit( _ ) |
522 AnthropicError::Stream( _ ) |
523 AnthropicError::Internal( _ ) => true,
524 AnthropicError::Http( http_error ) => {
525 http_error.status_code().map_or( true, | code | ( 500..600 ).contains( &code ) )
526 },
527 _ => false,
528 }
529 }
530 #[ cfg( not( feature = "error-handling" ) ) ]
531 {
532 let error_msg = error.to_string().to_lowercase();
533 error_msg.contains( "timeout" ) ||
534 error_msg.contains( "rate limit" ) ||
535 error_msg.contains( "5" ) // Basic HTTP 5xx detection
536 }
537 } )
538 } );
539
540 let mut last_error = None;
541
542 for attempt in 1..=max_attempts
543 {
544 match operation( self.client ).await
545 {
546 Ok( result ) => return Ok( result ),
547 Err( error ) =>
548 {
549 last_error = Some( error );
550
551 // Check if we should retry
552 if attempt < max_attempts && should_retry( last_error.as_ref().unwrap(), attempt )
553 {
554 tokio::time::sleep( delay ).await;
555 }
556 else
557 {
558 break;
559 }
560 }
561 }
562 }
563
564 Err( last_error.unwrap_or_else( ||
565 AnthropicError::Internal( "Unexpected retry failure".to_string() )
566 ) )
567 }
568 }
569}
570
571crate::mod_interface!
572{
573 exposed use RateLimitInfo;
574 exposed use HealthStatus;
575 exposed use ExplicitRetryBuilder;
576}