Skip to main content

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}