#[ allow( clippy::missing_inline_in_public_items ) ]
mod private
{
use serde::{ Serialize, Deserialize };
use std::{ fmt, time::Duration };
#[ cfg( feature = "error-handling" ) ]
use super::super::enhanced::orphan::{ EnhancedAnthropicError, ErrorContext };
#[ derive( Debug, Clone ) ]
pub struct HttpError
{
status_code : Option< u16 >,
message : String,
url : Option< String >,
method : Option< String >,
headers : Option< Vec< ( String, String ) > >,
}
impl HttpError
{
pub fn new( message : String ) -> Self
{
Self {
status_code : None,
message,
url : None,
method : None,
headers : None,
}
}
#[ must_use ]
pub fn with_status_code( mut self, status_code : u16 ) -> Self
{
self.status_code = Some( status_code );
self
}
#[ must_use ]
pub fn with_request_info( mut self, method : String, url : String ) -> Self
{
self.method = Some( method );
self.url = Some( url );
self
}
pub fn status_code( &self ) -> Option< u16 >
{
self.status_code
}
pub fn message( &self ) -> &str
{
&self.message
}
pub fn is_retryable( &self ) -> bool
{
match self.status_code
{
Some( code ) => matches!( code, 500..=599 | 429 | 408 ),
None => false,
}
}
pub fn headers( &self ) -> Option< &Vec< ( String, String ) > >
{
self.headers.as_ref()
}
}
impl fmt::Display for HttpError
{
fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
{
match ( &self.status_code, &self.method, &self.url )
{
( Some( code ), Some( method ), Some( url ) ) =>
write!( f, "HTTP {} error for {} {}: {}", code, method, url, self.message ),
( Some( code ), _, _ ) =>
write!( f, "HTTP {} error : {}", code, self.message ),
_ =>
write!( f, "HTTP error : {}", self.message ),
}
}
}
#[ derive( Debug, Clone ) ]
pub enum AnthropicError
{
Http( HttpError ),
Api( AnthropicApiError ),
InvalidArgument( String ),
InvalidRequest( String ),
MissingEnvironment( String ),
Authentication( AuthenticationError ),
RateLimit( RateLimitError ),
File( String ),
Internal( String ),
Stream( String ),
Parsing( String ),
NotImplemented( String ),
#[ cfg( feature = "circuit-breaker" ) ]
CircuitOpen( String ),
#[ cfg( feature = "error-handling" ) ]
Enhanced( Box< EnhancedAnthropicError > ),
}
impl fmt::Display for AnthropicError
{
fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
{
match self
{
AnthropicError::Http( err ) => write!( f, "{err}" ),
AnthropicError::Api( err ) => write!( f, "API error : {err}" ),
AnthropicError::InvalidArgument( msg ) => write!( f, "Invalid argument : {msg}" ),
AnthropicError::InvalidRequest( msg ) => write!( f, "Invalid request : {msg}" ),
AnthropicError::MissingEnvironment( msg ) => write!( f, "Missing environment : {msg}" ),
AnthropicError::Authentication( err ) => write!( f, "Authentication error : {err}" ),
AnthropicError::RateLimit( err ) => write!( f, "Rate limit error : {err}" ),
AnthropicError::File( msg ) => write!( f, "File error : {msg}" ),
AnthropicError::Internal( msg ) => write!( f, "Internal error : {msg}" ),
AnthropicError::Stream( msg ) => write!( f, "Stream error : {msg}" ),
AnthropicError::Parsing( msg ) => write!( f, "Parsing error : {msg}" ),
AnthropicError::NotImplemented( msg ) => write!( f, "Not implemented : {msg}" ),
#[ cfg( feature = "circuit-breaker" ) ]
AnthropicError::CircuitOpen( msg ) => write!( f, "Circuit breaker open : {msg}" ),
#[ cfg( feature = "error-handling" ) ]
AnthropicError::Enhanced( err ) => write!( f, "Enhanced error : {}", err.message() ),
}
}
}
impl core::error::Error for AnthropicError
{}
impl AnthropicError
{
#[ must_use ]
pub fn is_retryable( &self ) -> bool
{
match self
{
AnthropicError::Http( http_err ) => http_err.is_retryable(),
AnthropicError::RateLimit( _ ) | AnthropicError::Stream( _ ) | AnthropicError::Internal( _ ) => true,
AnthropicError::Api( api_err ) => api_err.is_retryable(),
_ => false,
}
}
#[ must_use ]
pub fn severity( &self ) -> ErrorSeverity
{
match self
{
AnthropicError::Authentication( _ ) | AnthropicError::MissingEnvironment( _ ) => ErrorSeverity::Critical,
AnthropicError::InvalidArgument( _ ) | AnthropicError::InvalidRequest( _ ) => ErrorSeverity::High,
AnthropicError::RateLimit( _ ) | AnthropicError::Http( _ ) | AnthropicError::Stream( _ ) | AnthropicError::Api( _ ) => ErrorSeverity::Medium,
_ => ErrorSeverity::Low,
}
}
#[ must_use ]
pub fn recovery_suggestions( &self ) -> Vec< String >
{
match self
{
AnthropicError::Authentication( _ ) => vec![
"Verify your API key is correct and properly formatted".to_string(),
"Check that your API key has the required permissions".to_string(),
"Ensure the API key starts with 'sk-ant-'".to_string(),
],
AnthropicError::RateLimit( rate_err ) => {
let mut suggestions = vec![
"Implement exponential backoff retry strategy".to_string(),
"Reduce request frequency".to_string(),
];
if let Some( retry_after ) = rate_err.retry_after()
{
suggestions.push( format!( "Wait {retry_after} seconds before retrying" ) );
}
suggestions
},
AnthropicError::Http( http_err ) => {
if http_err.is_retryable()
{
vec![
"Retry the request with exponential backoff".to_string(),
"Check network connectivity".to_string(),
]
} else {
vec![
"Verify request parameters and format".to_string(),
"Check API endpoint URL".to_string(),
]
}
},
AnthropicError::MissingEnvironment( msg ) => vec![
format!( "Set the required environment variable : {}", msg ),
"Check your .env file or environment configuration".to_string(),
],
_ => vec![ "Check error message for specific guidance".to_string() ],
}
}
pub fn http_error( message : String ) -> Self
{
Self::Http( HttpError::new( message ) )
}
pub fn http_error_with_status( message : String, status_code : u16 ) -> Self
{
Self::Http( HttpError::new( message ).with_status_code( status_code ) )
}
pub fn http_error_with_request( message : String, status_code : u16, method : String, url : String ) -> Self
{
Self::Http(
HttpError::new( message )
.with_status_code( status_code )
.with_request_info( method, url )
)
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize ) ]
pub enum ErrorSeverity
{
Low,
Medium,
High,
Critical,
}
#[ cfg( feature = "error-handling" ) ]
impl AnthropicError
{
#[ must_use ]
pub fn has_context( &self ) -> bool
{
match self
{
AnthropicError::Enhanced( err ) => err.has_context(),
_ => false,
}
}
#[ must_use ]
pub fn context( &self ) -> &str
{
match self
{
AnthropicError::Enhanced( err ) => err.context().map_or( "", ErrorContext::context ),
_ => "",
}
}
#[ must_use ]
pub fn has_stack_trace( &self ) -> bool
{
match self
{
AnthropicError::Enhanced( err ) => err.has_stack_trace(),
_ => false,
}
}
#[ must_use ]
pub fn stack_trace( &self ) -> Vec< String >
{
match self
{
AnthropicError::Enhanced( err ) => err.stack_trace().clone(),
_ => vec![],
}
}
#[ must_use ]
pub fn request_id( &self ) -> Option< String >
{
match self
{
AnthropicError::Enhanced( err ) => err.request_id().clone(),
_ => None,
}
}
#[ must_use ]
pub fn correlation_id( &self ) -> Option< String >
{
match self
{
AnthropicError::Enhanced( err ) => err.correlation_id().clone(),
_ => None,
}
}
}
#[ derive( Debug, Serialize, Deserialize, Clone ) ]
pub struct AnthropicApiError
{
pub r#type : String,
pub message : String,
}
impl fmt::Display for AnthropicApiError
{
fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
{
write!( f, "{}: {}", self.r#type, self.message )
}
}
impl AnthropicApiError
{
#[ must_use ]
pub fn is_retryable( &self ) -> bool
{
matches!(
self.r#type.as_str(),
"rate_limit_error" |
"internal_server_error" |
"service_unavailable" |
"timeout_error"
)
}
}
#[ derive( Debug, Clone ) ]
pub struct AuthenticationError
{
message : String,
recoverable : bool,
retry_after : Option< Duration >,
suggested_action : Option< String >,
}
impl AuthenticationError
{
#[ inline ]
#[ must_use ]
pub fn new( message : String ) -> Self
{
Self
{
message,
recoverable : false,
retry_after : None,
suggested_action : None,
}
}
#[ inline ]
#[ must_use ]
pub fn recoverable( message : String, retry_after : Option< Duration >, suggested_action : Option< String > ) -> Self
{
Self
{
message,
recoverable : true,
retry_after,
suggested_action,
}
}
#[ inline ]
#[ must_use ]
pub fn is_recoverable( &self ) -> bool
{
self.recoverable
}
#[ inline ]
#[ must_use ]
pub fn retry_after( &self ) -> &Option< Duration >
{
&self.retry_after
}
#[ inline ]
#[ must_use ]
pub fn suggested_action( &self ) -> &Option< String >
{
&self.suggested_action
}
}
impl fmt::Display for AuthenticationError
{
fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
{
write!( f, "{}", self.message )
}
}
#[ derive( Debug, Clone ) ]
pub struct RateLimitError
{
message : String,
retry_after : Option< u64 >,
limit_type : String,
rate_limit_info : Option< Box< AnthropicRateLimitInfo > >,
}
#[ derive( Debug, Clone ) ]
pub struct AnthropicRateLimitInfo
{
pub requests_limit : Option< u64 >,
pub requests_remaining : Option< u64 >,
pub requests_reset : Option< String >,
pub tokens_limit : Option< u64 >,
pub tokens_remaining : Option< u64 >,
pub tokens_reset : Option< String >,
}
impl AnthropicRateLimitInfo
{
#[ must_use ]
pub fn from_headers( headers : &reqwest::header::HeaderMap ) -> Self
{
Self
{
requests_limit : Self::parse_header_u64( headers, "anthropic-ratelimit-requests-limit" ),
requests_remaining : Self::parse_header_u64( headers, "anthropic-ratelimit-requests-remaining" ),
requests_reset : Self::parse_header_string( headers, "anthropic-ratelimit-requests-reset" ),
tokens_limit : Self::parse_header_u64( headers, "anthropic-ratelimit-tokens-limit" ),
tokens_remaining : Self::parse_header_u64( headers, "anthropic-ratelimit-tokens-remaining" ),
tokens_reset : Self::parse_header_string( headers, "anthropic-ratelimit-tokens-reset" ),
}
}
#[ must_use ]
pub fn has_data( &self ) -> bool
{
self.requests_limit.is_some() ||
self.requests_remaining.is_some() ||
self.tokens_limit.is_some() ||
self.tokens_remaining.is_some()
}
#[ must_use ]
pub fn requests_usage_percentage( &self ) -> Option< f64 >
{
match ( self.requests_limit, self.requests_remaining )
{
( Some( limit ), Some( remaining ) ) if limit > 0 =>
{
let used = limit.saturating_sub( remaining );
Some( used as f64 / limit as f64 )
},
_ => None,
}
}
#[ must_use ]
pub fn tokens_usage_percentage( &self ) -> Option< f64 >
{
match ( self.tokens_limit, self.tokens_remaining )
{
( Some( limit ), Some( remaining ) ) if limit > 0 =>
{
let used = limit.saturating_sub( remaining );
Some( used as f64 / limit as f64 )
},
_ => None,
}
}
fn parse_header_u64( headers : &reqwest::header::HeaderMap, name : &str ) -> Option< u64 >
{
headers.get( name )
.and_then( | v | v.to_str().ok() )
.and_then( | s | s.parse::< u64 >().ok() )
}
fn parse_header_string( headers : &reqwest::header::HeaderMap, name : &str ) -> Option< String >
{
headers.get( name )
.and_then( | v | v.to_str().ok() )
.map( String::from )
}
}
impl RateLimitError
{
#[ inline ]
#[ must_use ]
pub fn new( message : String, retry_after : Option< u64 >, limit_type : String ) -> Self
{
Self { message, retry_after, limit_type, rate_limit_info : None }
}
#[ inline ]
#[ must_use ]
pub fn with_headers( message : String, retry_after : Option< u64 >, limit_type : String, rate_limit_info : AnthropicRateLimitInfo ) -> Self
{
Self { message, retry_after, limit_type, rate_limit_info : Some( Box::new( rate_limit_info ) ) }
}
#[ inline ]
#[ must_use ]
pub fn retry_after( &self ) -> &Option< u64 >
{
&self.retry_after
}
#[ inline ]
#[ must_use ]
pub fn limit_type( &self ) -> &str
{
&self.limit_type
}
#[ inline ]
#[ must_use ]
pub fn rate_limit_info( &self ) -> Option< &AnthropicRateLimitInfo >
{
self.rate_limit_info.as_deref()
}
}
impl fmt::Display for RateLimitError
{
fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
{
write!( f, "{}", self.message )?;
if let Some( retry_after ) = self.retry_after
{
write!( f, " (retry after {retry_after}s)" )?;
}
if let Some( ref info ) = self.rate_limit_info
{
if let ( Some( remaining ), Some( limit ) ) = ( info.requests_remaining, info.requests_limit )
{
write!( f, " [requests : {remaining}/{limit}]" )?;
}
if let ( Some( remaining ), Some( limit ) ) = ( info.tokens_remaining, info.tokens_limit )
{
write!( f, " [tokens : {remaining}/{limit}]" )?;
}
}
Ok( () )
}
}
#[ derive( Debug, Serialize, Deserialize ) ]
pub struct ApiErrorWrap
{
pub error : AnthropicApiError,
}
impl From< reqwest::Error > for AnthropicError
{
fn from( error : reqwest::Error ) -> Self
{
Self::http_error( error.to_string() )
}
}
impl From< serde_json::Error > for AnthropicError
{
fn from( error : serde_json::Error ) -> Self
{
Self::Internal( format!( "JSON error : {error}" ) )
}
}
pub type AnthropicResult< T > = core::result::Result< T, AnthropicError >;
pub fn map_deserialization_error( error : &serde_json::Error ) -> AnthropicError
{
AnthropicError::Parsing( format!( "Failed to deserialize response : {error}" ) )
}
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum ErrorClass
{
Authentication,
InvalidRequest,
ServerError,
RateLimit,
Network,
Timeout,
Parsing,
Internal,
}
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum ErrorType
{
Authentication,
InvalidRequest,
ServerError,
RateLimit,
Network,
Timeout,
Parsing,
Internal,
}
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum TimeoutType
{
Connection,
Read,
Write,
Request,
}
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum NetworkErrorType
{
DnsResolution,
SslHandshake,
ConnectionRefused,
ConnectionReset,
HostUnreachable,
Generic,
}
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum BackoffStrategy
{
Linear,
ExponentialBackoff,
Fixed,
Custom,
}
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum BackoffType
{
Linear,
Exponential,
Fixed,
}
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum LogSeverity
{
Debug,
Info,
Warning,
Error,
Critical,
}
}
crate::mod_interface!
{
exposed use HttpError;
exposed use AnthropicError;
exposed use ErrorSeverity;
exposed use AnthropicApiError;
exposed use AuthenticationError;
exposed use RateLimitError;
exposed use AnthropicRateLimitInfo;
exposed use ApiErrorWrap;
exposed use AnthropicResult;
exposed use map_deserialization_error;
exposed use ErrorClass;
exposed use ErrorType;
exposed use TimeoutType;
exposed use NetworkErrorType;
exposed use BackoffStrategy;
exposed use BackoffType;
exposed use LogSeverity;
}