use std::pin::Pin;
use cow_errors::CowError;
#[cfg(not(target_arch = "wasm32"))]
pub type StepFuture =
Pin<Box<dyn std::future::Future<Output = Result<(), CowError>> + Send + 'static>>;
#[cfg(target_arch = "wasm32")]
pub type StepFuture = Pin<Box<dyn std::future::Future<Output = Result<(), CowError>> + 'static>>;
#[cfg(not(target_arch = "wasm32"))]
pub type StepFn = Box<dyn Fn() -> StepFuture + Send + Sync>;
#[cfg(target_arch = "wasm32")]
pub type StepFn = Box<dyn Fn() -> StepFuture>;
#[cfg(not(target_arch = "wasm32"))]
pub type ErrFn = Box<dyn Fn(&CowError) + Send + Sync>;
#[cfg(target_arch = "wasm32")]
pub type ErrFn = Box<dyn Fn(&CowError)>;
#[derive(Default)]
pub struct SigningStepManager {
pub before_bridging_sign: Option<StepFn>,
pub after_bridging_sign: Option<StepFn>,
pub before_order_sign: Option<StepFn>,
pub after_order_sign: Option<StepFn>,
pub on_bridging_sign_error: Option<ErrFn>,
pub on_order_sign_error: Option<ErrFn>,
}
impl std::fmt::Debug for SigningStepManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SigningStepManager")
.field("before_bridging_sign", &self.before_bridging_sign.is_some())
.field("after_bridging_sign", &self.after_bridging_sign.is_some())
.field("before_order_sign", &self.before_order_sign.is_some())
.field("after_order_sign", &self.after_order_sign.is_some())
.field("on_bridging_sign_error", &self.on_bridging_sign_error.is_some())
.field("on_order_sign_error", &self.on_order_sign_error.is_some())
.finish()
}
}
impl SigningStepManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_before_bridging_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + Send + Sync + 'static,
{
self.before_bridging_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_before_bridging_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + 'static,
{
self.before_bridging_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_after_bridging_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + Send + Sync + 'static,
{
self.after_bridging_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_after_bridging_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + 'static,
{
self.after_bridging_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_before_order_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + Send + Sync + 'static,
{
self.before_order_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_before_order_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + 'static,
{
self.before_order_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_after_order_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + Send + Sync + 'static,
{
self.after_order_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_after_order_sign<F>(mut self, f: F) -> Self
where
F: Fn() -> StepFuture + 'static,
{
self.after_order_sign = Some(Box::new(f));
self
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_on_bridging_sign_error<F>(mut self, f: F) -> Self
where
F: Fn(&CowError) + Send + Sync + 'static,
{
self.on_bridging_sign_error = Some(Box::new(f));
self
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_on_bridging_sign_error<F>(mut self, f: F) -> Self
where
F: Fn(&CowError) + 'static,
{
self.on_bridging_sign_error = Some(Box::new(f));
self
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_on_order_sign_error<F>(mut self, f: F) -> Self
where
F: Fn(&CowError) + Send + Sync + 'static,
{
self.on_order_sign_error = Some(Box::new(f));
self
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_on_order_sign_error<F>(mut self, f: F) -> Self
where
F: Fn(&CowError) + 'static,
{
self.on_order_sign_error = Some(Box::new(f));
self
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_before_bridging_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.with_before_bridging_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_before_bridging_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + 'static,
{
self.with_before_bridging_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_after_bridging_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.with_after_bridging_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_after_bridging_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + 'static,
{
self.with_after_bridging_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_before_order_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.with_before_order_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_before_order_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + 'static,
{
self.with_before_order_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn with_after_order_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.with_after_order_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
#[must_use]
#[cfg(target_arch = "wasm32")]
pub fn with_after_order_sign_sync<F>(self, f: F) -> Self
where
F: Fn() + 'static,
{
self.with_after_order_sign(move || {
f();
Box::pin(async { Ok(()) })
})
}
pub async fn fire_before_bridging_sign(&self) -> Result<(), CowError> {
if let Some(f) = &self.before_bridging_sign {
f().await?;
}
Ok(())
}
pub async fn fire_after_bridging_sign(&self) -> Result<(), CowError> {
if let Some(f) = &self.after_bridging_sign {
f().await?;
}
Ok(())
}
pub async fn fire_before_order_sign(&self) -> Result<(), CowError> {
if let Some(f) = &self.before_order_sign {
f().await?;
}
Ok(())
}
pub async fn fire_after_order_sign(&self) -> Result<(), CowError> {
if let Some(f) = &self.after_order_sign {
f().await?;
}
Ok(())
}
pub fn fire_on_bridging_sign_error(&self, err: &CowError) {
if let Some(f) = &self.on_bridging_sign_error {
f(err);
}
}
pub fn fire_on_order_sign_error(&self, err: &CowError) {
if let Some(f) = &self.on_order_sign_error {
f(err);
}
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
#[allow(clippy::tests_outside_test_module, reason = "inner module + cfg guard for WASM test skip")]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicU8, Ordering},
};
#[allow(
clippy::disallowed_types,
reason = "test-only recorder — contention not a concern"
)]
use std::sync::Mutex;
use super::*;
#[test]
fn default_has_no_callbacks() {
let mgr = SigningStepManager::default();
assert!(mgr.before_bridging_sign.is_none());
assert!(mgr.after_bridging_sign.is_none());
assert!(mgr.before_order_sign.is_none());
assert!(mgr.after_order_sign.is_none());
assert!(mgr.on_bridging_sign_error.is_none());
assert!(mgr.on_order_sign_error.is_none());
}
#[test]
fn new_matches_default() {
let a = SigningStepManager::new();
let b = SigningStepManager::default();
assert_eq!(
format!("{a:?}").contains("SigningStepManager"),
format!("{b:?}").contains("SigningStepManager"),
);
}
#[test]
fn debug_impl_reports_presence_booleans() {
let mgr =
SigningStepManager::new().with_before_bridging_sign(|| Box::pin(async { Ok(()) }));
let dbg = format!("{mgr:?}");
assert!(dbg.contains("before_bridging_sign: true"));
assert!(dbg.contains("after_bridging_sign: false"));
}
#[tokio::test]
async fn callbacks_fire_in_registered_order() {
#[allow(clippy::disallowed_types, reason = "test-only recorder")]
let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
let o1 = Arc::clone(&order);
let o2 = Arc::clone(&order);
let o3 = Arc::clone(&order);
let o4 = Arc::clone(&order);
let mgr = SigningStepManager::new()
.with_before_bridging_sign(move || {
let o = Arc::clone(&o1);
Box::pin(async move {
o.lock().unwrap().push("before_bridging_sign");
Ok(())
})
})
.with_after_bridging_sign(move || {
let o = Arc::clone(&o2);
Box::pin(async move {
o.lock().unwrap().push("after_bridging_sign");
Ok(())
})
})
.with_before_order_sign(move || {
let o = Arc::clone(&o3);
Box::pin(async move {
o.lock().unwrap().push("before_order_sign");
Ok(())
})
})
.with_after_order_sign(move || {
let o = Arc::clone(&o4);
Box::pin(async move {
o.lock().unwrap().push("after_order_sign");
Ok(())
})
});
mgr.fire_before_bridging_sign().await.unwrap();
mgr.fire_after_bridging_sign().await.unwrap();
mgr.fire_before_order_sign().await.unwrap();
mgr.fire_after_order_sign().await.unwrap();
let recorded: Vec<&str> = order.lock().unwrap().clone();
assert_eq!(
recorded,
vec![
"before_bridging_sign",
"after_bridging_sign",
"before_order_sign",
"after_order_sign",
],
);
}
#[tokio::test]
async fn empty_manager_is_no_op() {
let mgr = SigningStepManager::new();
mgr.fire_before_bridging_sign().await.unwrap();
mgr.fire_after_bridging_sign().await.unwrap();
mgr.fire_before_order_sign().await.unwrap();
mgr.fire_after_order_sign().await.unwrap();
mgr.fire_on_bridging_sign_error(&CowError::AppData("x".into()));
mgr.fire_on_order_sign_error(&CowError::AppData("x".into()));
}
#[tokio::test]
async fn before_callback_error_is_propagated() {
let mgr = SigningStepManager::new().with_before_bridging_sign(|| {
Box::pin(async { Err(CowError::AppData("abort".into())) })
});
let err = mgr.fire_before_bridging_sign().await.unwrap_err();
assert!(err.to_string().contains("abort"));
}
#[tokio::test]
async fn before_order_sign_error_is_propagated() {
let mgr = SigningStepManager::new()
.with_before_order_sign(|| Box::pin(async { Err(CowError::AppData("stop".into())) }));
let err = mgr.fire_before_order_sign().await.unwrap_err();
assert!(err.to_string().contains("stop"));
}
#[test]
fn sync_error_callbacks_fire_exactly_once() {
let bridge_count = Arc::new(AtomicU8::new(0));
let order_count = Arc::new(AtomicU8::new(0));
let bc = Arc::clone(&bridge_count);
let oc = Arc::clone(&order_count);
let mgr = SigningStepManager::new()
.with_on_bridging_sign_error(move |_err| {
bc.fetch_add(1, Ordering::SeqCst);
})
.with_on_order_sign_error(move |_err| {
oc.fetch_add(1, Ordering::SeqCst);
});
mgr.fire_on_bridging_sign_error(&CowError::AppData("e".into()));
mgr.fire_on_order_sign_error(&CowError::AppData("e".into()));
assert_eq!(bridge_count.load(Ordering::SeqCst), 1);
assert_eq!(order_count.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn sync_helpers_fire_in_registered_order() {
#[allow(clippy::disallowed_types, reason = "test-only recorder")]
let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
let o1 = Arc::clone(&order);
let o2 = Arc::clone(&order);
let o3 = Arc::clone(&order);
let o4 = Arc::clone(&order);
let mgr = SigningStepManager::new()
.with_before_bridging_sign_sync(move || {
o1.lock().unwrap().push("before_bridging_sign");
})
.with_after_bridging_sign_sync(move || {
o2.lock().unwrap().push("after_bridging_sign");
})
.with_before_order_sign_sync(move || {
o3.lock().unwrap().push("before_order_sign");
})
.with_after_order_sign_sync(move || {
o4.lock().unwrap().push("after_order_sign");
});
mgr.fire_before_bridging_sign().await.unwrap();
mgr.fire_after_bridging_sign().await.unwrap();
mgr.fire_before_order_sign().await.unwrap();
mgr.fire_after_order_sign().await.unwrap();
let recorded: Vec<&str> = order.lock().unwrap().clone();
assert_eq!(
recorded,
vec![
"before_bridging_sign",
"after_bridging_sign",
"before_order_sign",
"after_order_sign",
],
);
}
#[tokio::test]
async fn sync_helpers_always_return_ok() {
let mgr = SigningStepManager::new()
.with_before_bridging_sign_sync(|| {})
.with_after_bridging_sign_sync(|| {})
.with_before_order_sign_sync(|| {})
.with_after_order_sign_sync(|| {});
assert!(mgr.fire_before_bridging_sign().await.is_ok());
assert!(mgr.fire_after_bridging_sign().await.is_ok());
assert!(mgr.fire_before_order_sign().await.is_ok());
assert!(mgr.fire_after_order_sign().await.is_ok());
}
#[tokio::test]
async fn sync_and_async_helpers_compose() {
let sync_fired = Arc::new(AtomicU8::new(0));
let sf = Arc::clone(&sync_fired);
let mgr = SigningStepManager::new()
.with_before_bridging_sign_sync(move || {
sf.fetch_add(1, Ordering::SeqCst);
})
.with_after_bridging_sign(|| {
Box::pin(async move { Err(CowError::AppData("stop after bridging".into())) })
});
mgr.fire_before_bridging_sign().await.unwrap();
assert_eq!(sync_fired.load(Ordering::SeqCst), 1);
let err = mgr.fire_after_bridging_sign().await.unwrap_err();
assert!(err.to_string().contains("stop after bridging"));
}
}