1use std::sync::Arc;
17
18use async_trait::async_trait;
19
20use crate::{
21 communication::{
22 apply_common_options, build_message, CallOptions, NotificationError, Notifier,
23 RegistrationError, UPayload,
24 },
25 LocalUriProvider, UListener, UMessageBuilder, UTransport, UUri,
26};
27
28pub struct SimpleNotifier<T, P> {
31 transport: Arc<T>,
32 uri_provider: Arc<P>,
33}
34
35impl<T, P> core::fmt::Debug for SimpleNotifier<T, P> {
36 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37 f.debug_struct("SimpleNotifier").finish_non_exhaustive()
38 }
39}
40
41impl<T: UTransport, P: LocalUriProvider> SimpleNotifier<T, P> {
42 pub fn new(transport: Arc<T>, uri_provider: Arc<P>) -> Self {
49 SimpleNotifier {
50 transport,
51 uri_provider,
52 }
53 }
54}
55
56#[async_trait]
57impl<T: UTransport, P: LocalUriProvider> Notifier for SimpleNotifier<T, P> {
58 async fn notify(
59 &self,
60 resource_id: u16,
61 destination: &UUri,
62 call_options: CallOptions,
63 payload: Option<UPayload>,
64 ) -> Result<(), NotificationError> {
65 let mut builder = UMessageBuilder::notification(
66 self.uri_provider.get_resource_uri(resource_id),
67 destination.to_owned(),
68 );
69 apply_common_options(call_options, &mut builder);
70 let msg = build_message(&mut builder, payload)
71 .map_err(|e| NotificationError::InvalidArgument(e.to_string()))?;
72 self.transport
73 .send(msg)
74 .await
75 .map_err(Box::from)
76 .map_err(NotificationError::NotifyError)
77 }
78
79 async fn start_listening(
80 &self,
81 topic: &UUri,
82 listener: Arc<dyn UListener>,
83 ) -> Result<(), RegistrationError> {
84 topic
85 .verify_no_wildcards()
86 .map_err(|e| RegistrationError::InvalidFilter(e.to_string()))?;
87 self.transport
88 .register_listener(topic, Some(&self.uri_provider.get_source_uri()), listener)
89 .await
90 .map_err(RegistrationError::from)
91 }
92
93 async fn stop_listening(
94 &self,
95 topic: &UUri,
96 listener: Arc<dyn UListener>,
97 ) -> Result<(), RegistrationError> {
98 topic
99 .verify_no_wildcards()
100 .map_err(|e| RegistrationError::InvalidFilter(e.to_string()))?;
101 self.transport
102 .unregister_listener(topic, Some(&self.uri_provider.get_source_uri()), listener)
103 .await
104 .map_err(RegistrationError::from)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110
111 use super::*;
114
115 use crate::{
116 utransport::{MockTransport, MockUListener},
117 StaticUriProvider, UCode, UPriority, UStatus, UUri, UUID,
118 };
119
120 fn new_uri_provider() -> Arc<StaticUriProvider> {
121 Arc::new(StaticUriProvider::new("", 0x0005, 0x02).expect("failed to create URI Provider"))
122 }
123
124 #[tokio::test]
125 async fn test_start_stop_listening_rejects_wildcard_topic() {
126 let mut transport = MockTransport::new();
127 transport.expect_do_register_listener().never();
128 let uri_provider = new_uri_provider();
129 let notifier = SimpleNotifier::new(Arc::new(transport), uri_provider);
130
131 let invalid_topic = UUri::try_from("up://my-vin/A15B/1/FFFF").unwrap();
132 let mut listener = MockUListener::new();
133 listener.expect_on_receive().never();
134 let wrapped_listener = Arc::new(listener);
135
136 let result = notifier
137 .start_listening(&invalid_topic, wrapped_listener.clone())
138 .await;
139 assert!(result.is_err_and(|e| matches!(e, RegistrationError::InvalidFilter(_))));
140
141 let result = notifier
142 .stop_listening(&invalid_topic, wrapped_listener)
143 .await;
144 assert!(result.is_err_and(|e| matches!(e, RegistrationError::InvalidFilter(_))));
145 }
146
147 #[tokio::test]
148 async fn test_start_listening_succeeds() {
149 let uri_provider = new_uri_provider();
150 let topic = UUri::try_from("up://my-vin/A15B/1/B10F").unwrap();
151 let expected_source_filter = topic.clone();
152 let expected_sink_filter = uri_provider.get_source_uri();
153 let mut transport = MockTransport::new();
154 transport
155 .expect_do_register_listener()
156 .once()
157 .withf(move |source_filter, sink_filter, _listener| {
158 source_filter == &expected_source_filter
159 && *sink_filter == Some(&expected_sink_filter)
160 })
161 .return_const(Ok(()));
162 let notifier = SimpleNotifier::new(Arc::new(transport), uri_provider);
163
164 let mut listener = MockUListener::new();
165 listener.expect_on_receive().never();
166 let result = notifier.start_listening(&topic, Arc::new(listener)).await;
167 assert!(result.is_ok());
168 }
169
170 #[tokio::test]
171 async fn test_stop_listening_succeeds() {
172 let uri_provider = new_uri_provider();
173 let topic = UUri::try_from("up://my-vin/A15B/1/B10F").unwrap();
174 let expected_source_filter = topic.clone();
175 let expected_sink_filter = uri_provider.get_source_uri();
176 let mut transport = MockTransport::new();
177 transport
178 .expect_do_unregister_listener()
179 .once()
180 .withf(move |source_filter, sink_filter, _listener| {
181 source_filter == &expected_source_filter
182 && *sink_filter == Some(&expected_sink_filter)
183 })
184 .return_const(Ok(()));
185 let notifier = SimpleNotifier::new(Arc::new(transport), uri_provider);
186
187 let mut listener = MockUListener::new();
188 listener.expect_on_receive().never();
189 let result = notifier.stop_listening(&topic, Arc::new(listener)).await;
190 assert!(result.is_ok());
191 }
192
193 #[tokio::test]
194 async fn test_publish_succeeds() {
195 let message_id = UUID::build();
196 let value = b"Hello";
197
198 let uri_provider = new_uri_provider();
199 let destination = UUri::try_from("up://other-vin/A15B/1/0").unwrap();
200 let expected_message_id = message_id.clone();
201 let expected_sink = destination.clone();
202 let expected_source = uri_provider.get_resource_uri(0xB10F);
203 let mut transport = MockTransport::new();
204 transport
205 .expect_do_send()
206 .once()
207 .withf(move |message| {
208 message.is_notification()
209 && message.id() == &expected_message_id
210 && message.source() == &expected_source
211 && message.sink_unchecked() == &expected_sink
212 && message.ttl_unchecked() == 10_000
213 && message.priority_unchecked() == UPriority::CS2
214 && message.payload() == Some(value.as_slice().into())
215 })
216 .return_const(Ok(()));
217 let notifier = SimpleNotifier::new(Arc::new(transport), uri_provider);
218
219 let payload = UPayload::new(value.as_slice(), crate::UPayloadFormat::Raw);
220 let options =
221 CallOptions::for_notification(Some(10_000), Some(message_id), Some(UPriority::CS2));
222 let result = notifier
223 .notify(0xB10F, &destination, options, Some(payload))
224 .await;
225 assert!(result.is_ok());
226 }
227
228 #[tokio::test]
229 async fn test_publish_fails_for_transport_error() {
230 let uri_provider = new_uri_provider();
231 let destination = UUri::try_from("up://other-vin/A15B/1/0").unwrap();
232 let mut transport = MockTransport::new();
233 transport
234 .expect_do_send()
235 .once()
236 .return_const(Err(UStatus::fail_with_code(
237 crate::UCode::Unavailable,
238 "connection lost",
239 )));
240 let notifier = SimpleNotifier::new(Arc::new(transport), uri_provider);
241
242 let options = CallOptions::for_notification(None, None, None);
243 let result = notifier.notify(0xB10F, &destination, options, None).await;
244 assert!(result.is_err_and(|e| match e {
245 NotificationError::NotifyError(status) => status.code() == UCode::Unavailable,
246 _ => false,
247 }));
248 }
249
250 #[tokio::test]
251 async fn test_publish_fails_for_invalid_destination() {
252 let uri_provider = new_uri_provider();
253 let destination = UUri::try_from("up://other-vin/A15B/1/10").unwrap();
255 let mut transport = MockTransport::new();
256 transport.expect_do_send().never();
257 let notifier = SimpleNotifier::new(Arc::new(transport), uri_provider);
258
259 let options = CallOptions::for_notification(None, None, None);
260 let result = notifier.notify(0xB10F, &destination, options, None).await;
261 assert!(result.is_err_and(|e| matches!(e, NotificationError::InvalidArgument(_))));
262 }
263
264 #[tokio::test]
265 async fn test_publish_fails_for_invalid_resource_id() {
266 let uri_provider = new_uri_provider();
267 let destination = UUri::try_from("up://other-vin/A15B/1/0").unwrap();
268 let mut transport = MockTransport::new();
269 transport.expect_do_send().never();
270 let notifier = SimpleNotifier::new(Arc::new(transport), uri_provider);
271
272 let options = CallOptions::for_notification(None, None, None);
273 let result = notifier.notify(0x0000, &destination, options, None).await;
275 assert!(result.is_err_and(|e| matches!(e, NotificationError::InvalidArgument(_))));
276 }
277}