Skip to main content

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_ERRNO_VALID, clib_error_t, uword};
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        // SAFETY: The safety requirements are documented in the function's safety comment.
58        unsafe { Self(Vec::from_raw(ptr.cast())) }
59    }
60
61    /// Consumes the error stack and returns a raw pointer
62    ///
63    /// After calling this method, the caller is responsible for managing the memory of the
64    /// vector of errors.
65    pub fn into_raw(self) -> *mut clib_error_t {
66        let errors = ManuallyDrop::new(self);
67        errors.0.as_mut_ptr().cast()
68    }
69
70    /// Internal helper to create a new error stack
71    ///
72    /// # Panics
73    ///
74    /// Panics if `what` contains nul characters.
75    fn new_internal(
76        errors: Option<Self>,
77        what: String,
78        code: Option<crate::bindings::any>,
79    ) -> Self {
80        let errors_ptr = if let Some(errors) = errors {
81            errors.into_raw()
82        } else {
83            std::ptr::null_mut()
84        };
85        let what_c = CString::new(what).expect("message should not contain nul characters");
86        let flags = if code.is_some() {
87            CLIB_ERROR_ERRNO_VALID as uword
88        } else {
89            0
90        };
91        let code = code.unwrap_or_default();
92        // Note: we cannot populate where_ without using macros in callers due to need for 'static str with a
93        // nul-terminator
94        // SAFETY: errors_ptr is either null or a valid clib_error_t pointer, what_c is a valid C string
95        unsafe {
96            Self::from_raw(_clib_error_return(
97                errors_ptr,
98                code,
99                flags,
100                std::ptr::null_mut(),
101                what_c.as_ptr(),
102            ))
103        }
104    }
105
106    /// Creates a new error stack from a standard error
107    ///
108    /// The error message is taken from the Display version of the error.
109    ///
110    /// # Panics
111    ///
112    /// Panics if the error message contains nul characters.
113    pub fn new<E: StdError + Send + Sync + 'static>(e: E) -> Self {
114        Self::new_internal(None, e.to_string(), None)
115    }
116
117    /// Creates a new error stack from a message
118    ///
119    /// # Panics
120    ///
121    /// Panics if the message contains nul characters.
122    pub fn msg<M>(message: M) -> Self
123    where
124        M: Display + Send + Sync + 'static,
125    {
126        Self::new_internal(None, message.to_string(), None)
127    }
128
129    /// Adds context to the error stack
130    ///
131    /// # Panics
132    ///
133    /// Panics if the context message contains nul characters.
134    pub fn context<C>(self, context: C) -> Self
135    where
136        C: Display + Send + Sync + 'static,
137    {
138        Self::new_internal(Some(self), context.to_string(), None)
139    }
140
141    /// Returns the errors in the stack.
142    pub fn errors(&self) -> &[Error] {
143        self.0.as_slice()
144    }
145}
146
147impl Drop for ErrorStack {
148    fn drop(&mut self) {
149        for e in self.errors() {
150            if e.0.what.is_null() {
151                continue;
152            }
153            // Free the what vec
154            // SAFETY: what is a valid non-null pointer to a VPP vector of u8
155            let _ = unsafe { Vec::from_raw(e.0.what) };
156        }
157    }
158}
159
160impl Display for ErrorStack {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        let errs = self.errors();
163        if let Some(err) = errs.last() {
164            write!(f, "{}", err)?;
165            if errs.len() > 1 {
166                write!(f, "\n\nCaused by:")?;
167                for err in errs[..errs.len() - 1].iter().rev() {
168                    writeln!(f)?;
169                    write!(f, "    {}", err)?;
170                }
171            }
172        } else {
173            write!(f, "Empty VPP error")?;
174        }
175        Ok(())
176    }
177}
178
179impl StdError for ErrorStack {}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    use crate::vppinfra::clib_mem_init;
186
187    #[test]
188    fn basic() {
189        clib_mem_init();
190
191        let io_e = std::io::Error::new(std::io::ErrorKind::AlreadyExists, "Already exists");
192        let e = ErrorStack::new(io_e);
193        assert_eq!(e.errors().len(), 1);
194        assert_eq!(e.to_string(), "Already exists");
195
196        let e = ErrorStack::msg("Unknown interface");
197        assert_eq!(e.errors().len(), 1);
198        assert_eq!(e.to_string(), "Unknown interface");
199
200        let e = e.context("Failed to enable feature");
201        assert_eq!(e.errors().len(), 2);
202        assert_eq!(
203            e.to_string(),
204            "\
205Failed to enable feature
206
207Caused by:
208    Unknown interface"
209        );
210    }
211
212    #[test]
213    fn ptrs() {
214        clib_mem_init();
215
216        let e = ErrorStack::msg("Unknown interface");
217        assert_eq!(e.errors().len(), 1);
218        assert_eq!(e.to_string(), "Unknown interface");
219
220        let ptr = e.into_raw();
221        // SAFETY: ptr was obtained from ErrorStack::into_raw, so must be valid
222        let e = unsafe { ErrorStack::from_raw(ptr) };
223        assert_eq!(e.errors().len(), 1);
224        assert_eq!(e.to_string(), "Unknown interface");
225    }
226}