vpp_plugin/vppinfra/
error.rs

1//! VPP error type abstractions
2//!
3//! This module provides abstractions around VPP's error handling, including
4//! an error stack type that can represent a chain of errors similar to Rust's
5//! [`std::error::Error`].
6use core::str;
7use std::{
8    error::Error as StdError,
9    ffi::{CStr, CString},
10    fmt::{self, Display},
11    mem::ManuallyDrop,
12};
13
14use super::{Vec, VecRef};
15use crate::bindings::{_clib_error_return, clib_error_t, uword, CLIB_ERROR_ERRNO_VALID};
16
17/// A single VPP error
18///
19/// This type represents a single error in VPP, corresponding to a `clib_error_t`.
20#[derive(Debug, Copy, Clone)]
21#[repr(transparent)]
22pub struct Error(clib_error_t);
23
24impl fmt::Display for Error {
25    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
26        if self.0.what.is_null() {
27            return write!(fmt, "<none>");
28        }
29
30        if !self.0.where_.is_null() {
31            // SAFETY: where_ is a valid non-null C string
32            let where_c = unsafe { CStr::from_ptr(self.0.where_.cast()) };
33            write!(fmt, "{}: ", where_c.to_string_lossy())?;
34        }
35        // SAFETY: what is a valid non-null pointer to a VPP vector of u8
36        let what_v = unsafe { VecRef::from_raw(self.0.what) };
37        let what_str = str::from_utf8(what_v).unwrap_or("<invalid>");
38        write!(fmt, "{}", what_str)
39    }
40}
41
42/// A stack of VPP errors
43///
44/// This type represents a stack of VPP errors, similar to Rust's [`std::error::Error`]. It
45/// typically corresponds to a `clib_error_t *` returned by VPP APIs.
46#[derive(Debug)]
47pub struct ErrorStack(Vec<Error>);
48
49impl ErrorStack {
50    /// Creates an `ErrorStack` directly from a pointer
51    ///
52    /// # Safety
53    /// - The pointer must be valid and point to a VPP vector of type `clib_error_t`.
54    /// - The pointer must stay valid and the contents must not be mutated for the duration of the
55    ///   lifetime of the returned object.
56    pub unsafe fn from_raw(ptr: *mut clib_error_t) -> Self {
57        Self(Vec::from_raw(ptr.cast()))
58    }
59
60    /// Consumes the error stack and returns a raw pointer
61    ///
62    /// After calling this method, the caller is responsible for managing the memory of the
63    /// vector of errors.
64    pub fn into_raw(self) -> *mut clib_error_t {
65        let errors = ManuallyDrop::new(self);
66        errors.0.as_mut_ptr().cast()
67    }
68
69    /// Internal helper to create a new error stack
70    ///
71    /// # Panics
72    ///
73    /// Panics if `what` contains nul characters.
74    fn new_internal(
75        errors: Option<Self>,
76        what: String,
77        code: Option<crate::bindings::any>,
78    ) -> Self {
79        let errors_ptr = if let Some(errors) = errors {
80            errors.into_raw()
81        } else {
82            std::ptr::null_mut()
83        };
84        let what_c = CString::new(what).expect("message should not contain nul characters");
85        let flags = if code.is_some() {
86            CLIB_ERROR_ERRNO_VALID as uword
87        } else {
88            0
89        };
90        let code = code.unwrap_or_default();
91        // Note: we cannot populate where_ without using macros in callers due to need for 'static str with a
92        // nul-terminator
93        // SAFETY: errors_ptr is either null or a valid clib_error_t pointer, what_c is a valid C string
94        unsafe {
95            Self::from_raw(_clib_error_return(
96                errors_ptr,
97                code,
98                flags,
99                std::ptr::null_mut(),
100                what_c.as_ptr(),
101            ))
102        }
103    }
104
105    /// Creates a new error stack from a standard error
106    ///
107    /// The error message is taken from the Display version of the error.
108    ///
109    /// # Panics
110    ///
111    /// Panics if the error message contains nul characters.
112    pub fn new<E: StdError + Send + Sync + 'static>(e: E) -> Self {
113        Self::new_internal(None, e.to_string(), None)
114    }
115
116    /// Creates a new error stack from a message
117    ///
118    /// # Panics
119    ///
120    /// Panics if the message contains nul characters.
121    pub fn msg<M>(message: M) -> Self
122    where
123        M: Display + Send + Sync + 'static,
124    {
125        Self::new_internal(None, message.to_string(), None)
126    }
127
128    /// Adds context to the error stack
129    ///
130    /// # Panics
131    ///
132    /// Panics if the context message contains nul characters.
133    pub fn context<C>(self, context: C) -> Self
134    where
135        C: Display + Send + Sync + 'static,
136    {
137        Self::new_internal(Some(self), context.to_string(), None)
138    }
139
140    /// Returns the errors in the stack.
141    pub fn errors(&self) -> &[Error] {
142        self.0.as_slice()
143    }
144}
145
146impl Drop for ErrorStack {
147    fn drop(&mut self) {
148        for e in self.errors() {
149            if e.0.what.is_null() {
150                continue;
151            }
152            // Free the what vec
153            // SAFETY: what is a valid non-null pointer to a VPP vector of u8
154            let _ = unsafe { Vec::from_raw(e.0.what) };
155        }
156    }
157}
158
159impl Display for ErrorStack {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        let errs = self.errors();
162        if let Some(err) = errs.last() {
163            write!(f, "{}", err)?;
164            if errs.len() > 1 {
165                write!(f, "\n\nCaused by:")?;
166                for err in errs[..errs.len() - 1].iter().rev() {
167                    writeln!(f)?;
168                    write!(f, "    {}", err)?;
169                }
170            }
171        } else {
172            write!(f, "Empty VPP error")?;
173        }
174        Ok(())
175    }
176}
177
178impl StdError for ErrorStack {}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    use crate::vppinfra::clib_mem_init;
185
186    #[test]
187    fn basic() {
188        clib_mem_init();
189
190        let io_e = std::io::Error::new(std::io::ErrorKind::AlreadyExists, "Already exists");
191        let e = ErrorStack::new(io_e);
192        assert_eq!(e.errors().len(), 1);
193        assert_eq!(e.to_string(), "Already exists");
194
195        let e = ErrorStack::msg("Unknown interface");
196        assert_eq!(e.errors().len(), 1);
197        assert_eq!(e.to_string(), "Unknown interface");
198
199        let e = e.context("Failed to enable feature");
200        assert_eq!(e.errors().len(), 2);
201        assert_eq!(
202            e.to_string(),
203            "\
204Failed to enable feature
205
206Caused by:
207    Unknown interface"
208        );
209    }
210
211    #[test]
212    fn ptrs() {
213        clib_mem_init();
214
215        let e = ErrorStack::msg("Unknown interface");
216        assert_eq!(e.errors().len(), 1);
217        assert_eq!(e.to_string(), "Unknown interface");
218
219        let ptr = e.into_raw();
220        // SAFETY: ptr was obtained from ErrorStack::into_raw, so must be valid
221        let e = unsafe { ErrorStack::from_raw(ptr) };
222        assert_eq!(e.errors().len(), 1);
223        assert_eq!(e.to_string(), "Unknown interface");
224    }
225}