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 /// A cycle was detected while walking a linked list of blocks (e.g. the
119 /// data-group, channel-group, channel, or data-list chain).
120 ///
121 /// This indicates file corruption where block links form a loop, which
122 /// would otherwise cause the parser to loop forever.
123 LinkCycle {
124 /// The link address that was visited twice
125 address: u64,
126 },
127}
128
129impl fmt::Display for Error {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 Error::TooShortBuffer {
133 actual,
134 expected,
135 file,
136 line,
137 } => write!(
138 f,
139 "Buffer too small at {file}:{line}: need at least {expected} bytes, got {actual}"
140 ),
141 Error::FileIdentifierError(id) => {
142 write!(
143 f,
144 r#"Invalid file identifier: Expected "MDF ", found {id}"#
145 )
146 }
147 Error::FileVersioningError(ver) => {
148 write!(f, r#"File version too low: Expected "> 4.1", found {ver}"#)
149 }
150 Error::BlockIDError { actual, expected } => {
151 write!(
152 f,
153 "Invalid block identifier: Expected {expected:?}, got {actual:?}"
154 )
155 }
156 #[cfg(feature = "std")]
157 Error::IOError(e) => write!(f, "I/O error: {e}"),
158 #[cfg(not(feature = "std"))]
159 Error::WriteError => write!(f, "Write error"),
160 Error::InvalidVersionString(s) => write!(f, "Invalid version string: {s}"),
161 Error::BlockLinkError(s) => write!(f, "Block linking error: {s}"),
162 Error::BlockSerializationError(s) => write!(f, "Block serialization error: {s}"),
163 Error::ConversionChainTooDeep { max_depth } => {
164 write!(
165 f,
166 "Conversion chain too deep: maximum depth of {max_depth} exceeded"
167 )
168 }
169 Error::ConversionChainCycle { address } => {
170 write!(
171 f,
172 "Conversion chain cycle detected at block address {address:#x}"
173 )
174 }
175 Error::LinkCycle { address } => {
176 write!(f, "Block link cycle detected at address {address:#x}")
177 }
178 }
179 }
180}
181
182#[cfg(feature = "std")]
183impl std::error::Error for Error {
184 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
185 match self {
186 Error::IOError(e) => Some(e),
187 _ => None,
188 }
189 }
190}
191
192#[cfg(feature = "std")]
193impl From<std::io::Error> for Error {
194 fn from(err: std::io::Error) -> Self {
195 Error::IOError(err)
196 }
197}
198
199/// A specialized Result type for MDF operations.
200///
201/// This is defined as `core::result::Result<T, Error>` for convenience.
202pub type Result<T> = core::result::Result<T, Error>;