Skip to main content

hdf5_metno/
error.rs

1use std::convert::Infallible;
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5use std::ops::Deref;
6use std::panic;
7use std::ptr::{self, addr_of_mut};
8
9use ndarray::ShapeError;
10
11#[cfg(not(feature = "1.10.0"))]
12use hdf5_sys::h5::hssize_t;
13use hdf5_sys::h5e::{
14    H5E_auto2_t, H5E_error2_t, H5Eget_current_stack, H5Eget_msg, H5Eprint2, H5Eset_auto2, H5Ewalk2,
15    H5E_DEFAULT, H5E_WALK_DOWNWARD,
16};
17
18use crate::internal_prelude::*;
19
20/// Silence errors emitted by `hdf5`
21///
22/// Safety: This version is not thread-safe and must be synchronised
23/// with other calls to `hdf5`
24pub(crate) unsafe fn silence_errors_no_sync(silence: bool) {
25    // Cast function with different argument types. This is safe because H5Eprint2 is
26    // documented to support this interface
27    let h5eprint: Option<unsafe extern "C" fn(hid_t, *mut libc::FILE) -> herr_t> =
28        Some(H5Eprint2 as _);
29    let h5eprint: H5E_auto2_t = std::mem::transmute(h5eprint);
30    H5Eset_auto2(H5E_DEFAULT, if silence { None } else { h5eprint }, ptr::null_mut());
31}
32
33/// Silence errors emitted by `hdf5`
34pub fn silence_errors(silence: bool) {
35    h5lock!(silence_errors_no_sync(silence));
36}
37
38/// A stack of error records from an HDF5 library call.
39#[repr(transparent)]
40#[derive(Clone)]
41pub struct ErrorStack(Handle);
42
43impl ObjectClass for ErrorStack {
44    const NAME: &'static str = "errorstack";
45    const VALID_TYPES: &'static [H5I_type_t] = &[H5I_ERROR_STACK];
46
47    fn from_handle(handle: Handle) -> Self {
48        Self(handle)
49    }
50
51    fn handle(&self) -> &Handle {
52        &self.0
53    }
54
55    // TODO: short_repr()
56}
57
58impl ErrorStack {
59    pub(crate) fn from_current() -> Result<Self> {
60        let stack_id = h5lock!(H5Eget_current_stack());
61        Handle::try_new(stack_id).map(Self)
62    }
63
64    /// Expands the error stack to a format which is easier to handle
65    // known HDF5 bug: H5Eget_msg() used in this function may corrupt
66    // the current stack, so we use self over &self
67    pub fn expand(self) -> Result<ExpandedErrorStack> {
68        struct CallbackData {
69            stack: ExpandedErrorStack,
70            err: Option<Error>,
71        }
72        unsafe extern "C" fn callback(
73            _: c_uint, err_desc: *const H5E_error2_t, data: *mut c_void,
74        ) -> herr_t {
75            panic::catch_unwind(|| unsafe {
76                let data = &mut *(data.cast::<CallbackData>());
77                if data.err.is_some() {
78                    return 0;
79                }
80                let closure = |e: H5E_error2_t| -> Result<ErrorFrame> {
81                    let (desc, func) = (string_from_cstr(e.desc), string_from_cstr(e.func_name));
82                    let major = get_h5_str(|m, s| H5Eget_msg(e.maj_num, ptr::null_mut(), m, s))?;
83                    let minor = get_h5_str(|m, s| H5Eget_msg(e.min_num, ptr::null_mut(), m, s))?;
84                    Ok(ErrorFrame::new(&desc, &func, &major, &minor))
85                };
86                match closure(*err_desc) {
87                    Ok(frame) => {
88                        data.stack.push(frame);
89                    }
90                    Err(err) => {
91                        data.err = Some(err);
92                    }
93                }
94                0
95            })
96            .unwrap_or(-1)
97        }
98
99        let mut data = CallbackData { stack: ExpandedErrorStack::new(), err: None };
100        let data_ptr: *mut c_void = addr_of_mut!(data).cast::<c_void>();
101
102        let stack_id = self.handle().id();
103        h5lock!({
104            H5Ewalk2(stack_id, H5E_WALK_DOWNWARD, Some(callback), data_ptr);
105        });
106
107        data.err.map_or(Ok(data.stack), Err)
108    }
109}
110
111/// An error record for an HDF5 library call.
112#[derive(Clone, Debug)]
113pub struct ErrorFrame {
114    desc: String,
115    func: String,
116    major: String,
117    minor: String,
118    description: String,
119}
120
121impl ErrorFrame {
122    pub(crate) fn new(desc: &str, func: &str, major: &str, minor: &str) -> Self {
123        Self {
124            desc: desc.into(),
125            func: func.into(),
126            major: major.into(),
127            minor: minor.into(),
128            description: format!("{func}(): {desc}"),
129        }
130    }
131
132    /// Returns the error description.
133    pub fn desc(&self) -> &str {
134        self.desc.as_ref()
135    }
136
137    /// Returns a message with the error description and the relevant function name.
138    pub fn description(&self) -> &str {
139        self.description.as_ref()
140    }
141
142    /// Returns a message with the error description and the relevant function name, file name,
143    /// and line number.
144    pub fn detail(&self) -> Option<String> {
145        Some(format!("Error in {}(): {} [{}: {}]", self.func, self.desc, self.major, self.minor))
146    }
147}
148
149/// A converted [`ErrorStack`] with methods to access [`ErrorFrame`] data.
150#[derive(Clone, Debug)]
151pub struct ExpandedErrorStack {
152    frames: Vec<ErrorFrame>,
153    description: Option<String>,
154}
155
156impl Deref for ExpandedErrorStack {
157    type Target = [ErrorFrame];
158
159    fn deref(&self) -> &Self::Target {
160        &self.frames
161    }
162}
163
164impl Default for ExpandedErrorStack {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170impl ExpandedErrorStack {
171    pub(crate) fn new() -> Self {
172        Self { frames: Vec::new(), description: None }
173    }
174
175    pub(crate) fn push(&mut self, frame: ErrorFrame) {
176        self.frames.push(frame);
177        if !self.is_empty() {
178            let top_desc = self.frames[0].description().to_owned();
179            if self.len() == 1 {
180                self.description = Some(top_desc);
181            } else {
182                self.description =
183                    Some(format!("{}: {}", top_desc, self.frames[self.len() - 1].desc()));
184            }
185        }
186    }
187
188    /// Returns the top [`ErrorFrame`] of the stack, or `None` if it is empty.
189    pub fn top(&self) -> Option<&ErrorFrame> {
190        self.first()
191    }
192
193    /// Returns the description of the error on top of the stack.
194    pub fn description(&self) -> &str {
195        match self.description {
196            None => "unknown library error",
197            Some(ref desc) => desc.as_ref(),
198        }
199    }
200
201    /// Returns a detailed message for the error on top of the stack, or `None` if it is empty.
202    pub fn detail(&self) -> Option<String> {
203        self.top().and_then(ErrorFrame::detail)
204    }
205}
206
207/// The error type for HDF5-related functions.
208#[derive(Clone)]
209pub enum Error {
210    /// An error occurred in the C API of the HDF5 library. Full error stack is captured.
211    HDF5(ErrorStack),
212    /// A user error occurred in the high-level Rust API (e.g., invalid user input).
213    Internal(String),
214}
215
216/// A type for results generated by HDF5-related functions where the `Err` type is
217/// set to `hdf5::Error`.
218pub type Result<T, E = Error> = ::std::result::Result<T, E>;
219
220impl Error {
221    /// Obtain the current error stack. The stack might be empty, which
222    /// will result in a valid error stack
223    pub fn query() -> Result<Self> {
224        if let Ok(stack) = ErrorStack::from_current() {
225            Ok(Self::HDF5(stack))
226        } else {
227            Err(Self::Internal("Could not get errorstack".to_owned()))
228        }
229    }
230}
231
232impl From<&str> for Error {
233    fn from(desc: &str) -> Self {
234        Self::Internal(desc.into())
235    }
236}
237
238impl From<String> for Error {
239    fn from(desc: String) -> Self {
240        Self::Internal(desc)
241    }
242}
243
244impl From<Infallible> for Error {
245    fn from(_: Infallible) -> Self {
246        unreachable!("Infallible error can never be constructed")
247    }
248}
249
250impl fmt::Debug for Error {
251    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252        match *self {
253            Self::Internal(ref desc) => f.write_str(desc),
254            Self::HDF5(ref stack) => match stack.clone().expand() {
255                Ok(stack) => f.write_str(stack.description()),
256                Err(_) => f.write_str("Could not get error stack"),
257            },
258        }
259    }
260}
261
262impl fmt::Display for Error {
263    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264        match *self {
265            Self::Internal(ref desc) => f.write_str(desc),
266            Self::HDF5(ref stack) => match stack.clone().expand() {
267                Ok(stack) => f.write_str(stack.description()),
268                Err(_) => f.write_str("Could not get error stack"),
269            },
270        }
271    }
272}
273
274impl StdError for Error {}
275
276impl From<ShapeError> for Error {
277    fn from(err: ShapeError) -> Self {
278        format!("shape error: {err}").into()
279    }
280}
281
282impl From<Error> for io::Error {
283    fn from(err: Error) -> Self {
284        Self::new(io::ErrorKind::Other, err)
285    }
286}
287
288pub fn h5check<T: H5ErrorCode>(value: T) -> Result<T> {
289    H5ErrorCode::h5check(value)
290}
291
292#[allow(unused)]
293pub fn is_err_code<T: H5ErrorCode>(value: T) -> bool {
294    H5ErrorCode::is_err_code(value)
295}
296
297pub trait H5ErrorCode: Copy {
298    fn is_err_code(value: Self) -> bool;
299
300    fn h5check(value: Self) -> Result<Self> {
301        if Self::is_err_code(value) {
302            Err(Error::query().unwrap_or_else(|e| e))
303        } else {
304            Ok(value)
305        }
306    }
307}
308
309impl H5ErrorCode for hsize_t {
310    fn is_err_code(value: Self) -> bool {
311        value == 0
312    }
313}
314
315impl H5ErrorCode for herr_t {
316    fn is_err_code(value: Self) -> bool {
317        value < 0
318    }
319}
320
321#[cfg(feature = "1.10.0")]
322impl H5ErrorCode for hid_t {
323    fn is_err_code(value: Self) -> bool {
324        value < 0
325    }
326}
327
328#[cfg(not(feature = "1.10.0"))]
329impl H5ErrorCode for hssize_t {
330    fn is_err_code(value: Self) -> bool {
331        value < 0
332    }
333}
334
335impl H5ErrorCode for libc::ssize_t {
336    fn is_err_code(value: Self) -> bool {
337        value < 0
338    }
339}
340
341#[cfg(test)]
342pub mod tests {
343    use hdf5_sys::h5p::{H5Pclose, H5Pcreate};
344
345    use crate::globals::H5P_ROOT;
346    use crate::internal_prelude::*;
347
348    use super::ExpandedErrorStack;
349
350    #[test]
351    pub fn test_error_stack() {
352        let stack = h5lock!({
353            let plist_id = H5Pcreate(*H5P_ROOT);
354            H5Pclose(plist_id);
355            Error::query()
356        })
357        .unwrap();
358        let stack = match stack {
359            Error::HDF5(stack) => stack,
360            Error::Internal(internal) => panic!("Expected hdf5 error, not {}", internal),
361        }
362        .expand()
363        .unwrap();
364        assert!(stack.is_empty());
365
366        let stack = h5lock!({
367            let plist_id = H5Pcreate(*H5P_ROOT);
368            H5Pclose(plist_id);
369            H5Pclose(plist_id);
370            Error::query()
371        })
372        .unwrap();
373        let stack = match stack {
374            Error::HDF5(stack) => stack,
375            Error::Internal(internal) => panic!("Expected hdf5 error, not {}", internal),
376        }
377        .expand()
378        .unwrap();
379        assert_eq!(stack.description(), "H5Pclose(): can't close: can't locate ID");
380        assert_eq!(
381            &stack.detail().unwrap(),
382            "Error in H5Pclose(): can't close [Property lists: Unable to free object]"
383        );
384
385        assert!(stack.len() >= 2 && stack.len() <= 4); // depending on HDF5 version
386        assert!(!stack.is_empty());
387
388        assert_eq!(stack[0].description(), "H5Pclose(): can't close");
389        assert_eq!(
390            &stack[0].detail().unwrap(),
391            "Error in H5Pclose(): can't close \
392             [Property lists: Unable to free object]"
393        );
394
395        #[cfg(not(feature = "1.14.0"))]
396        {
397            assert_eq!(stack[stack.len() - 1].description(), "H5I_dec_ref(): can't locate ID");
398            assert_eq!(
399                &stack[stack.len() - 1].detail().unwrap(),
400                "Error in H5I_dec_ref(): can't locate ID \
401             [Object atom: Unable to find atom information (already closed?)]"
402            );
403        }
404        #[cfg(feature = "1.14.0")]
405        {
406            assert_eq!(stack[stack.len() - 1].description(), "H5I__dec_ref(): can't locate ID");
407            assert_eq!(
408                &stack[stack.len() - 1].detail().unwrap(),
409                "Error in H5I__dec_ref(): can't locate ID \
410             [Object ID: Unable to find ID information (already closed?)]"
411            );
412        }
413
414        let empty_stack = ExpandedErrorStack::new();
415        assert!(empty_stack.is_empty());
416        assert_eq!(empty_stack.len(), 0);
417    }
418
419    #[test]
420    pub fn test_h5call() {
421        let result_no_error = h5call!({
422            let plist_id = H5Pcreate(*H5P_ROOT);
423            H5Pclose(plist_id)
424        });
425        assert!(result_no_error.is_ok());
426
427        let result_error = h5call!({
428            let plist_id = H5Pcreate(*H5P_ROOT);
429            H5Pclose(plist_id);
430            H5Pclose(plist_id)
431        });
432        assert!(result_error.is_err());
433    }
434
435    #[test]
436    pub fn test_h5try() {
437        fn f1() -> Result<herr_t> {
438            h5try!(H5Pcreate(*H5P_ROOT));
439            Ok(100)
440        }
441
442        assert_eq!(f1().unwrap(), 100);
443
444        fn f2() -> Result<herr_t> {
445            h5try!(H5Pcreate(123456));
446            Ok(100)
447        }
448
449        assert!(f2().is_err());
450    }
451}