use std::fmt::{self, Debug};
use crate::facilitator::{BoxFuture, Facilitator, FacilitatorError};
use crate::proto;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum HookDecision {
Continue,
Abort {
reason: String,
message: String,
},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FailureRecovery<T> {
Propagate,
Recovered(T),
}
#[derive(Clone)]
pub struct VerifyContext {
pub request: proto::VerifyRequest,
}
impl Debug for VerifyContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VerifyContext")
.field("request", &"<VerifyRequest>")
.finish()
}
}
#[derive(Clone)]
pub struct SettleContext {
pub request: proto::SettleRequest,
}
impl Debug for SettleContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SettleContext")
.field("request", &"<SettleRequest>")
.finish()
}
}
pub trait FacilitatorHooks: Send + Sync {
fn before_verify<'a>(&'a self, _ctx: &'a VerifyContext) -> BoxFuture<'a, HookDecision> {
Box::pin(async { HookDecision::Continue })
}
fn after_verify<'a>(
&'a self,
_ctx: &'a VerifyContext,
_result: &'a proto::VerifyResponse,
) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_verify_failure<'a>(
&'a self,
_ctx: &'a VerifyContext,
_error: &'a FacilitatorError,
) -> BoxFuture<'a, FailureRecovery<proto::VerifyResponse>> {
Box::pin(async { FailureRecovery::Propagate })
}
fn before_settle<'a>(&'a self, _ctx: &'a SettleContext) -> BoxFuture<'a, HookDecision> {
Box::pin(async { HookDecision::Continue })
}
fn after_settle<'a>(
&'a self,
_ctx: &'a SettleContext,
_result: &'a proto::SettleResponse,
) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn on_settle_failure<'a>(
&'a self,
_ctx: &'a SettleContext,
_error: &'a FacilitatorError,
) -> BoxFuture<'a, FailureRecovery<proto::SettleResponse>> {
Box::pin(async { FailureRecovery::Propagate })
}
}
pub struct HookedFacilitator<F> {
inner: F,
hooks: Vec<Box<dyn FacilitatorHooks>>,
}
impl<F: Debug> Debug for HookedFacilitator<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HookedFacilitator")
.field("inner", &self.inner)
.field("hooks", &format!("[{} hooks]", self.hooks.len()))
.finish()
}
}
impl<F> HookedFacilitator<F> {
pub fn new(inner: F) -> Self {
Self {
inner,
hooks: Vec::new(),
}
}
#[must_use]
pub fn with_hook(mut self, hook: impl FacilitatorHooks + 'static) -> Self {
self.hooks.push(Box::new(hook));
self
}
pub fn add_hook(&mut self, hook: impl FacilitatorHooks + 'static) {
self.hooks.push(Box::new(hook));
}
#[must_use]
pub fn hook_count(&self) -> usize {
self.hooks.len()
}
#[must_use]
pub const fn inner(&self) -> &F {
&self.inner
}
}
impl<F> Facilitator for HookedFacilitator<F>
where
F: Facilitator,
{
fn verify(
&self,
request: proto::VerifyRequest,
) -> BoxFuture<'_, Result<proto::VerifyResponse, FacilitatorError>> {
Box::pin(async move {
let ctx = VerifyContext {
request: request.clone(),
};
for hook in &self.hooks {
if let HookDecision::Abort { reason, message } = hook.before_verify(&ctx).await {
return Err(FacilitatorError::Aborted { reason, message });
}
}
match self.inner.verify(request).await {
Ok(response) => {
for hook in &self.hooks {
hook.after_verify(&ctx, &response).await;
}
Ok(response)
}
Err(e) => {
for hook in &self.hooks {
if let FailureRecovery::Recovered(response) =
hook.on_verify_failure(&ctx, &e).await
{
return Ok(response);
}
}
Err(e)
}
}
})
}
fn settle(
&self,
request: proto::SettleRequest,
) -> BoxFuture<'_, Result<proto::SettleResponse, FacilitatorError>> {
Box::pin(async move {
let ctx = SettleContext {
request: request.clone(),
};
for hook in &self.hooks {
if let HookDecision::Abort { reason, message } = hook.before_settle(&ctx).await {
return Err(FacilitatorError::Aborted { reason, message });
}
}
match self.inner.settle(request).await {
Ok(response) => {
for hook in &self.hooks {
hook.after_settle(&ctx, &response).await;
}
Ok(response)
}
Err(e) => {
for hook in &self.hooks {
if let FailureRecovery::Recovered(response) =
hook.on_settle_failure(&ctx, &e).await
{
return Ok(response);
}
}
Err(e)
}
}
})
}
fn supported(&self) -> BoxFuture<'_, Result<proto::SupportedResponse, FacilitatorError>> {
Box::pin(async move { self.inner.supported().await })
}
}