impl RateLimitInfo
{
fn new() -> Self
{
Self
{
remaining_requests : 1000, total_limit : 1000, reset_time : None, window_duration : std::time::Duration::from_secs( 3600 ), }
}
#[ inline ]
#[ must_use ]
pub fn remaining_requests( &self ) -> u32
{
self.remaining_requests
}
#[ inline ]
#[ must_use ]
pub fn total_limit( &self ) -> u32
{
self.total_limit
}
#[ inline ]
#[ must_use ]
pub fn reset_time( &self ) -> Option< std::time::SystemTime >
{
self.reset_time
}
#[ inline ]
#[ must_use ]
pub fn window_duration( &self ) -> std::time::Duration
{
self.window_duration
}
#[ inline ]
#[ must_use ]
pub fn usage_percentage( &self ) -> f64
{
if self.total_limit == 0
{
0.0
}
else
{
let used = self.total_limit.saturating_sub( self.remaining_requests );
f64::from( used ) / f64::from( self.total_limit )
}
}
#[ inline ]
#[ must_use ]
pub fn is_approaching_limit_with_threshold( &self, threshold_percentage : f64 ) -> bool
{
self.usage_percentage() >= threshold_percentage
}
#[ inline ]
#[ must_use ]
pub fn suggested_delay_for_rate( &self, desired_requests_per_minute : u32 ) -> std::time::Duration
{
if desired_requests_per_minute == 0
{
std::time::Duration::from_secs( 60 ) }
else
{
let seconds_per_request = 60.0 / f64::from( desired_requests_per_minute );
std::time::Duration::from_secs_f64( seconds_per_request )
}
}
}
impl HealthStatus
{
fn new() -> Self
{
Self
{
consecutive_failures : 0,
total_requests : 0,
total_failures : 0,
last_error : None,
}
}
#[ inline ]
#[ must_use ]
pub fn consecutive_failures( &self ) -> u32
{
self.consecutive_failures
}
#[ inline ]
#[ must_use ]
pub fn total_requests( &self ) -> u64
{
self.total_requests
}
#[ inline ]
#[ must_use ]
pub fn total_failures( &self ) -> u64
{
self.total_failures
}
#[ inline ]
#[ must_use ]
pub fn success_rate( &self ) -> f64
{
if self.total_requests == 0
{
1.0
}
else
{
let successes = self.total_requests.saturating_sub( self.total_failures );
successes as f64 / self.total_requests as f64
}
}
#[ inline ]
#[ must_use ]
pub fn last_error( &self ) -> Option< &str >
{
self.last_error.as_deref()
}
#[ inline ]
#[ must_use ]
pub fn is_healthy_with_criteria( &self, max_consecutive_failures : u32, min_success_rate : f64 ) -> bool
{
self.consecutive_failures <= max_consecutive_failures && self.success_rate() >= min_success_rate
}
}
impl core::fmt::Debug for ExplicitRetryBuilder< '_ >
{
fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result
{
f.debug_struct( "ExplicitRetryBuilder" )
.field( "max_attempts", &self.max_attempts )
.field( "delay", &self.delay )
.field( "has_custom_retry_fn", &self.should_retry_fn.is_some() )
.finish()
}
}
impl< 'a > ExplicitRetryBuilder< 'a >
{
fn new( client : &'a Client ) -> Self
{
Self
{
client,
max_attempts : None,
delay : None,
should_retry_fn : None,
}
}
#[ must_use ]
pub fn with_attempts( mut self, attempts : u32 ) -> Self
{
self.max_attempts = Some( attempts );
self
}
#[ must_use ]
pub fn with_delay( mut self, delay : Duration ) -> Self
{
self.delay = Some( delay );
self
}
#[ must_use ]
pub fn with_retry_condition< F >( mut self, should_retry : F ) -> Self
where
F : Fn( &AnthropicError, u32 ) -> bool + Send + Sync + 'static,
{
self.should_retry_fn = Some( Box::new( should_retry ) );
self
}
pub async fn execute< F, Fut, T >( self, operation : F ) -> AnthropicResult< T >
where
F : Fn( &Client ) -> Fut,
Fut : core::future::Future< Output = AnthropicResult< T > >,
{
let max_attempts = self.max_attempts
.ok_or_else( || AnthropicError::InvalidArgument( "max_attempts must be explicitly configured".to_string() ) )?;
let delay = self.delay
.ok_or_else( || AnthropicError::InvalidArgument( "delay must be explicitly configured".to_string() ) )?;
if max_attempts == 0
{
return Err( AnthropicError::InvalidArgument( "max_attempts must be >= 1".to_string() ) );
}
let should_retry = self.should_retry_fn.unwrap_or_else( || {
Box::new( | error : &AnthropicError, _attempt : u32 | -> bool {
#[ cfg( feature = "error-handling" ) ]
{
match error
{
AnthropicError::RateLimit( _ ) |
AnthropicError::Stream( _ ) |
AnthropicError::Internal( _ ) => true,
AnthropicError::Http( http_error ) => {
http_error.status_code().is_none_or( | code | ( 500..600 ).contains( &code ) )
},
_ => false,
}
}
#[ cfg( not( feature = "error-handling" ) ) ]
{
let error_msg = error.to_string().to_lowercase();
error_msg.contains( "timeout" ) ||
error_msg.contains( "rate limit" ) ||
error_msg.contains( "5" ) }
} )
} );
let mut last_error = None;
for attempt in 1..=max_attempts
{
match operation( self.client ).await
{
Ok( result ) => return Ok( result ),
Err( error ) =>
{
last_error = Some( error );
if attempt < max_attempts && should_retry( last_error.as_ref().unwrap(), attempt )
{
tokio::time::sleep( delay ).await;
}
else
{
break;
}
}
}
}
Err( last_error.unwrap_or_else( ||
AnthropicError::Internal( "Unexpected retry failure".to_string() )
) )
}
}