mdf4_rs/
error.rs

1//! Error types for MDF4 operations.
2//!
3//! This module defines the [`Error`] enum which represents all possible failures
4//! that can occur when reading, writing, or processing MDF files.
5//!
6//! # Example
7//!
8//! ```no_run
9//! # #[cfg(feature = "std")]
10//! use mdf4_rs::{MDF, Error, Result};
11//!
12//! # #[cfg(feature = "std")]
13//! fn process_file(path: &str) -> Result<()> {
14//!     match MDF::from_file(path) {
15//!         Ok(mdf) => {
16//!             println!("Loaded {} channel groups", mdf.channel_groups().len());
17//!             Ok(())
18//!         }
19//!         Err(Error::IOError(e)) => {
20//!             eprintln!("File I/O error: {}", e);
21//!             Err(Error::IOError(e))
22//!         }
23//!         Err(Error::FileIdentifierError(id)) => {
24//!             eprintln!("Not a valid MDF file: {}", id);
25//!             Err(Error::FileIdentifierError(id))
26//!         }
27//!         Err(e) => Err(e),
28//!     }
29//! }
30//! ```
31
32use core::fmt;
33
34#[cfg(feature = "alloc")]
35use alloc::string::String;
36
37/// Errors that can occur during MDF file operations.
38///
39/// This enum covers all failure modes including I/O errors, parsing failures,
40/// and structural issues in the MDF file.
41#[derive(Debug)]
42pub enum Error {
43    /// Buffer provided for parsing was too small.
44    ///
45    /// This typically indicates file corruption or an incomplete read.
46    TooShortBuffer {
47        /// Actual number of bytes available
48        actual: usize,
49        /// Minimum number of bytes required
50        expected: usize,
51        /// Source file where the error was detected
52        file: &'static str,
53        /// Line number where the error was detected
54        line: u32,
55    },
56
57    /// The file identifier is not "MDF     " as required by the specification.
58    ///
59    /// This can occur when trying to open a non-MDF file or a file using an
60    /// unsupported variant like "UnFinMF" (unfinalized MDF).
61    FileIdentifierError(String),
62
63    /// The MDF version is not supported (requires 4.1 or later).
64    FileVersioningError(String),
65
66    /// A block identifier did not match the expected value.
67    ///
68    /// Each MDF block starts with a 4-character identifier (e.g., "##HD" for
69    /// the header block). This error indicates structural corruption.
70    BlockIDError {
71        /// The identifier that was found
72        actual: String,
73        /// The identifier that was expected
74        expected: String,
75    },
76
77    /// An I/O error occurred while reading or writing the file.
78    ///
79    /// Only available with the `std` feature.
80    #[cfg(feature = "std")]
81    IOError(std::io::Error),
82
83    /// A write operation failed (no_std version).
84    ///
85    /// Only available without the `std` feature.
86    #[cfg(not(feature = "std"))]
87    WriteError,
88
89    /// The version string in the identification block could not be parsed.
90    InvalidVersionString(String),
91
92    /// Failed to link blocks together during file writing.
93    ///
94    /// This typically indicates a programming error where blocks are
95    /// referenced before being written.
96    BlockLinkError(String),
97
98    /// Failed to serialize a block to bytes.
99    BlockSerializationError(String),
100
101    /// A conversion chain exceeded the maximum allowed depth.
102    ///
103    /// MDF supports chained conversions where one conversion references another.
104    /// This error prevents infinite loops from malformed files.
105    ConversionChainTooDeep {
106        /// The maximum depth that was exceeded
107        max_depth: usize,
108    },
109
110    /// A cycle was detected in a conversion chain.
111    ///
112    /// This indicates file corruption where conversion blocks form a loop.
113    ConversionChainCycle {
114        /// The address where the cycle was detected
115        address: u64,
116    },
117}
118
119impl fmt::Display for Error {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            Error::TooShortBuffer {
123                actual,
124                expected,
125                file,
126                line,
127            } => write!(
128                f,
129                "Buffer too small at {file}:{line}: need at least {expected} bytes, got {actual}"
130            ),
131            Error::FileIdentifierError(id) => {
132                write!(
133                    f,
134                    r#"Invalid file identifier: Expected "MDF     ", found {id}"#
135                )
136            }
137            Error::FileVersioningError(ver) => {
138                write!(f, r#"File version too low: Expected "> 4.1", found {ver}"#)
139            }
140            Error::BlockIDError { actual, expected } => {
141                write!(
142                    f,
143                    "Invalid block identifier: Expected {expected:?}, got {actual:?}"
144                )
145            }
146            #[cfg(feature = "std")]
147            Error::IOError(e) => write!(f, "I/O error: {e}"),
148            #[cfg(not(feature = "std"))]
149            Error::WriteError => write!(f, "Write error"),
150            Error::InvalidVersionString(s) => write!(f, "Invalid version string: {s}"),
151            Error::BlockLinkError(s) => write!(f, "Block linking error: {s}"),
152            Error::BlockSerializationError(s) => write!(f, "Block serialization error: {s}"),
153            Error::ConversionChainTooDeep { max_depth } => {
154                write!(
155                    f,
156                    "Conversion chain too deep: maximum depth of {max_depth} exceeded"
157                )
158            }
159            Error::ConversionChainCycle { address } => {
160                write!(
161                    f,
162                    "Conversion chain cycle detected at block address {address:#x}"
163                )
164            }
165        }
166    }
167}
168
169#[cfg(feature = "std")]
170impl std::error::Error for Error {
171    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
172        match self {
173            Error::IOError(e) => Some(e),
174            _ => None,
175        }
176    }
177}
178
179#[cfg(feature = "std")]
180impl From<std::io::Error> for Error {
181    fn from(err: std::io::Error) -> Self {
182        Error::IOError(err)
183    }
184}
185
186/// A specialized Result type for MDF operations.
187///
188/// This is defined as `core::result::Result<T, Error>` for convenience.
189pub type Result<T> = core::result::Result<T, Error>;