audio-clock-bsd 0.1.1

PTP/NTP clock synchronization and sample-index to timestamp conversion for real-time audio
Documentation
//! Crate error types and the [`Result`] alias.

use std::io;

use audio_core_bsd::AudioError;

/// Errors returned by clock-source operations.
///
/// These cover the two concerns of this crate:
///
/// - **Clock availability / sync** — whether a usable time reference exists at
///   all (PTP daemon reachable, NTP synced). Represented by [`ClockError::Unavailable`]
///   and [`ClockError::NotSynced`].
/// - **Conversion integrity** — sample-rate validation and the checked
///   arithmetic that guards [`crate::ClockSource::sample_to_ptp`] /
///   [`crate::ClockSource::ptp_to_sample`] against overflow. Represented by
///   [`ClockError::InvalidSampleRate`], [`ClockError::ConversionOverflow`], and
///   the core-delegated [`ClockError::Core`].
#[derive(Debug, thiserror::Error)]
pub enum ClockError {
    /// No clock source is available on this host (e.g. no PTP daemon running,
    /// no PTP-capable NIC).
    #[error("clock source unavailable: {0}")]
    Unavailable(String),

    /// The clock exists but has not yet synchronised (e.g. NTP still
    /// converging, PTP master not yet locked).
    #[error("clock not yet synchronised: {0}")]
    NotSynced(String),

    /// The provided sample rate is not a positive, supported value.
    #[error("invalid sample rate: {0} Hz")]
    InvalidSampleRate(u32),

    /// A sample-index ↔ timestamp conversion would overflow `i64`.
    ///
    /// This indicates an anchor or sample index absurdly far from the clock's
    /// reference point; callers should re-anchor closer to the working range.
    #[error("timestamp conversion overflow")]
    ConversionOverflow,

    /// A validation failure delegated to the shared core types.
    #[error(transparent)]
    Core(#[from] AudioError),

    /// A raw I/O error from the operating system (e.g. reading a PTP device).
    #[error(transparent)]
    Io(#[from] io::Error),
}

/// Convenience alias used throughout the crate and by consumers.
pub type Result<T> = core::result::Result<T, ClockError>;

#[cfg(test)]
mod tests {
    use std::io;

    use audio_core_bsd::AudioError;

    use super::{ClockError, Result};

    #[test]
    fn unavailable_display_carries_message() {
        let msg = ClockError::Unavailable("no ptpd".into()).to_string();
        assert_eq!(msg, "clock source unavailable: no ptpd");
    }

    #[test]
    fn not_synced_display_carries_message() {
        let msg = ClockError::NotSynced("ntp converging".into()).to_string();
        assert_eq!(msg, "clock not yet synchronised: ntp converging");
    }

    #[test]
    fn invalid_sample_rate_display_includes_value() {
        let msg = ClockError::InvalidSampleRate(0).to_string();
        assert_eq!(msg, "invalid sample rate: 0 Hz");
    }

    #[test]
    fn conversion_overflow_display() {
        assert_eq!(
            ClockError::ConversionOverflow.to_string(),
            "timestamp conversion overflow"
        );
    }

    #[test]
    fn core_variant_wraps_audio_error() {
        let err = ClockError::from(AudioError::InvalidSampleRate(0));
        assert_eq!(err.to_string(), "invalid sample rate: 0 Hz");
    }

    #[test]
    fn core_from_audio_error_preserves_identity() {
        let core = AudioError::InvalidChannelCount(0);
        let wrapped: ClockError = core.clone().into();
        assert!(matches!(wrapped, ClockError::Core(ref e) if *e == core));
    }

    #[test]
    fn io_variant_wraps_std_io_error() {
        let io_err = io::Error::new(io::ErrorKind::NotFound, "missing");
        let err = ClockError::from(io_err);
        assert!(err.to_string().contains("missing"));
    }

    #[test]
    fn result_alias_ok_carries_value() {
        let ok: Result<u32> = Ok(7);
        assert_eq!(ok.map(|v| v + 1).unwrap(), 8);
    }

    #[test]
    fn result_alias_err_carries_clock_error() {
        let err: Result<u32> = Err(ClockError::Unavailable("x".into()));
        assert!(err.is_err());
    }
}