use std::future::Future;
use crate::{Error, Result};
pub(crate) async fn drain_undeclare<T, F, Fut>(items: Vec<(String, T)>, undeclare: F) -> Result<()>
where
F: Fn(T) -> Fut,
Fut: Future<Output = Result<()>>,
{
let mut failed = Vec::new();
for (label, item) in items {
if let Err(e) = undeclare(item).await {
failed.push(format!("{label}: {}", crate::one_line(&e)));
}
}
if failed.is_empty() {
Ok(())
} else {
Err(Error::bus(
"undeclare",
failed.join("; "),
"one or more handles refused",
))
}
}
pub const DECLARE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
pub(crate) async fn declared<T, E>(
op: &'static str,
target: impl std::fmt::Display,
builder: impl std::future::IntoFuture<Output = std::result::Result<T, E>>,
) -> Result<T>
where
E: std::fmt::Display,
{
match tokio::time::timeout(DECLARE_TIMEOUT, builder.into_future()).await {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(Error::bus(op, target.to_string(), e.to_string())),
Err(_) => Err(Error::bus(
op,
target.to_string(),
format!("did not complete within {DECLARE_TIMEOUT:?}"),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[tokio::test]
async fn a_failure_stops_nothing_and_every_failure_is_reported() {
let visited = AtomicUsize::new(0);
let err = drain_undeclare(
vec![
("first".to_string(), Ok(())),
("second".to_string(), Err("busy")),
("third".to_string(), Ok(())),
("fourth".to_string(), Err("gone")),
],
|outcome: std::result::Result<(), &str>| {
visited.fetch_add(1, Ordering::Relaxed);
async move { outcome.map_err(|e| Error::bus("undeclare", "handle", e)) }
},
)
.await
.expect_err("two of the four would not undeclare")
.to_string();
assert_eq!(visited.load(Ordering::Relaxed), 4, "every item was drained");
for (label, reason) in [("second", "busy"), ("fourth", "gone")] {
assert!(err.contains(label), "{label} missing from: {err}");
assert!(err.contains(reason), "{reason} missing from: {err}");
}
}
#[tokio::test]
async fn everything_undeclared_is_silent() {
let all_fine = drain_undeclare(vec![("only".to_string(), ())], |()| async { Ok(()) }).await;
assert!(all_fine.is_ok());
}
}