Skip to main content

nros_core/
service.rs

1//! ROS 2 Service types
2//!
3//! Services provide synchronous request/response communication.
4//! A service client sends a request and waits for a response from a service server.
5
6use crate::{NanoRosError, types::RosService};
7
8/// Service server handle
9///
10/// Receives requests and sends responses for a ROS 2 service.
11pub struct ServiceServer<S: RosService> {
12    /// Service name (e.g., "/add_two_ints")
13    pub name: &'static str,
14    /// Marker for service type
15    _marker: core::marker::PhantomData<S>,
16}
17
18impl<S: RosService> ServiceServer<S> {
19    /// Create a new service server handle
20    pub fn new(name: &'static str) -> Self {
21        Self {
22            name,
23            _marker: core::marker::PhantomData,
24        }
25    }
26
27    /// Get the service name
28    pub fn name(&self) -> &str {
29        self.name
30    }
31
32    /// Get the service type name
33    pub fn service_type(&self) -> &'static str {
34        S::SERVICE_NAME
35    }
36
37    /// Get the service type hash
38    pub fn service_hash(&self) -> &'static str {
39        S::SERVICE_HASH
40    }
41}
42
43/// Service client handle
44///
45/// Sends requests and receives responses for a ROS 2 service.
46pub struct ServiceClient<S: RosService> {
47    /// Service name (e.g., "/add_two_ints")
48    pub name: &'static str,
49    /// Marker for service type
50    _marker: core::marker::PhantomData<S>,
51}
52
53impl<S: RosService> ServiceClient<S> {
54    /// Create a new service client handle
55    pub fn new(name: &'static str) -> Self {
56        Self {
57            name,
58            _marker: core::marker::PhantomData,
59        }
60    }
61
62    /// Get the service name
63    pub fn name(&self) -> &str {
64        self.name
65    }
66
67    /// Get the service type name
68    pub fn service_type(&self) -> &'static str {
69        S::SERVICE_NAME
70    }
71
72    /// Get the service type hash
73    pub fn service_hash(&self) -> &'static str {
74        S::SERVICE_HASH
75    }
76}
77
78/// Service request context
79///
80/// Passed to service handlers with request data and means to send a reply.
81pub struct ServiceRequest<'a, S: RosService> {
82    /// The deserialized request message
83    pub request: S::Request,
84    /// Raw request data (CDR encoded)
85    pub raw_data: &'a [u8],
86}
87
88/// Result type for service calls.
89///
90/// Wraps `Result<T, NanoRosError>` for convenience in service handler signatures.
91pub type ServiceResult<T> = Result<T, NanoRosError>;
92
93/// Synchronous service handler function pointer.
94///
95/// Takes a reference to the deserialized request and returns the reply.
96/// Used by [`ServiceServer`] for simple request-reply patterns.
97pub type ServiceCallback<S> = fn(&<S as RosService>::Request) -> <S as RosService>::Reply;
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    // Mock service for testing
104    struct MockService;
105
106    // Mock types for testing - fields exist for structural completeness but are not
107    // read since the mock serialize/deserialize implementations are no-ops
108    #[derive(Debug, Clone)]
109    struct MockRequest {
110        #[allow(dead_code)]
111        pub a: i32,
112        #[allow(dead_code)]
113        pub b: i32,
114    }
115
116    #[derive(Debug, Clone)]
117    struct MockReply {
118        #[allow(dead_code)]
119        pub sum: i32,
120    }
121
122    // Implement minimal traits for testing
123    impl nros_serdes::Serialize for MockRequest {
124        fn serialize(
125            &self,
126            _writer: &mut nros_serdes::CdrWriter,
127        ) -> Result<(), nros_serdes::SerError> {
128            Ok(())
129        }
130    }
131
132    impl nros_serdes::Deserialize for MockRequest {
133        fn deserialize(
134            _reader: &mut nros_serdes::CdrReader,
135        ) -> Result<Self, nros_serdes::DeserError> {
136            Ok(MockRequest { a: 0, b: 0 })
137        }
138    }
139
140    impl crate::RosMessage for MockRequest {
141        const TYPE_NAME: &'static str = "test_msgs::srv::dds_::AddTwoInts_Request_";
142        const TYPE_HASH: &'static str =
143            "0000000000000000000000000000000000000000000000000000000000000000";
144    }
145
146    impl nros_serdes::Serialize for MockReply {
147        fn serialize(
148            &self,
149            _writer: &mut nros_serdes::CdrWriter,
150        ) -> Result<(), nros_serdes::SerError> {
151            Ok(())
152        }
153    }
154
155    impl nros_serdes::Deserialize for MockReply {
156        fn deserialize(
157            _reader: &mut nros_serdes::CdrReader,
158        ) -> Result<Self, nros_serdes::DeserError> {
159            Ok(MockReply { sum: 0 })
160        }
161    }
162
163    impl crate::RosMessage for MockReply {
164        const TYPE_NAME: &'static str = "test_msgs::srv::dds_::AddTwoInts_Reply_";
165        const TYPE_HASH: &'static str =
166            "0000000000000000000000000000000000000000000000000000000000000000";
167    }
168
169    impl RosService for MockService {
170        type Request = MockRequest;
171        type Reply = MockReply;
172        const SERVICE_NAME: &'static str = "test_msgs::srv::dds_::AddTwoInts_";
173        const SERVICE_HASH: &'static str =
174            "0000000000000000000000000000000000000000000000000000000000000000";
175    }
176
177    #[test]
178    fn test_service_server_creation() {
179        let server = ServiceServer::<MockService>::new("/add_two_ints");
180        assert_eq!(server.name(), "/add_two_ints");
181        assert_eq!(server.service_type(), "test_msgs::srv::dds_::AddTwoInts_");
182    }
183
184    #[test]
185    fn test_service_client_creation() {
186        let client = ServiceClient::<MockService>::new("/add_two_ints");
187        assert_eq!(client.name(), "/add_two_ints");
188        assert_eq!(client.service_type(), "test_msgs::srv::dds_::AddTwoInts_");
189    }
190}