liminal-server 0.23.1

Standalone server for the liminal messaging bus
Documentation
//! The tap's failures: a lock another thread poisoned, a station list its
//! two passes disagreed on, and hops that could not be kept as measured.

/// A tap or console operation found a lock poisoned and refused to continue
/// with a guard it cannot trust.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum TapError {
    /// The named lock was poisoned by a panic on another thread.
    #[error("console {what} lock is poisoned")]
    Poisoned {
        /// Which lock.
        what: &'static str,
    },
    /// The two passes that build a message's station list disagreed: the
    /// count named `named` stations and the fill found `found`. The list is
    /// refused, never filled in, and the message is kept as not recorded.
    #[error("tap station list refused: counted {named} stations, found {found}")]
    StationsDisagree {
        /// Stations the counting pass named.
        named: usize,
        /// Stations the filling pass found.
        found: usize,
    },
    /// A kept message's hop was measured earlier than the hop before it,
    /// which monotonic instants taken in order never give (lane brief
    /// ruling 3, Hermes 04:09). The message is kept with its hops as
    /// measured, never rewritten, and the observe is counted as failed.
    #[error("tap hop {hop} measured at {at_ms} ms, before the hop ahead of it at {before_ms} ms")]
    HopsOutOfOrder {
        /// Where the hop stood in the message's chain.
        hop: usize,
        /// Its measured milliseconds after first sight.
        at_ms: u64,
        /// The hop before it, measured.
        before_ms: u64,
    },
    /// A kept message's hop was measured before the message was first seen
    /// (Buttercup's V2, Hermes 04:27). `at_ms` cannot carry it, so the hop
    /// is left out of the record, never written as 0, and the observe is
    /// counted as failed.
    #[error("tap hop {hop} measured {early_ms} ms before first sight; left out")]
    HopBeforeFirstSight {
        /// Where the hop stood in the message's chain.
        hop: usize,
        /// How long before first sight it was measured.
        early_ms: u64,
    },
    /// A kept message's hop was measured further from first sight than
    /// `at_ms` can carry. The hop is left out, never written as the most it
    /// can hold, and the observe is counted as failed.
    #[error("tap hop {hop} measured beyond u64 milliseconds; left out")]
    HopOverflow {
        /// Where the hop stood in the message's chain.
        hop: usize,
    },
    /// More than one of the above on one observe, each reported (Buttercup's
    /// V3, Hermes 04:27): a refused station list never hides a hop. One
    /// entry per kind, the first hop of each.
    #[error("tap observe failed {} ways: {}", .0.len(), display_all(.0))]
    Several(Vec<Self>),
}

impl TapError {
    /// How many failures this is: one, or each of [`Self::Several`].
    #[must_use]
    pub fn failures(&self) -> usize {
        match self {
            Self::Several(each) => each.len(),
            _ => 1,
        }
    }
}

fn display_all(each: &[TapError]) -> String {
    each.iter()
        .map(ToString::to_string)
        .collect::<Vec<_>>()
        .join("; ")
}

/// Locks `mutex`, naming it in the error when it is poisoned.
///
/// # Errors
/// Returns [`TapError::Poisoned`] when another thread panicked holding it.
pub fn lock<'a, T>(
    mutex: &'a std::sync::Mutex<T>,
    what: &'static str,
) -> Result<std::sync::MutexGuard<'a, T>, TapError> {
    mutex.lock().map_err(|_| TapError::Poisoned { what })
}