use core::time::Duration;
use std::time::SystemTime;
use serde::{ Serialize, Deserialize };
#[ derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum HealthStatus
{
Healthy,
Degraded,
Unhealthy,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct HealthCheckResult
{
pub status : HealthStatus,
pub response_time : Option< Duration >,
pub checked_at : Option< SystemTime >,
pub endpoint : String,
pub error_message : Option< String >,
}
#[ derive( Debug, Clone ) ]
pub struct HealthCheckConfig
{
pub timeout : Duration,
pub strategy : HealthCheckStrategy,
}
#[ derive( Debug, Clone, PartialEq, Eq ) ]
pub enum HealthCheckStrategy
{
Ping,
LightweightApiCall,
}
impl Default for HealthCheckConfig
{
#[ inline ]
fn default() -> Self
{
Self {
timeout : Duration::from_secs( 10 ),
strategy : HealthCheckStrategy::Ping,
}
}
}
#[ derive( Debug, Clone ) ]
pub struct HealthCheckBuilder
{
config : HealthCheckConfig,
client : crate::client::Client,
}
impl HealthCheckBuilder
{
#[ must_use ]
#[ inline ]
pub fn new( client : crate::client::Client ) -> Self
{
Self {
config : HealthCheckConfig::default(),
client,
}
}
#[ inline ]
#[ must_use ]
pub fn timeout( mut self, timeout : Duration ) -> Self
{
self.config.timeout = timeout;
self
}
#[ inline ]
#[ must_use ]
pub fn strategy( mut self, strategy : HealthCheckStrategy ) -> Self
{
self.config.strategy = strategy;
self
}
#[ inline ]
pub async fn check_endpoint( self ) -> Result< HealthCheckResult, crate::error::Error >
{
let start_time = SystemTime::now();
let result = match self.config.strategy
{
HealthCheckStrategy::Ping => self.perform_ping_check().await,
HealthCheckStrategy::LightweightApiCall => self.perform_api_check().await,
};
let response_time = start_time.elapsed().ok();
match result
{
Ok( () ) => Ok( HealthCheckResult {
status : HealthStatus::Healthy,
response_time,
checked_at : Some( start_time ),
endpoint : self.client.base_url().to_string(),
error_message : None,
} ),
Err( e ) => Ok( HealthCheckResult {
status : HealthStatus::Unhealthy,
response_time,
checked_at : Some( start_time ),
endpoint : self.client.base_url().to_string(),
error_message : Some( e.to_string() ),
} ),
}
}
async fn perform_ping_check( &self ) -> Result< (), crate::error::Error >
{
let client = reqwest::Client::builder()
.timeout( self.config.timeout )
.build()
.map_err( |e| crate::error::Error::Health( format!( "Failed to create HTTP client : {e}" ) ) )?;
let response = client
.head( self.client.base_url() )
.send()
.await
.map_err( |e| crate::error::Error::Health( format!( "Health check request failed : {e}" ) ) )?;
if response.status().is_success() || response.status().is_redirection()
{
Ok( () )
} else {
Err( crate::error::Error::Health( format!( "Health check failed with status : {}", response.status() ) ) )
}
}
async fn perform_api_check( &self ) -> Result< (), crate::error::Error >
{
let _models = self.client.models().list().await?;
Ok( () )
}
}