use std::{
error::Error,
ffi::{CStr, NulError},
fmt::{self, Display},
};
use crate::rcl_bindings::*;
#[derive(Debug, PartialEq, Eq)]
pub enum RclrsError {
RclError {
code: RclReturnCode,
msg: Option<RclErrorMsg>,
},
UnknownRclError {
code: i32,
msg: Option<RclErrorMsg>,
},
StringContainsNul {
s: String,
err: NulError,
},
AlreadyAddedToWaitSet,
}
impl RclrsError {
pub fn is_timeout(&self) -> bool {
matches!(
self,
RclrsError::RclError {
code: RclReturnCode::Timeout,
..
}
)
}
pub fn is_take_failed(&self) -> bool {
matches!(
self,
RclrsError::RclError {
code: RclReturnCode::SubscriptionTakeFailed
| RclReturnCode::ServiceTakeFailed
| RclReturnCode::ClientTakeFailed,
..
}
)
}
}
impl Display for RclrsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RclrsError::RclError { code, .. } => write!(f, "{}", code),
RclrsError::UnknownRclError { code, .. } => write!(f, "{}", code),
RclrsError::StringContainsNul { s, .. } => {
write!(f, "Could not convert string '{}' to CString", s)
}
RclrsError::AlreadyAddedToWaitSet => {
write!(
f,
"Could not add entity to wait set because it was already added to a wait set"
)
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct RclErrorMsg(String);
impl Display for RclErrorMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Error for RclErrorMsg {}
impl Error for RclrsError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
RclrsError::RclError { msg, .. } => msg.as_ref().map(|e| e as &dyn Error),
RclrsError::UnknownRclError { msg, .. } => msg.as_ref().map(|e| e as &dyn Error),
RclrsError::StringContainsNul { err, .. } => Some(err).map(|e| e as &dyn Error),
RclrsError::AlreadyAddedToWaitSet => None,
}
}
}
#[repr(i32)]
#[derive(Debug, PartialEq, Eq)]
pub enum RclReturnCode {
Ok = 0,
Error = 1,
Timeout = 2,
Unsupported = 3,
BadAlloc = 10,
InvalidArgument = 11,
AlreadyInit = 100,
NotInit = 101,
MismatchedRmwId = 102,
TopicNameInvalid = 103,
ServiceNameInvalid = 104,
UnknownSubstitution = 105,
AlreadyShutdown = 106,
NodeInvalid = 200,
NodeInvalidName = 201,
NodeInvalidNamespace = 202,
NodeNameNonexistent = 203,
PublisherInvalid = 300,
SubscriptionInvalid = 400,
SubscriptionTakeFailed = 401,
ClientInvalid = 500,
ClientTakeFailed = 501,
ServiceInvalid = 600,
ServiceTakeFailed = 601,
TimerInvalid = 800,
TimerCanceled = 801,
WaitSetInvalid = 900,
WaitSetEmpty = 901,
WaitSetFull = 902,
InvalidRemapRule = 1001,
WrongLexeme = 1002,
InvalidRosArgs = 1003,
InvalidParamRule = 1010,
InvalidLogLevelRule = 1020,
EventInvalid = 2000,
EventTakeFailed = 2001,
LifecycleStateRegistered = 3000,
LifecycleStateNotRegistered = 3001,
}
impl TryFrom<i32> for RclReturnCode {
type Error = i32;
fn try_from(value: i32) -> Result<Self, i32> {
let code = match value {
x if x == Self::Ok as i32 => Self::Ok,
x if x == Self::Error as i32 => Self::Error,
x if x == Self::Timeout as i32 => Self::Timeout,
x if x == Self::Unsupported as i32 => Self::Unsupported,
x if x == Self::BadAlloc as i32 => Self::BadAlloc,
x if x == Self::InvalidArgument as i32 => Self::InvalidArgument,
x if x == Self::AlreadyInit as i32 => Self::AlreadyInit,
x if x == Self::NotInit as i32 => Self::NotInit,
x if x == Self::MismatchedRmwId as i32 => Self::MismatchedRmwId,
x if x == Self::TopicNameInvalid as i32 => Self::TopicNameInvalid,
x if x == Self::ServiceNameInvalid as i32 => Self::ServiceNameInvalid,
x if x == Self::UnknownSubstitution as i32 => Self::UnknownSubstitution,
x if x == Self::AlreadyShutdown as i32 => Self::AlreadyShutdown,
x if x == Self::NodeInvalid as i32 => Self::NodeInvalid,
x if x == Self::NodeInvalidName as i32 => Self::NodeInvalidName,
x if x == Self::NodeInvalidNamespace as i32 => Self::NodeInvalidNamespace,
x if x == Self::NodeNameNonexistent as i32 => Self::NodeNameNonexistent,
x if x == Self::PublisherInvalid as i32 => Self::PublisherInvalid,
x if x == Self::SubscriptionInvalid as i32 => Self::SubscriptionInvalid,
x if x == Self::SubscriptionTakeFailed as i32 => Self::SubscriptionTakeFailed,
x if x == Self::ClientInvalid as i32 => Self::ClientInvalid,
x if x == Self::ClientTakeFailed as i32 => Self::ClientTakeFailed,
x if x == Self::ServiceInvalid as i32 => Self::ServiceInvalid,
x if x == Self::ServiceTakeFailed as i32 => Self::ServiceTakeFailed,
x if x == Self::TimerInvalid as i32 => Self::TimerInvalid,
x if x == Self::TimerCanceled as i32 => Self::TimerCanceled,
x if x == Self::WaitSetInvalid as i32 => Self::WaitSetInvalid,
x if x == Self::WaitSetEmpty as i32 => Self::WaitSetEmpty,
x if x == Self::WaitSetFull as i32 => Self::WaitSetFull,
x if x == Self::InvalidRemapRule as i32 => Self::InvalidRemapRule,
x if x == Self::WrongLexeme as i32 => Self::WrongLexeme,
x if x == Self::InvalidRosArgs as i32 => Self::InvalidRosArgs,
x if x == Self::InvalidParamRule as i32 => Self::InvalidParamRule,
x if x == Self::InvalidLogLevelRule as i32 => Self::InvalidLogLevelRule,
x if x == Self::EventInvalid as i32 => Self::EventInvalid,
x if x == Self::EventTakeFailed as i32 => Self::EventTakeFailed,
x if x == Self::LifecycleStateRegistered as i32 => Self::LifecycleStateRegistered,
x if x == Self::LifecycleStateNotRegistered as i32 => Self::LifecycleStateNotRegistered,
other => {
return Err(other);
}
};
Ok(code)
}
}
impl Display for RclReturnCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Ok => "Operation successful (RCL_RET_OK).",
Self::Error => "Unspecified error (RCL_RET_ERROR).",
Self::Timeout => "Timeout occurred (RCL_RET_TIMEOUT).",
Self::Unsupported => "Unsupported return code (RCL_RET_UNSUPPORTED).",
Self::BadAlloc => "Failed to allocate memory (RCL_RET_BAD_ALLOC).",
Self::InvalidArgument => "Argument to function was invalid (RCL_RET_INVALID_ARGUMENT).",
Self::AlreadyInit => "`rcl_init()` already called (RCL_RET_ALREADY_INIT).",
Self::NotInit => "`rcl_init() not yet called (RCL_RET_NOT_INIT).",
Self::MismatchedRmwId => "Mismatched rmw identifier (RCL_RET_MISMATCHED_RMW_ID).",
Self::TopicNameInvalid => {
"Topic name does not pass validation (RCL_RET_TOPIC_NAME_INVALID)."
}
Self::ServiceNameInvalid => {
"Service name does not pass validation (RCL_RET_SERVICE_NAME_INVALID)."
}
Self::UnknownSubstitution => {
"Topic name substitution is unknown (RCL_RET_UNKNOWN_SUBSTITUTION)."
}
Self::AlreadyShutdown => "`rcl_shutdown()` already called (RCL_RET_ALREADY_SHUTDOWN).",
Self::NodeInvalid => "Invalid `rcl_node_t` given (RCL_RET_NODE_INVALID).",
Self::NodeInvalidName => "Invalid node name (RCL_RET_NODE_INVALID_NAME).",
Self::NodeInvalidNamespace => {
"Invalid node namespace (RCL_RET_NODE_INVALID_NAMESPACE)."
}
Self::NodeNameNonexistent => {
"Failed to find node name (RCL_RET_NODE_NAME_NON_EXISTENT)."
}
Self::PublisherInvalid => {
"Invalid `rcl_publisher_t` given (RCL_RET_PUBLISHER_INVALID)."
}
Self::SubscriptionInvalid => {
"Invalid `rcl_subscription_t` given (RCL_RET_SUBSCRIPTION_INVALID)."
}
Self::SubscriptionTakeFailed => {
"Failed to take a message from the subscription (RCL_RET_SUBSCRIPTION_TAKE_FAILED)."
}
Self::ClientInvalid => "Invalid `rcl_client_t` given (RCL_RET_CLIENT_INVALID).",
Self::ClientTakeFailed => {
"Failed to take a response from the client (RCL_RET_CLIENT_TAKE_FAILED)."
}
Self::ServiceInvalid => "Invalid `rcl_service_t` given (RCL_RET_SERVICE_INVALID).",
Self::ServiceTakeFailed => {
"Failed to take a request from the service (RCL_RET_SERVICE_TAKE_FAILED)."
}
Self::TimerInvalid => "Invalid `rcl_timer_t` given (RCL_RET_TIMER_INVALID).",
Self::TimerCanceled => "Given timer was canceled (RCL_RET_TIMER_CANCELED).",
Self::WaitSetInvalid => "Invalid `rcl_wait_set_t` given (RCL_RET_WAIT_SET_INVALID).",
Self::WaitSetEmpty => "Given `rcl_wait_set_t` was empty (RCL_RET_WAIT_SET_EMPTY).",
Self::WaitSetFull => "Given `rcl_wait_set_t` was full (RCL_RET_WAIT_SET_FULL).",
Self::InvalidRemapRule => {
"Argument is not a valid remap rule (RCL_RET_INVALID_REMAP_RULE)."
}
Self::WrongLexeme => {
"Expected one type of lexeme, but got another (RCL_RET_WRONG_LEXEME)"
}
Self::InvalidRosArgs => {
"Found invalid ROS argument while parsing (RCL_RET_INVALID_ROS_ARGS)."
}
Self::InvalidParamRule => {
"Argument is not a valid parameter rule (RCL_RET_INVALID_PARAM_RULE)."
}
Self::InvalidLogLevelRule => {
"Argument is not a valid log level rule (RCL_RET_INVALID_LOG_LEVEL_RULE)."
}
Self::EventInvalid => "Invalid `rcl_event_t` given (RCL_RET_EVENT_INVALID).",
Self::EventTakeFailed => {
"Failed to take an event from the event handle (RCL_RET_EVENT_TAKE_FAILED)."
}
Self::LifecycleStateRegistered => {
"`rcl_lifecycle` state registered (RCL_RET_LIFECYCLE_STATE_REGISTERED)."
}
Self::LifecycleStateNotRegistered => {
"`rcl_lifecycle` state not registered (RCL_RET_LIFECYCLE_STATE_NOT_REGISTERED)."
}
};
write!(f, "{}", s)
}
}
impl Error for RclReturnCode {}
pub(crate) fn to_rclrs_result(ret: i32) -> Result<(), RclrsError> {
if ret == 0 {
return Ok(());
}
let mut msg = None;
let error_state_ptr = unsafe { rcutils_get_error_state() };
if !error_state_ptr.is_null() {
let msg_ptr = unsafe { (*error_state_ptr).message.as_ptr() };
let s = unsafe { CStr::from_ptr(msg_ptr) }
.to_string_lossy()
.into_owned();
msg = Some(RclErrorMsg(s));
}
unsafe { rcutils_reset_error() };
Err(match RclReturnCode::try_from(ret) {
Ok(code) => RclrsError::RclError { code, msg },
Err(code) => RclrsError::UnknownRclError { code, msg },
})
}
pub(crate) trait ToResult {
fn ok(&self) -> Result<(), RclrsError>;
}
impl ToResult for rcl_ret_t {
fn ok(&self) -> Result<(), RclrsError> {
to_rclrs_result(*self)
}
}
pub trait RclrsErrorFilter {
fn first_error(self) -> Result<(), RclrsError>;
fn timeout_ok(self) -> Self;
fn take_failed_ok(self) -> Self;
fn ignore_non_errors(self) -> Self
where
Self: Sized,
{
self.timeout_ok().take_failed_ok()
}
}
impl RclrsErrorFilter for Result<(), RclrsError> {
fn first_error(self) -> Result<(), RclrsError> {
self
}
fn timeout_ok(self) -> Result<(), RclrsError> {
match self {
Ok(()) => Ok(()),
Err(err) => {
if err.is_timeout() {
Ok(())
} else {
Err(err)
}
}
}
}
fn take_failed_ok(self) -> Result<(), RclrsError> {
match self {
Err(err) => {
if err.is_take_failed() {
Ok(())
} else {
Err(err)
}
}
other => other,
}
}
}
impl RclrsErrorFilter for Vec<RclrsError> {
fn first_error(mut self) -> Result<(), RclrsError> {
if self.is_empty() {
return Ok(());
}
Err(self.remove(0))
}
fn timeout_ok(mut self) -> Self {
self.retain(|err| !err.is_timeout());
self
}
fn take_failed_ok(mut self) -> Self {
self.retain(|err| !err.is_take_failed());
self
}
}