do_not_use_testing_rclrs/
error.rs

1use std::error::Error;
2use std::ffi::{CStr, NulError};
3use std::fmt::{self, Display};
4
5use crate::rcl_bindings::*;
6
7/// The main error type.
8#[derive(Debug, PartialEq, Eq)]
9pub enum RclrsError {
10    /// An error originating in the `rcl` layer.
11    RclError {
12        /// The error code.
13        code: RclReturnCode,
14        /// The error message set in the `rcl` layer or below.
15        msg: Option<RclErrorMsg>,
16    },
17    /// An unknown error originating in the `rcl` layer.
18    UnknownRclError {
19        /// The error code.
20        code: i32,
21        /// The error message set in the `rcl` layer or below.
22        msg: Option<RclErrorMsg>,
23    },
24    /// A string provided to `rclrs` could not be converted into a `CString`.
25    StringContainsNul {
26        /// The string that contains a nul byte.
27        s: String,
28        /// The error indicating the position of the nul byte.
29        err: NulError,
30    },
31    /// It was attempted to add a waitable to a wait set twice.
32    AlreadyAddedToWaitSet,
33}
34
35impl Display for RclrsError {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        match self {
38            RclrsError::RclError { code, .. } => write!(f, "{}", code),
39            RclrsError::UnknownRclError { code, .. } => write!(f, "{}", code),
40            RclrsError::StringContainsNul { s, .. } => {
41                write!(f, "Could not convert string '{}' to CString", s)
42            }
43            RclrsError::AlreadyAddedToWaitSet => {
44                write!(
45                    f,
46                    "Could not add entity to wait set because it was already added to a wait set"
47                )
48            }
49        }
50    }
51}
52
53/// Struct encapsulating an error message from the rcl layer or below.
54///
55/// This struct is intended to be returned by the `source` method in the implementation of the
56/// standard [`Error`][1] trait for [`RclrsError`][2].
57/// By doing this, the error message is printed as a separate item in the error chain.
58/// This avoids an unreadable, inconsistent formatting of error codes and messages that would
59/// likely be produced by a combined display of `RclReturnCode` and message.
60///
61/// [1]: std::error::Error
62/// [2]: crate::RclrsError
63#[derive(Debug, PartialEq, Eq)]
64pub struct RclErrorMsg(String);
65
66impl Display for RclErrorMsg {
67    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        write!(f, "{}", self.0)
69    }
70}
71
72impl Error for RclErrorMsg {}
73
74impl Error for RclrsError {
75    fn source(&self) -> Option<&(dyn Error + 'static)> {
76        match self {
77            RclrsError::RclError { msg, .. } => msg.as_ref().map(|e| e as &dyn Error),
78            RclrsError::UnknownRclError { msg, .. } => msg.as_ref().map(|e| e as &dyn Error),
79            RclrsError::StringContainsNul { err, .. } => Some(err).map(|e| e as &dyn Error),
80            RclrsError::AlreadyAddedToWaitSet => None,
81        }
82    }
83}
84
85/// Return codes of `rcl` functions.
86///
87/// This type corresponds to `rcl_ret_t`.
88/// Most of these return codes should never occur in an `rclrs` application,
89/// since they are returned when `rcl` functions are used wrongly..
90#[repr(i32)]
91#[derive(Debug, PartialEq, Eq)]
92pub enum RclReturnCode {
93    /// Success
94    Ok = 0,
95    /// Unspecified error
96    Error = 1,
97    /// Timeout occurred
98    Timeout = 2,
99    /// Unsupported return code
100    Unsupported = 3,
101    /// Failed to allocate memory
102    BadAlloc = 10,
103    /// Argument to function was invalid
104    InvalidArgument = 11,
105    // ====== 1xx: `rcl`-specific errors ======
106    /// `rcl_init()` already called
107    AlreadyInit = 100,
108    /// `rcl_init()` not yet called
109    NotInit = 101,
110    /// Mismatched rmw identifier
111    MismatchedRmwId = 102,
112    /// Topic name does not pass validation
113    TopicNameInvalid = 103,
114    /// Service name (same as topic name) does not pass validation
115    ServiceNameInvalid = 104,
116    /// Topic name substitution is unknown
117    UnknownSubstitution = 105,
118    /// `rcl_shutdown()` already called
119    AlreadyShutdown = 106,
120    // ====== 2xx: node-specific errors ======
121    /// Invalid `rcl_node_t` given
122    NodeInvalid = 200,
123    /// Invalid node name
124    NodeInvalidName = 201,
125    /// Invalid node namespace
126    NodeInvalidNamespace = 202,
127    /// Failed to find node name
128    NodeNameNonexistent = 203,
129    // ====== 3XX: publisher-specific errors ======
130    /// Invalid `rcl_publisher_t` given
131    PublisherInvalid = 300,
132    // ====== 4XX: subscription-specific errors ======
133    /// Invalid `rcl_subscription_t` given
134    SubscriptionInvalid = 400,
135    /// Failed to take a message from the subscription
136    SubscriptionTakeFailed = 401,
137    // ====== 5XX: client-specific errors ======
138    /// Invalid `rcl_client_t` given
139    ClientInvalid = 500,
140    /// Failed to take a response from the client
141    ClientTakeFailed = 501,
142    // ====== 6XX: service-specific errors ======
143    /// Invalid `rcl_service_t` given
144    ServiceInvalid = 600,
145    /// Failed to take a request from the service
146    ServiceTakeFailed = 601,
147    // ====== 8XX: timer-specific errors ======
148    /// Invalid `rcl_timer_t` given
149    TimerInvalid = 800,
150    /// Given timer was canceled
151    TimerCanceled = 801,
152    // ====== 9XX: wait set-specific errors ======
153    /// Invalid `rcl_wait_set_t` given
154    WaitSetInvalid = 900,
155    /// Given `rcl_wait_set_t` is empty
156    WaitSetEmpty = 901,
157    /// Given `rcl_wait_set_t` is full
158    WaitSetFull = 902,
159    // ====== 10XX: argument parsing errors ======
160    /// Argument is not a valid remap rule
161    InvalidRemapRule = 1001,
162    /// Expected one type of lexeme but got another
163    WrongLexeme = 1002,
164    /// Found invalid ROS argument while parsing
165    InvalidRosArgs = 1003,
166    /// Argument is not a valid parameter rule
167    InvalidParamRule = 1010,
168    /// Argument is not a valid log level rule
169    InvalidLogLevelRule = 1020,
170    // ====== 20XX: event-specific errors ======
171    /// Invalid `rcl_event_t` given
172    EventInvalid = 2000,
173    /// Failed to take an event from the event handle
174    EventTakeFailed = 2001,
175    // ====== 30XX: lifecycle-specific errors ======
176    /// `rcl_lifecycle` state registered
177    LifecycleStateRegistered = 3000,
178    /// `rcl_lifecycle` state not registered
179    LifecycleStateNotRegistered = 3001,
180}
181
182impl TryFrom<i32> for RclReturnCode {
183    type Error = i32;
184
185    fn try_from(value: i32) -> Result<Self, i32> {
186        let code = match value {
187            x if x == Self::Ok as i32 => Self::Ok,
188            x if x == Self::Error as i32 => Self::Error,
189            x if x == Self::Timeout as i32 => Self::Timeout,
190            x if x == Self::Unsupported as i32 => Self::Unsupported,
191            x if x == Self::BadAlloc as i32 => Self::BadAlloc,
192            x if x == Self::InvalidArgument as i32 => Self::InvalidArgument,
193            x if x == Self::AlreadyInit as i32 => Self::AlreadyInit,
194            x if x == Self::NotInit as i32 => Self::NotInit,
195            x if x == Self::MismatchedRmwId as i32 => Self::MismatchedRmwId,
196            x if x == Self::TopicNameInvalid as i32 => Self::TopicNameInvalid,
197            x if x == Self::ServiceNameInvalid as i32 => Self::ServiceNameInvalid,
198            x if x == Self::UnknownSubstitution as i32 => Self::UnknownSubstitution,
199            x if x == Self::AlreadyShutdown as i32 => Self::AlreadyShutdown,
200            x if x == Self::NodeInvalid as i32 => Self::NodeInvalid,
201            x if x == Self::NodeInvalidName as i32 => Self::NodeInvalidName,
202            x if x == Self::NodeInvalidNamespace as i32 => Self::NodeInvalidNamespace,
203            x if x == Self::NodeNameNonexistent as i32 => Self::NodeNameNonexistent,
204            x if x == Self::PublisherInvalid as i32 => Self::PublisherInvalid,
205            x if x == Self::SubscriptionInvalid as i32 => Self::SubscriptionInvalid,
206            x if x == Self::SubscriptionTakeFailed as i32 => Self::SubscriptionTakeFailed,
207            x if x == Self::ClientInvalid as i32 => Self::ClientInvalid,
208            x if x == Self::ClientTakeFailed as i32 => Self::ClientTakeFailed,
209            x if x == Self::ServiceInvalid as i32 => Self::ServiceInvalid,
210            x if x == Self::ServiceTakeFailed as i32 => Self::ServiceTakeFailed,
211            x if x == Self::TimerInvalid as i32 => Self::TimerInvalid,
212            x if x == Self::TimerCanceled as i32 => Self::TimerCanceled,
213            x if x == Self::WaitSetInvalid as i32 => Self::WaitSetInvalid,
214            x if x == Self::WaitSetEmpty as i32 => Self::WaitSetEmpty,
215            x if x == Self::WaitSetFull as i32 => Self::WaitSetFull,
216            x if x == Self::InvalidRemapRule as i32 => Self::InvalidRemapRule,
217            x if x == Self::WrongLexeme as i32 => Self::WrongLexeme,
218            x if x == Self::InvalidRosArgs as i32 => Self::InvalidRosArgs,
219            x if x == Self::InvalidParamRule as i32 => Self::InvalidParamRule,
220            x if x == Self::InvalidLogLevelRule as i32 => Self::InvalidLogLevelRule,
221            x if x == Self::EventInvalid as i32 => Self::EventInvalid,
222            x if x == Self::EventTakeFailed as i32 => Self::EventTakeFailed,
223            x if x == Self::LifecycleStateRegistered as i32 => Self::LifecycleStateRegistered,
224            x if x == Self::LifecycleStateNotRegistered as i32 => Self::LifecycleStateNotRegistered,
225            other => {
226                return Err(other);
227            }
228        };
229        Ok(code)
230    }
231}
232
233impl Display for RclReturnCode {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        let s = match self {
236            Self::Ok => "Operation successful (RCL_RET_OK).",
237            Self::Error => "Unspecified error (RCL_RET_ERROR).",
238            Self::Timeout => "Timeout occurred (RCL_RET_TIMEOUT).",
239            Self::Unsupported => "Unsupported return code (RCL_RET_UNSUPPORTED).",
240            Self::BadAlloc => "Failed to allocate memory (RCL_RET_BAD_ALLOC).",
241            Self::InvalidArgument => "Argument to function was invalid (RCL_RET_INVALID_ARGUMENT).",
242            Self::AlreadyInit => "`rcl_init()` already called (RCL_RET_ALREADY_INIT).",
243            Self::NotInit => "`rcl_init() not yet called (RCL_RET_NOT_INIT).",
244            Self::MismatchedRmwId => "Mismatched rmw identifier (RCL_RET_MISMATCHED_RMW_ID).",
245            Self::TopicNameInvalid => {
246                "Topic name does not pass validation (RCL_RET_TOPIC_NAME_INVALID)."
247            }
248            Self::ServiceNameInvalid => {
249                "Service name does not pass validation (RCL_RET_SERVICE_NAME_INVALID)."
250            }
251            Self::UnknownSubstitution => {
252                "Topic name substitution is unknown (RCL_RET_UNKNOWN_SUBSTITUTION)."
253            }
254            Self::AlreadyShutdown => "`rcl_shutdown()` already called (RCL_RET_ALREADY_SHUTDOWN).",
255            Self::NodeInvalid => "Invalid `rcl_node_t` given (RCL_RET_NODE_INVALID).",
256            Self::NodeInvalidName => "Invalid node name (RCL_RET_NODE_INVALID_NAME).",
257            Self::NodeInvalidNamespace => {
258                "Invalid node namespace (RCL_RET_NODE_INVALID_NAMESPACE)."
259            }
260            Self::NodeNameNonexistent => {
261                "Failed to find node name (RCL_RET_NODE_NAME_NON_EXISTENT)."
262            }
263            Self::PublisherInvalid => {
264                "Invalid `rcl_publisher_t` given (RCL_RET_PUBLISHER_INVALID)."
265            }
266            Self::SubscriptionInvalid => {
267                "Invalid `rcl_subscription_t` given (RCL_RET_SUBSCRIPTION_INVALID)."
268            }
269            Self::SubscriptionTakeFailed => {
270                "Failed to take a message from the subscription (RCL_RET_SUBSCRIPTION_TAKE_FAILED)."
271            }
272            Self::ClientInvalid => "Invalid `rcl_client_t` given (RCL_RET_CLIENT_INVALID).",
273            Self::ClientTakeFailed => {
274                "Failed to take a response from the client (RCL_RET_CLIENT_TAKE_FAILED)."
275            }
276            Self::ServiceInvalid => "Invalid `rcl_service_t` given (RCL_RET_SERVICE_INVALID).",
277            Self::ServiceTakeFailed => {
278                "Failed to take a request from the service (RCL_RET_SERVICE_TAKE_FAILED)."
279            }
280            Self::TimerInvalid => "Invalid `rcl_timer_t` given (RCL_RET_TIMER_INVALID).",
281            Self::TimerCanceled => "Given timer was canceled (RCL_RET_TIMER_CANCELED).",
282            Self::WaitSetInvalid => "Invalid `rcl_wait_set_t` given (RCL_RET_WAIT_SET_INVALID).",
283            Self::WaitSetEmpty => "Given `rcl_wait_set_t` was empty (RCL_RET_WAIT_SET_EMPTY).",
284            Self::WaitSetFull => "Given `rcl_wait_set_t` was full (RCL_RET_WAIT_SET_FULL).",
285            Self::InvalidRemapRule => {
286                "Argument is not a valid remap rule (RCL_RET_INVALID_REMAP_RULE)."
287            }
288            Self::WrongLexeme => {
289                "Expected one type of lexeme, but got another (RCL_RET_WRONG_LEXEME)"
290            }
291            Self::InvalidRosArgs => {
292                "Found invalid ROS argument while parsing (RCL_RET_INVALID_ROS_ARGS)."
293            }
294            Self::InvalidParamRule => {
295                "Argument is not a valid parameter rule (RCL_RET_INVALID_PARAM_RULE)."
296            }
297            Self::InvalidLogLevelRule => {
298                "Argument is not a valid log level rule (RCL_RET_INVALID_LOG_LEVEL_RULE)."
299            }
300            Self::EventInvalid => "Invalid `rcl_event_t` given (RCL_RET_EVENT_INVALID).",
301            Self::EventTakeFailed => {
302                "Failed to take an event from the event handle (RCL_RET_EVENT_TAKE_FAILED)."
303            }
304            Self::LifecycleStateRegistered => {
305                "`rcl_lifecycle` state registered (RCL_RET_LIFECYCLE_STATE_REGISTERED)."
306            }
307            Self::LifecycleStateNotRegistered => {
308                "`rcl_lifecycle` state not registered (RCL_RET_LIFECYCLE_STATE_NOT_REGISTERED)."
309            }
310        };
311        write!(f, "{}", s)
312    }
313}
314
315impl Error for RclReturnCode {}
316
317pub(crate) fn to_rclrs_result(ret: i32) -> Result<(), RclrsError> {
318    if ret == 0 {
319        return Ok(());
320    }
321    let mut msg = None;
322    // SAFETY: No preconditions for this function.
323    let error_state_ptr = unsafe { rcutils_get_error_state() };
324    // The returned pointer may be null if the error state has not been set.
325    if !error_state_ptr.is_null() {
326        // SAFETY: Dereferencing the pointer is safe since it was checked to be non-null.
327        let msg_ptr = unsafe { (*error_state_ptr).message.as_ptr() };
328        // SAFETY: Pointer has been created from an array, no lifetime issues due to
329        // immediate conversion to owned string.
330        let s = unsafe { CStr::from_ptr(msg_ptr) }
331            .to_string_lossy()
332            .into_owned();
333        msg = Some(RclErrorMsg(s));
334    }
335    // SAFETY: No preconditions for this function.
336    unsafe { rcutils_reset_error() };
337    // Finally, try to parse it into a return code.
338    Err(match RclReturnCode::try_from(ret) {
339        Ok(code) => RclrsError::RclError { code, msg },
340        Err(code) => RclrsError::UnknownRclError { code, msg },
341    })
342}
343
344pub(crate) trait ToResult {
345    fn ok(&self) -> Result<(), RclrsError>;
346}
347
348impl ToResult for rcl_ret_t {
349    fn ok(&self) -> Result<(), RclrsError> {
350        to_rclrs_result(*self)
351    }
352}