Skip to main content

nexcore_audio/
error.rs

1// Copyright (c) 2026 Matthew Campion, PharmD; NexVigilant
2// All Rights Reserved. See LICENSE file for details.
3
4//! Audio subsystem error types.
5//!
6//! Tier: T2-P (Σ Sum — error variant union)
7
8use serde::{Deserialize, Serialize};
9
10/// Audio subsystem errors.
11///
12/// Tier: T2-P (Σ Sum — all audio failure modes)
13#[non_exhaustive]
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub enum AudioError {
16    /// Device not found by ID.
17    DeviceNotFound(String),
18    /// Device is already in use.
19    DeviceInUse(String),
20    /// Unsupported sample format.
21    UnsupportedFormat {
22        /// What was requested.
23        requested: String,
24        /// What the device supports.
25        supported: Vec<String>,
26    },
27    /// Unsupported sample rate.
28    UnsupportedSampleRate {
29        /// Requested rate in Hz.
30        requested: u32,
31        /// Supported rates.
32        supported: Vec<u32>,
33    },
34    /// Buffer overflow — producer is faster than consumer.
35    BufferOverflow {
36        /// Samples lost.
37        samples_lost: usize,
38    },
39    /// Buffer underrun — consumer is faster than producer.
40    BufferUnderrun {
41        /// Samples of silence inserted.
42        silence_inserted: usize,
43    },
44    /// Stream is not in correct state for this operation.
45    InvalidState {
46        /// Current state.
47        current: String,
48        /// Required state.
49        required: String,
50    },
51    /// Volume out of range [0.0, 1.0].
52    VolumeOutOfRange(String),
53    /// Codec error.
54    CodecError(String),
55    /// Platform/driver error.
56    PlatformError(String),
57}
58
59impl std::fmt::Display for AudioError {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            Self::DeviceNotFound(id) => write!(f, "audio device not found: {id}"),
63            Self::DeviceInUse(id) => write!(f, "audio device in use: {id}"),
64            Self::UnsupportedFormat {
65                requested,
66                supported,
67            } => {
68                write!(
69                    f,
70                    "unsupported format {requested}, supported: {supported:?}"
71                )
72            }
73            Self::UnsupportedSampleRate {
74                requested,
75                supported,
76            } => {
77                write!(
78                    f,
79                    "unsupported sample rate {requested}Hz, supported: {supported:?}"
80                )
81            }
82            Self::BufferOverflow { samples_lost } => {
83                write!(f, "buffer overflow: {samples_lost} samples lost")
84            }
85            Self::BufferUnderrun { silence_inserted } => {
86                write!(f, "buffer underrun: {silence_inserted} samples of silence")
87            }
88            Self::InvalidState { current, required } => {
89                write!(f, "invalid state: {current}, required: {required}")
90            }
91            Self::VolumeOutOfRange(v) => write!(f, "volume out of range: {v}"),
92            Self::CodecError(msg) => write!(f, "codec error: {msg}"),
93            Self::PlatformError(msg) => write!(f, "platform error: {msg}"),
94        }
95    }
96}
97
98impl std::error::Error for AudioError {}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn device_not_found_display() {
106        let e = AudioError::DeviceNotFound("hw:0".into());
107        assert!(e.to_string().contains("hw:0"));
108    }
109
110    #[test]
111    fn buffer_overflow_display() {
112        let e = AudioError::BufferOverflow { samples_lost: 42 };
113        assert!(e.to_string().contains("42"));
114    }
115
116    #[test]
117    fn unsupported_format_display() {
118        let e = AudioError::UnsupportedFormat {
119            requested: "F64".into(),
120            supported: vec!["S16".into(), "F32".into()],
121        };
122        let s = e.to_string();
123        assert!(s.contains("F64"));
124        assert!(s.contains("S16"));
125    }
126
127    #[test]
128    fn all_variants_display() {
129        let variants: Vec<AudioError> = vec![
130            AudioError::DeviceNotFound("x".into()),
131            AudioError::DeviceInUse("x".into()),
132            AudioError::UnsupportedFormat {
133                requested: "x".into(),
134                supported: vec![],
135            },
136            AudioError::UnsupportedSampleRate {
137                requested: 96000,
138                supported: vec![44100, 48000],
139            },
140            AudioError::BufferOverflow { samples_lost: 0 },
141            AudioError::BufferUnderrun {
142                silence_inserted: 0,
143            },
144            AudioError::InvalidState {
145                current: "a".into(),
146                required: "b".into(),
147            },
148            AudioError::VolumeOutOfRange("1.5".into()),
149            AudioError::CodecError("bad".into()),
150            AudioError::PlatformError("driver".into()),
151        ];
152        for v in &variants {
153            assert!(!v.to_string().is_empty());
154        }
155        assert_eq!(variants.len(), 10);
156    }
157
158    #[test]
159    fn error_trait_impl() {
160        let e = AudioError::CodecError("test".into());
161        let _: &dyn std::error::Error = &e;
162    }
163
164    #[test]
165    fn clone_and_eq() {
166        let a = AudioError::DeviceNotFound("hw:0".into());
167        let b = a.clone();
168        assert_eq!(a, b);
169    }
170
171    #[test]
172    fn debug_format() {
173        let e = AudioError::BufferUnderrun {
174            silence_inserted: 10,
175        };
176        let debug = format!("{e:?}");
177        assert!(debug.contains("BufferUnderrun"));
178    }
179}