use crate::domain::error::WesleyError;
use crate::domain::ir::WesleyIR;
use crate::ports::lowering::LoweringPort;
use async_trait::async_trait;
use ninelives::{ResilienceError as NineLivesError, TimeoutPolicy};
use std::time::Duration;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ResiliencePolicy {
cooperative_lowering_timeout: Option<Duration>,
}
impl ResiliencePolicy {
#[must_use]
pub fn disabled() -> Self {
Self {
cooperative_lowering_timeout: None,
}
}
pub fn cooperative_lowering_timeout(duration: Duration) -> Result<Self, WesleyError> {
Self::disabled().with_cooperative_lowering_timeout(duration)
}
pub fn with_cooperative_lowering_timeout(
mut self,
duration: Duration,
) -> Result<Self, WesleyError> {
TimeoutPolicy::new(duration)
.map_err(|error| WesleyError::ResilienceError(format!("invalid timeout: {error}")))?;
self.cooperative_lowering_timeout = Some(duration);
Ok(self)
}
#[must_use]
pub fn cooperative_lowering_timeout_duration(&self) -> Option<Duration> {
self.cooperative_lowering_timeout
}
#[must_use]
pub fn is_disabled(&self) -> bool {
self.cooperative_lowering_timeout.is_none()
}
}
#[derive(Debug, Clone)]
pub struct ResilientLoweringPort<P> {
inner: P,
policy: ResiliencePolicy,
}
impl<P> ResilientLoweringPort<P> {
#[must_use]
pub fn new(inner: P, policy: ResiliencePolicy) -> Self {
Self { inner, policy }
}
#[must_use]
pub fn policy(&self) -> ResiliencePolicy {
self.policy
}
#[must_use]
pub fn inner(&self) -> &P {
&self.inner
}
}
#[async_trait]
impl<P> LoweringPort for ResilientLoweringPort<P>
where
P: LoweringPort + Send + Sync,
{
async fn lower_sdl(&self, sdl: &str) -> Result<WesleyIR, WesleyError> {
let Some(timeout) = self.policy.cooperative_lowering_timeout_duration() else {
return self.inner.lower_sdl(sdl).await;
};
let policy = TimeoutPolicy::new(timeout)
.map_err(|error| WesleyError::ResilienceError(format!("invalid timeout: {error}")))?;
let result: Result<WesleyIR, NineLivesError<WesleyError>> = policy
.execute(|| async {
self.inner
.lower_sdl(sdl)
.await
.map_err(NineLivesError::Inner)
})
.await;
match result {
Ok(ir) => Ok(ir),
Err(NineLivesError::Inner(error)) => Err(error),
Err(error) => Err(WesleyError::ResilienceError(format!(
"schema lowering {error}"
))),
}
}
}