audio-io-bsd 0.2.0

Audio I/O backend abstraction (AudioBackend trait) with a cpal ALSA/OSS backend for FreeBSD-first real-time audio
//! Crate error types and the [`Result`] alias.
//!
//! [`IoError`] is the single error enum returned by every fallible operation in
//! this crate. It wraps the shared [`audio_core_bsd::AudioError`] (for sample /
//! buffer / channel validation) and adds I/O-backend-specific variants that do
//! not belong in the platform-agnostic core crate.

use std::io;

use audio_core_bsd::AudioError;

/// Errors returned by audio I/O backend operations.
///
/// This enum is the union of two concern areas:
///
/// - **Backend / device failures** — opening a device, enumerating it, or
///   building a stream. These are represented by the string-carrying variants
///   (`Backend`, `DeviceNotFound`, `StreamSetup`, `UnsupportedConfig`) so a
///   concrete backend (e.g. `CpalBackend`) can surface a human-readable
///   cause without leaking its own internal error types through the trait
///   boundary.
/// - **Core data validation** — sample rate, channel count, buffer size. These
///   are delegated to [`audio_core_bsd::AudioError`] via the [`IoError::Core`]
///   variant so callers share one validation vocabulary with the core types.
#[derive(Debug, thiserror::Error)]
pub enum IoError {
    /// The backend host could not be initialised (e.g. no audio subsystem).
    #[error("backend initialisation failed: {0}")]
    Backend(String),

    /// No device matching the requested name was found.
    #[error("device not found: {0}")]
    DeviceNotFound(String),

    /// A stream could not be built for the requested parameters.
    #[error("stream setup failed: {0}")]
    StreamSetup(String),

    /// The requested stream configuration is not supported by the device.
    #[error("unsupported stream configuration: {0}")]
    UnsupportedConfig(String),

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

    /// A raw I/O error from the operating system.
    #[error(transparent)]
    Io(#[from] io::Error),
}

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

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

    use audio_core_bsd::AudioError;

    use super::{IoError, Result};

    #[test]
    fn backend_display_carries_message() {
        let msg = IoError::Backend("no host".into()).to_string();
        assert_eq!(msg, "backend initialisation failed: no host");
    }

    #[test]
    fn device_not_found_display_carries_name() {
        let msg = IoError::DeviceNotFound("/dev/dsp0".into()).to_string();
        assert_eq!(msg, "device not found: /dev/dsp0");
    }

    #[test]
    fn stream_setup_display_carries_message() {
        let msg = IoError::StreamSetup("buffer size 0".into()).to_string();
        assert_eq!(msg, "stream setup failed: buffer size 0");
    }

    #[test]
    fn unsupported_config_display_carries_message() {
        let msg = IoError::UnsupportedConfig("192000 Hz not supported".into()).to_string();
        assert_eq!(
            msg,
            "unsupported stream configuration: 192000 Hz not supported"
        );
    }

    #[test]
    fn core_variant_wraps_audio_error() {
        let err = IoError::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: IoError = core.clone().into();
        assert!(matches!(wrapped, IoError::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 = IoError::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_io_error() {
        let err: Result<u32> = Err(IoError::DeviceNotFound("x".into()));
        assert!(err.is_err());
    }
}