Skip to main content

libflo_audio/core/
error.rs

1//! Shared flo error types
2
3use alloc::boxed::Box;
4use alloc::string::{String, ToString};
5use core::fmt;
6
7/// Category of a [`FloError`]
8#[non_exhaustive]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum FloErrorKind {
11    /// The bytes are not a well-formed flo file (bad magic, header, CRC, frames…).
12    InvalidData,
13    /// A feature or option this build doesn't support (wasm/audio-io/etc).
14    UnsupportedFeature,
15    /// I/O failure. `std` builds carry the underlying source error.
16    Io,
17    /// Codec / signal-processing failure.
18    Codec,
19    /// Metadata (de)serialization failure.
20    Metadata,
21    /// Lossy encode/decode failure.
22    Lossy,
23    /// Lossless encode/decode failure.
24    Lossless,
25    /// WebAssembly bridge failure.
26    Wasm,
27    /// Internal invariant broken: a bug, not a data problem.
28    Internal,
29}
30
31/// The one error type for the flo workspace.
32pub struct FloError {
33    kind: FloErrorKind,
34    message: String,
35    source: Option<Box<dyn core::error::Error + Send + Sync + 'static>>,
36}
37
38impl Clone for FloError {
39    fn clone(&self) -> Self {
40        Self {
41            kind: self.kind,
42            message: self.message.clone(),
43            // `Box<dyn Error>` is not cloneable; the source chain is not copied.
44            source: None,
45        }
46    }
47}
48
49impl FloError {
50    /// Build an error from a category and a message.
51    pub fn new<T: Into<String>>(kind: FloErrorKind, message: T) -> Self {
52        Self {
53            kind,
54            message: message.into(),
55            source: None,
56        }
57    }
58
59    /// The error's category.
60    pub fn kind(&self) -> FloErrorKind {
61        self.kind
62    }
63
64    /// The human-readable message.
65    pub fn message(&self) -> &str {
66        &self.message
67    }
68
69    /// Attach a wrapped message.
70    pub fn with_context<T: Into<String>>(mut self, ctx: T) -> Self {
71        let ctx = ctx.into();
72        if self.message.is_empty() {
73            self.message = ctx;
74        } else {
75            self.message = alloc::format!("{ctx}: {}", self.message);
76        }
77        self
78    }
79
80    pub fn with_source(mut self, source: impl core::error::Error + Send + Sync + 'static) -> Self {
81        self.source = Some(Box::new(source));
82        self
83    }
84
85    /// Box this error for use as `Box<dyn Error>` in streaming/reader contexts.
86    pub fn into_boxed(self) -> Box<FloError> {
87        Box::new(self)
88    }
89}
90
91impl fmt::Display for FloError {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        fmt::Display::fmt(&self.message, f)
94    }
95}
96
97impl fmt::Debug for FloError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        let mut d = f.debug_struct("FloError");
100        d.field("kind", &format_args!("{:?}", self.kind));
101        d.field("message", &self.message);
102        d.finish()
103    }
104}
105
106impl core::error::Error for FloError {
107    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
108        self.source
109            .as_deref()
110            .map(|e| e as &(dyn core::error::Error + 'static))
111    }
112}
113
114impl From<fmt::Error> for FloError {
115    fn from(e: fmt::Error) -> Self {
116        Self::new(FloErrorKind::Codec, e.to_string())
117    }
118}
119
120impl From<String> for FloError {
121    fn from(s: String) -> Self {
122        Self::new(FloErrorKind::Internal, s)
123    }
124}
125
126impl From<&str> for FloError {
127    fn from(s: &str) -> Self {
128        Self::new(FloErrorKind::Internal, s.to_string())
129    }
130}
131
132/// Extension trait for adding context to [`FloError`] instances.
133pub trait FloErrorExt<T> {
134    /// Wrap the error in a context message, producing a [`FloResult`].
135    fn context(self, ctx: impl Into<String>) -> Result<T, FloError>;
136}
137
138impl<T> FloErrorExt<T> for Result<T, FloError> {
139    fn context(self, ctx: impl Into<String>) -> Result<T, FloError> {
140        self.map_err(|e| e.with_context(ctx))
141    }
142}
143
144/// Result alias
145pub type FloResult<T> = Result<T, FloError>;