espresso_logic/espresso/
error.rs

1//! Error types for Espresso instance management and cube operations
2
3use std::fmt;
4use std::io;
5
6/// Errors related to Espresso instance management
7///
8/// These errors occur when trying to create or use Espresso instances with
9/// conflicting dimensions or configurations on the same thread.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum InstanceError {
12    /// The requested dimensions don't match the existing thread-local instance
13    DimensionMismatch {
14        /// The requested dimensions (num_inputs, num_outputs)
15        requested: (usize, usize),
16        /// The existing instance's dimensions (num_inputs, num_outputs)
17        existing: (usize, usize),
18    },
19    /// The requested configuration doesn't match the existing thread-local instance
20    ConfigMismatch {
21        /// The requested dimensions (num_inputs, num_outputs)
22        requested: (usize, usize),
23        /// The existing instance's dimensions (num_inputs, num_outputs)
24        existing: (usize, usize),
25    },
26}
27
28impl fmt::Display for InstanceError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            InstanceError::DimensionMismatch {
32                requested,
33                existing,
34            } => write!(
35                f,
36                "Cannot create Espresso instance with dimensions {:?} because a \
37                 thread-local instance with dimensions {:?} already exists. \
38                 Drop all existing covers and handles first.",
39                requested, existing
40            ),
41            InstanceError::ConfigMismatch {
42                requested,
43                existing,
44            } => write!(
45                f,
46                "Cannot create Espresso instance with different configuration while a \
47                 thread-local instance with dimensions {:?} already exists (requested {:?}). \
48                 Drop all existing covers and handles first.",
49                existing, requested
50            ),
51        }
52    }
53}
54
55impl std::error::Error for InstanceError {}
56
57impl From<InstanceError> for io::Error {
58    fn from(err: InstanceError) -> Self {
59        io::Error::other(err)
60    }
61}
62
63/// Errors related to cube validation
64///
65/// These errors occur when invalid cube values are provided during cover creation.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub enum CubeError {
68    /// Invalid cube value encountered
69    ///
70    /// Cube input values must be 0 (low), 1 (high), or 2 (don't care).
71    InvalidValue {
72        /// The invalid value that was encountered
73        value: u8,
74        /// The position in the input vector where the invalid value occurred
75        position: usize,
76    },
77}
78
79impl fmt::Display for CubeError {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            CubeError::InvalidValue { value, position } => write!(
83                f,
84                "Invalid cube value {} at position {}. Expected 0 (low), 1 (high), or 2 (don't care).",
85                value, position
86            ),
87        }
88    }
89}
90
91impl std::error::Error for CubeError {}
92
93impl From<CubeError> for io::Error {
94    fn from(err: CubeError) -> Self {
95        io::Error::new(io::ErrorKind::InvalidData, err)
96    }
97}
98
99/// Errors that can occur during minimization operations
100///
101/// This error type is returned by `Cover::minimize()` and `BoolExpr::minimize()`.
102#[derive(Debug)]
103pub enum MinimizationError {
104    /// Instance management error
105    Instance(InstanceError),
106    /// Cube validation error
107    Cube(CubeError),
108    /// IO error during minimization
109    Io(io::Error),
110}
111
112impl fmt::Display for MinimizationError {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            MinimizationError::Instance(e) => write!(f, "Instance error: {}", e),
116            MinimizationError::Cube(e) => write!(f, "Cube error: {}", e),
117            MinimizationError::Io(e) => write!(f, "IO error: {}", e),
118        }
119    }
120}
121
122impl std::error::Error for MinimizationError {
123    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
124        match self {
125            MinimizationError::Instance(e) => Some(e),
126            MinimizationError::Cube(e) => Some(e),
127            MinimizationError::Io(e) => Some(e),
128        }
129    }
130}
131
132impl From<InstanceError> for MinimizationError {
133    fn from(err: InstanceError) -> Self {
134        MinimizationError::Instance(err)
135    }
136}
137
138impl From<CubeError> for MinimizationError {
139    fn from(err: CubeError) -> Self {
140        MinimizationError::Cube(err)
141    }
142}
143
144impl From<io::Error> for MinimizationError {
145    fn from(err: io::Error) -> Self {
146        MinimizationError::Io(err)
147    }
148}
149
150impl From<MinimizationError> for io::Error {
151    fn from(err: MinimizationError) -> Self {
152        match err {
153            // If it's already an IO error, return it directly
154            MinimizationError::Io(e) => e,
155            // Otherwise, wrap it as Other
156            MinimizationError::Instance(e) => io::Error::other(e),
157            MinimizationError::Cube(e) => io::Error::new(io::ErrorKind::InvalidData, e),
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use std::error::Error;
166
167    #[test]
168    fn test_instance_error_dimension_mismatch() {
169        let err = InstanceError::DimensionMismatch {
170            requested: (2, 1),
171            existing: (3, 2),
172        };
173        let msg = err.to_string();
174        assert!(msg.contains("Cannot create Espresso instance"));
175        assert!(msg.contains("(2, 1)"));
176        assert!(msg.contains("(3, 2)"));
177    }
178
179    #[test]
180    fn test_instance_error_config_mismatch() {
181        let err = InstanceError::ConfigMismatch {
182            requested: (2, 1),
183            existing: (2, 1),
184        };
185        let msg = err.to_string();
186        assert!(msg.contains("different configuration"));
187        assert!(msg.contains("(2, 1)"));
188    }
189
190    #[test]
191    fn test_cube_error_invalid_value() {
192        let err = CubeError::InvalidValue {
193            value: 5,
194            position: 2,
195        };
196        let msg = err.to_string();
197        assert!(msg.contains("Invalid cube value 5"));
198        assert!(msg.contains("position 2"));
199    }
200
201    #[test]
202    fn test_minimization_error_from_instance_error() {
203        let inst_err = InstanceError::DimensionMismatch {
204            requested: (2, 1),
205            existing: (3, 2),
206        };
207        let min_err: MinimizationError = inst_err.into();
208        assert!(matches!(min_err, MinimizationError::Instance(_)));
209        assert!(min_err.source().is_some());
210    }
211
212    #[test]
213    fn test_minimization_error_from_cube_error() {
214        let cube_err = CubeError::InvalidValue {
215            value: 5,
216            position: 2,
217        };
218        let min_err: MinimizationError = cube_err.into();
219        assert!(matches!(min_err, MinimizationError::Cube(_)));
220    }
221
222    #[test]
223    fn test_minimization_error_from_io_error() {
224        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
225        let min_err: MinimizationError = io_err.into();
226        assert!(matches!(min_err, MinimizationError::Io(_)));
227    }
228
229    #[test]
230    fn test_instance_error_to_io_error() {
231        let err = InstanceError::DimensionMismatch {
232            requested: (2, 1),
233            existing: (3, 2),
234        };
235        let io_err: io::Error = err.into();
236        assert_eq!(io_err.kind(), io::ErrorKind::Other);
237    }
238
239    #[test]
240    fn test_cube_error_to_io_error() {
241        let err = CubeError::InvalidValue {
242            value: 5,
243            position: 2,
244        };
245        let io_err: io::Error = err.into();
246        assert_eq!(io_err.kind(), io::ErrorKind::InvalidData);
247    }
248
249    #[test]
250    fn test_minimization_error_to_io_error_preserves_io_error() {
251        let original_io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
252        let min_err = MinimizationError::Io(original_io_err);
253        let io_err: io::Error = min_err.into();
254        assert_eq!(io_err.kind(), io::ErrorKind::NotFound);
255        assert_eq!(io_err.to_string(), "file not found");
256    }
257
258    #[test]
259    fn test_minimization_error_instance_to_io_error() {
260        let inst_err = InstanceError::DimensionMismatch {
261            requested: (2, 1),
262            existing: (3, 2),
263        };
264        let min_err = MinimizationError::Instance(inst_err);
265        let io_err: io::Error = min_err.into();
266        assert_eq!(io_err.kind(), io::ErrorKind::Other);
267    }
268
269    #[test]
270    fn test_minimization_error_cube_to_io_error() {
271        let cube_err = CubeError::InvalidValue {
272            value: 5,
273            position: 2,
274        };
275        let min_err = MinimizationError::Cube(cube_err);
276        let io_err: io::Error = min_err.into();
277        assert_eq!(io_err.kind(), io::ErrorKind::InvalidData);
278    }
279}