1use core::fmt;
28use nros_serdes::{DeserError, SerError};
29
30#[repr(i32)]
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum RclReturnCode {
37 Ok = 0,
39 Error = 1,
41 Timeout = 2,
43 Unsupported = 3,
45 BadAlloc = 10,
47 InvalidArgument = 11,
49
50 AlreadyInit = 100,
53 NotInit = 101,
55 TopicNameInvalid = 103,
57 ServiceNameInvalid = 104,
59 AlreadyShutdown = 106,
61
62 NodeInvalid = 200,
65 NodeInvalidName = 201,
67 NodeInvalidNamespace = 202,
69
70 PublisherInvalid = 300,
73
74 SubscriptionInvalid = 400,
77 SubscriptionTakeFailed = 401,
79
80 ClientInvalid = 500,
83 ClientTakeFailed = 501,
85
86 ServiceInvalid = 600,
89 ServiceTakeFailed = 601,
91
92 TimerInvalid = 800,
95 TimerCanceled = 801,
97
98 ActionGoalAccepted = 2100,
101 ActionGoalRejected = 2101,
103 ActionClientInvalid = 2102,
105 ActionClientTakeFailed = 2103,
107 ActionServerInvalid = 2200,
109 ActionServerTakeFailed = 2201,
111 ActionGoalHandleInvalid = 2300,
113}
114
115impl RclReturnCode {
116 pub const fn as_i32(self) -> i32 {
118 self as i32
119 }
120
121 pub fn try_from_i32(value: i32) -> Option<Self> {
123 match value {
124 0 => Some(Self::Ok),
125 1 => Some(Self::Error),
126 2 => Some(Self::Timeout),
127 3 => Some(Self::Unsupported),
128 10 => Some(Self::BadAlloc),
129 11 => Some(Self::InvalidArgument),
130 100 => Some(Self::AlreadyInit),
131 101 => Some(Self::NotInit),
132 103 => Some(Self::TopicNameInvalid),
133 104 => Some(Self::ServiceNameInvalid),
134 106 => Some(Self::AlreadyShutdown),
135 200 => Some(Self::NodeInvalid),
136 201 => Some(Self::NodeInvalidName),
137 202 => Some(Self::NodeInvalidNamespace),
138 300 => Some(Self::PublisherInvalid),
139 400 => Some(Self::SubscriptionInvalid),
140 401 => Some(Self::SubscriptionTakeFailed),
141 500 => Some(Self::ClientInvalid),
142 501 => Some(Self::ClientTakeFailed),
143 600 => Some(Self::ServiceInvalid),
144 601 => Some(Self::ServiceTakeFailed),
145 800 => Some(Self::TimerInvalid),
146 801 => Some(Self::TimerCanceled),
147 2100 => Some(Self::ActionGoalAccepted),
148 2101 => Some(Self::ActionGoalRejected),
149 2102 => Some(Self::ActionClientInvalid),
150 2103 => Some(Self::ActionClientTakeFailed),
151 2200 => Some(Self::ActionServerInvalid),
152 2201 => Some(Self::ActionServerTakeFailed),
153 2300 => Some(Self::ActionGoalHandleInvalid),
154 _ => None,
155 }
156 }
157}
158
159impl fmt::Display for RclReturnCode {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 let msg = match self {
162 Self::Ok => "Operation successful (RCL_RET_OK)",
163 Self::Error => "Unspecified error (RCL_RET_ERROR)",
164 Self::Timeout => "Timeout occurred (RCL_RET_TIMEOUT)",
165 Self::Unsupported => "Unsupported operation (RCL_RET_UNSUPPORTED)",
166 Self::BadAlloc => "Failed to allocate memory (RCL_RET_BAD_ALLOC)",
167 Self::InvalidArgument => "Invalid argument (RCL_RET_INVALID_ARGUMENT)",
168 Self::AlreadyInit => "Already initialized (RCL_RET_ALREADY_INIT)",
169 Self::NotInit => "Not initialized (RCL_RET_NOT_INIT)",
170 Self::TopicNameInvalid => "Invalid topic name (RCL_RET_TOPIC_NAME_INVALID)",
171 Self::ServiceNameInvalid => "Invalid service name (RCL_RET_SERVICE_NAME_INVALID)",
172 Self::AlreadyShutdown => "Already shutdown (RCL_RET_ALREADY_SHUTDOWN)",
173 Self::NodeInvalid => "Invalid node (RCL_RET_NODE_INVALID)",
174 Self::NodeInvalidName => "Invalid node name (RCL_RET_NODE_INVALID_NAME)",
175 Self::NodeInvalidNamespace => "Invalid node namespace (RCL_RET_NODE_INVALID_NAMESPACE)",
176 Self::PublisherInvalid => "Invalid publisher (RCL_RET_PUBLISHER_INVALID)",
177 Self::SubscriptionInvalid => "Invalid subscription (RCL_RET_SUBSCRIPTION_INVALID)",
178 Self::SubscriptionTakeFailed => {
179 "Failed to take message (RCL_RET_SUBSCRIPTION_TAKE_FAILED)"
180 }
181 Self::ClientInvalid => "Invalid client (RCL_RET_CLIENT_INVALID)",
182 Self::ClientTakeFailed => "Failed to take response (RCL_RET_CLIENT_TAKE_FAILED)",
183 Self::ServiceInvalid => "Invalid service (RCL_RET_SERVICE_INVALID)",
184 Self::ServiceTakeFailed => "Failed to take request (RCL_RET_SERVICE_TAKE_FAILED)",
185 Self::TimerInvalid => "Invalid timer (RCL_RET_TIMER_INVALID)",
186 Self::TimerCanceled => "Timer was canceled (RCL_RET_TIMER_CANCELED)",
187 Self::ActionGoalAccepted => "Action goal accepted (RCL_RET_ACTION_GOAL_ACCEPTED)",
188 Self::ActionGoalRejected => "Action goal rejected (RCL_RET_ACTION_GOAL_REJECTED)",
189 Self::ActionClientInvalid => "Invalid action client (RCL_RET_ACTION_CLIENT_INVALID)",
190 Self::ActionClientTakeFailed => {
191 "Action client take failed (RCL_RET_ACTION_CLIENT_TAKE_FAILED)"
192 }
193 Self::ActionServerInvalid => "Invalid action server (RCL_RET_ACTION_SERVER_INVALID)",
194 Self::ActionServerTakeFailed => {
195 "Action server take failed (RCL_RET_ACTION_SERVER_TAKE_FAILED)"
196 }
197 Self::ActionGoalHandleInvalid => {
198 "Invalid action goal handle (RCL_RET_ACTION_GOAL_HANDLE_INVALID)"
199 }
200 };
201 write!(f, "{}", msg)
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct NanoRosError {
236 code: RclReturnCode,
238 context: Option<ErrorContext>,
240 nested: Option<NestedError>,
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
246pub enum ErrorContext {
247 Topic(&'static str),
249 Service(&'static str),
251 Node(&'static str),
253 Action(&'static str),
255 Timer(usize),
257 Parameter(&'static str),
259 Custom(&'static str),
261}
262
263impl fmt::Display for ErrorContext {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 match self {
266 Self::Topic(name) => write!(f, "topic '{}'", name),
267 Self::Service(name) => write!(f, "service '{}'", name),
268 Self::Node(name) => write!(f, "node '{}'", name),
269 Self::Action(name) => write!(f, "action '{}'", name),
270 Self::Timer(id) => write!(f, "timer {}", id),
271 Self::Parameter(name) => write!(f, "parameter '{}'", name),
272 Self::Custom(msg) => write!(f, "{}", msg),
273 }
274 }
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279pub enum NestedError {
280 Ser(SerError),
282 Deser(DeserError),
284}
285
286impl NanoRosError {
287 pub const fn new(code: RclReturnCode) -> Self {
291 Self {
292 code,
293 context: None,
294 nested: None,
295 }
296 }
297
298 pub const fn with_context(code: RclReturnCode, context: ErrorContext) -> Self {
300 Self {
301 code,
302 context: Some(context),
303 nested: None,
304 }
305 }
306
307 pub const fn timeout() -> Self {
309 Self::new(RclReturnCode::Timeout)
310 }
311
312 pub const fn invalid_argument() -> Self {
314 Self::new(RclReturnCode::InvalidArgument)
315 }
316
317 pub const fn unsupported() -> Self {
319 Self::new(RclReturnCode::Unsupported)
320 }
321
322 pub const fn bad_alloc() -> Self {
324 Self::new(RclReturnCode::BadAlloc)
325 }
326
327 pub const fn error() -> Self {
329 Self::new(RclReturnCode::Error)
330 }
331
332 pub const fn node_invalid() -> Self {
336 Self::new(RclReturnCode::NodeInvalid)
337 }
338
339 pub const fn node_invalid_name(name: &'static str) -> Self {
341 Self::with_context(RclReturnCode::NodeInvalidName, ErrorContext::Node(name))
342 }
343
344 pub const fn node_invalid_namespace(namespace: &'static str) -> Self {
346 Self::with_context(
347 RclReturnCode::NodeInvalidNamespace,
348 ErrorContext::Custom(namespace),
349 )
350 }
351
352 pub const fn topic_name_invalid(topic: &'static str) -> Self {
356 Self::with_context(RclReturnCode::TopicNameInvalid, ErrorContext::Topic(topic))
357 }
358
359 pub const fn publisher_invalid() -> Self {
361 Self::new(RclReturnCode::PublisherInvalid)
362 }
363
364 pub const fn subscription_invalid() -> Self {
366 Self::new(RclReturnCode::SubscriptionInvalid)
367 }
368
369 pub const fn subscription_take_failed() -> Self {
371 Self::new(RclReturnCode::SubscriptionTakeFailed)
372 }
373
374 pub const fn service_name_invalid(service: &'static str) -> Self {
378 Self::with_context(
379 RclReturnCode::ServiceNameInvalid,
380 ErrorContext::Service(service),
381 )
382 }
383
384 pub const fn service_invalid() -> Self {
386 Self::new(RclReturnCode::ServiceInvalid)
387 }
388
389 pub const fn service_take_failed() -> Self {
391 Self::new(RclReturnCode::ServiceTakeFailed)
392 }
393
394 pub const fn client_invalid() -> Self {
396 Self::new(RclReturnCode::ClientInvalid)
397 }
398
399 pub const fn client_take_failed() -> Self {
401 Self::new(RclReturnCode::ClientTakeFailed)
402 }
403
404 pub const fn timer_invalid() -> Self {
408 Self::new(RclReturnCode::TimerInvalid)
409 }
410
411 pub const fn timer_canceled() -> Self {
413 Self::new(RclReturnCode::TimerCanceled)
414 }
415
416 pub const fn action_goal_rejected() -> Self {
420 Self::new(RclReturnCode::ActionGoalRejected)
421 }
422
423 pub const fn action_client_invalid() -> Self {
425 Self::new(RclReturnCode::ActionClientInvalid)
426 }
427
428 pub const fn action_server_invalid() -> Self {
430 Self::new(RclReturnCode::ActionServerInvalid)
431 }
432
433 pub const fn action_goal_handle_invalid() -> Self {
435 Self::new(RclReturnCode::ActionGoalHandleInvalid)
436 }
437
438 pub const fn already_init() -> Self {
442 Self::new(RclReturnCode::AlreadyInit)
443 }
444
445 pub const fn not_init() -> Self {
447 Self::new(RclReturnCode::NotInit)
448 }
449
450 pub const fn already_shutdown() -> Self {
452 Self::new(RclReturnCode::AlreadyShutdown)
453 }
454
455 pub fn serialization(err: SerError) -> Self {
459 Self {
460 code: RclReturnCode::Error,
461 context: None,
462 nested: Some(NestedError::Ser(err)),
463 }
464 }
465
466 pub fn deserialization(err: DeserError) -> Self {
468 Self {
469 code: RclReturnCode::Error,
470 context: None,
471 nested: Some(NestedError::Deser(err)),
472 }
473 }
474
475 pub const fn code(&self) -> RclReturnCode {
479 self.code
480 }
481
482 pub const fn context(&self) -> Option<&ErrorContext> {
484 self.context.as_ref()
485 }
486
487 pub const fn nested(&self) -> Option<&NestedError> {
489 self.nested.as_ref()
490 }
491
492 pub const fn is_timeout(&self) -> bool {
494 matches!(self.code, RclReturnCode::Timeout)
495 }
496
497 pub const fn is_take_failed(&self) -> bool {
500 matches!(
501 self.code,
502 RclReturnCode::SubscriptionTakeFailed
503 | RclReturnCode::ServiceTakeFailed
504 | RclReturnCode::ClientTakeFailed
505 | RclReturnCode::ActionServerTakeFailed
506 | RclReturnCode::ActionClientTakeFailed
507 )
508 }
509
510 pub const fn is_action_error(&self) -> bool {
512 matches!(
513 self.code,
514 RclReturnCode::ActionGoalAccepted
515 | RclReturnCode::ActionGoalRejected
516 | RclReturnCode::ActionClientInvalid
517 | RclReturnCode::ActionClientTakeFailed
518 | RclReturnCode::ActionServerInvalid
519 | RclReturnCode::ActionServerTakeFailed
520 | RclReturnCode::ActionGoalHandleInvalid
521 )
522 }
523
524 pub const fn is_serialization_error(&self) -> bool {
526 self.nested.is_some()
527 }
528}
529
530impl fmt::Display for NanoRosError {
531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532 write!(f, "{}", self.code)?;
534
535 if let Some(ctx) = &self.context {
537 write!(f, " ({})", ctx)?;
538 }
539
540 if let Some(nested) = &self.nested {
542 match nested {
543 NestedError::Ser(e) => write!(f, ": {}", e)?,
544 NestedError::Deser(e) => write!(f, ": {}", e)?,
545 }
546 }
547
548 Ok(())
549 }
550}
551
552#[cfg(feature = "std")]
554impl std::error::Error for NanoRosError {
555 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
556 None
559 }
560}
561
562#[cfg(feature = "std")]
563impl std::error::Error for RclReturnCode {}
564
565impl From<SerError> for NanoRosError {
568 fn from(e: SerError) -> Self {
569 Self::serialization(e)
570 }
571}
572
573impl From<DeserError> for NanoRosError {
574 fn from(e: DeserError) -> Self {
575 Self::deserialization(e)
576 }
577}
578
579impl From<RclReturnCode> for NanoRosError {
580 fn from(code: RclReturnCode) -> Self {
581 Self::new(code)
582 }
583}
584
585pub trait NanoRosErrorFilter {
592 type Output;
594
595 fn timeout_ok(self) -> Self::Output;
597
598 fn take_failed_ok(self) -> Self::Output;
600
601 fn ignore_non_errors(self) -> Self::Output
603 where
604 Self: Sized,
605 Self::Output: From<Self>,
606 {
607 self.timeout_ok()
609 }
610}
611
612impl NanoRosErrorFilter for Result<(), NanoRosError> {
613 type Output = Result<(), NanoRosError>;
614
615 fn timeout_ok(self) -> Self::Output {
616 match self {
617 Ok(()) => Ok(()),
618 Err(e) if e.is_timeout() => Ok(()),
619 Err(e) => Err(e),
620 }
621 }
622
623 fn take_failed_ok(self) -> Self::Output {
624 match self {
625 Ok(()) => Ok(()),
626 Err(e) if e.is_take_failed() => Ok(()),
627 Err(e) => Err(e),
628 }
629 }
630
631 fn ignore_non_errors(self) -> Self::Output {
632 self.timeout_ok().take_failed_ok()
633 }
634}
635
636pub trait TakeFailedAsNone {
641 type T;
643
644 fn take_failed_as_none(self) -> Result<Option<Self::T>, NanoRosError>;
646}
647
648impl<T> TakeFailedAsNone for Result<T, NanoRosError> {
649 type T = T;
650
651 fn take_failed_as_none(self) -> Result<Option<T>, NanoRosError> {
652 match self {
653 Ok(value) => Ok(Some(value)),
654 Err(e) if e.is_take_failed() => Ok(None),
655 Err(e) => Err(e),
656 }
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 extern crate alloc;
663 use alloc::format;
664
665 use super::*;
666
667 #[test]
668 fn test_rcl_return_code_display() {
669 assert!(format!("{}", RclReturnCode::Ok).contains("RCL_RET_OK"));
670 assert!(format!("{}", RclReturnCode::Timeout).contains("RCL_RET_TIMEOUT"));
671 assert!(
672 format!("{}", RclReturnCode::NodeInvalidName).contains("RCL_RET_NODE_INVALID_NAME")
673 );
674 }
675
676 #[test]
677 fn test_rcl_return_code_try_from() {
678 assert_eq!(RclReturnCode::try_from_i32(0), Some(RclReturnCode::Ok));
679 assert_eq!(RclReturnCode::try_from_i32(2), Some(RclReturnCode::Timeout));
680 assert_eq!(
681 RclReturnCode::try_from_i32(201),
682 Some(RclReturnCode::NodeInvalidName)
683 );
684 assert_eq!(RclReturnCode::try_from_i32(9999), None);
685 }
686
687 #[test]
688 fn test_nros_error_timeout() {
689 let err = NanoRosError::timeout();
690 assert!(err.is_timeout());
691 assert!(!err.is_take_failed());
692 assert_eq!(err.code(), RclReturnCode::Timeout);
693 }
694
695 #[test]
696 fn test_nros_error_take_failed() {
697 let err = NanoRosError::subscription_take_failed();
698 assert!(err.is_take_failed());
699 assert!(!err.is_timeout());
700
701 let err = NanoRosError::client_take_failed();
702 assert!(err.is_take_failed());
703
704 let err = NanoRosError::service_take_failed();
705 assert!(err.is_take_failed());
706 }
707
708 #[test]
709 fn test_nros_error_with_context() {
710 let err = NanoRosError::topic_name_invalid("/bad topic");
711 assert!(err.context().is_some());
712 if let Some(ErrorContext::Topic(name)) = err.context() {
713 assert_eq!(*name, "/bad topic");
714 } else {
715 panic!("Expected Topic context");
716 }
717 }
718
719 #[test]
720 fn test_nros_error_display() {
721 let err = NanoRosError::timeout();
722 let msg = format!("{}", err);
723 assert!(msg.contains("Timeout"));
724
725 let err = NanoRosError::topic_name_invalid("/test");
726 let msg = format!("{}", err);
727 assert!(msg.contains("topic"));
728 assert!(msg.contains("/test"));
729 }
730
731 #[test]
732 fn test_nros_error_from_ser_error() {
733 let err: NanoRosError = SerError::BufferTooSmall.into();
734 assert!(err.is_serialization_error());
735 assert!(matches!(err.nested(), Some(NestedError::Ser(_))));
736 }
737
738 #[test]
739 fn test_nros_error_from_deser_error() {
740 let err: NanoRosError = DeserError::UnexpectedEof.into();
741 assert!(err.is_serialization_error());
742 assert!(matches!(err.nested(), Some(NestedError::Deser(_))));
743 }
744
745 #[test]
746 fn test_error_filter_timeout_ok() {
747 let result: Result<(), NanoRosError> = Err(NanoRosError::timeout());
748 assert!(result.timeout_ok().is_ok());
749
750 let result: Result<(), NanoRosError> = Err(NanoRosError::error());
751 assert!(result.timeout_ok().is_err());
752
753 let result: Result<(), NanoRosError> = Ok(());
754 assert!(result.timeout_ok().is_ok());
755 }
756
757 #[test]
758 fn test_error_filter_take_failed_ok() {
759 let result: Result<(), NanoRosError> = Err(NanoRosError::subscription_take_failed());
760 assert!(result.take_failed_ok().is_ok());
761
762 let result: Result<(), NanoRosError> = Err(NanoRosError::error());
763 assert!(result.take_failed_ok().is_err());
764 }
765
766 #[test]
767 fn test_take_failed_as_none() {
768 let result: Result<i32, NanoRosError> = Err(NanoRosError::subscription_take_failed());
769 assert_eq!(result.take_failed_as_none().unwrap(), None);
770
771 let result: Result<i32, NanoRosError> = Ok(42);
772 assert_eq!(result.take_failed_as_none().unwrap(), Some(42));
773
774 let result: Result<i32, NanoRosError> = Err(NanoRosError::timeout());
775 assert!(result.take_failed_as_none().is_err());
776 }
777
778 #[test]
779 fn test_action_error_detection() {
780 let err = NanoRosError::action_goal_rejected();
781 assert!(err.is_action_error());
782
783 let err = NanoRosError::action_client_invalid();
784 assert!(err.is_action_error());
785
786 let err = NanoRosError::timeout();
787 assert!(!err.is_action_error());
788 }
789
790 #[test]
791 fn test_error_context_display() {
792 let ctx = ErrorContext::Topic("/my_topic");
793 assert!(format!("{}", ctx).contains("topic"));
794 assert!(format!("{}", ctx).contains("/my_topic"));
795
796 let ctx = ErrorContext::Service("/my_service");
797 assert!(format!("{}", ctx).contains("service"));
798
799 let ctx = ErrorContext::Timer(42);
800 assert!(format!("{}", ctx).contains("42"));
801 }
802}