1use bytes::Bytes;
15
16use crate::{UFrameMetadata, UFrameMetadataError};
17
18#[derive(Clone, Debug, PartialEq)]
24pub struct UOwnedFrame<S = Validated> {
25 metadata: UFrameMetadata,
26 payload: Option<Bytes>,
27 _state: core::marker::PhantomData<S>,
28}
29
30pub use crate::validation_state::{Unvalidated, Validated};
31
32impl UOwnedFrame<Validated> {
33 pub fn new(
40 metadata: UFrameMetadata,
41 payload: Option<Bytes>,
42 ) -> Result<Self, UFrameMetadataError> {
43 let frame = UOwnedFrame::<Validated> {
44 metadata,
45 payload,
46 _state: core::marker::PhantomData,
47 };
48 frame.check()?;
49 Ok(frame)
50 }
51
52 #[must_use]
54 pub fn new_unchecked(
55 metadata: UFrameMetadata,
56 payload: Option<Bytes>,
57 ) -> UOwnedFrame<Unvalidated> {
58 UOwnedFrame {
59 metadata,
60 payload,
61 _state: core::marker::PhantomData,
62 }
63 }
64
65 pub fn with_payload(
72 metadata: UFrameMetadata,
73 payload: impl Into<Bytes>,
74 ) -> Result<Self, UFrameMetadataError> {
75 Self::new(metadata, Some(payload.into()))
76 }
77
78 #[must_use]
81 #[cfg(test)]
82 pub(crate) fn with_payload_unchecked(
83 metadata: UFrameMetadata,
84 payload: impl Into<Bytes>,
85 ) -> UOwnedFrame<Unvalidated> {
86 Self::new_unchecked(metadata, Some(payload.into()))
87 }
88
89 pub fn without_payload(metadata: UFrameMetadata) -> Result<Self, UFrameMetadataError> {
96 Self::new(metadata, None)
97 }
98}
99
100impl UOwnedFrame<Unvalidated> {
101 pub fn validate(self) -> Result<UOwnedFrame<Validated>, UFrameMetadataError> {
113 self.check()?;
114 Ok(UOwnedFrame {
115 metadata: self.metadata,
116 payload: self.payload,
117 _state: core::marker::PhantomData,
118 })
119 }
120}
121
122impl UOwnedFrame<Validated> {
123 pub fn to_umessage(&self) -> Result<crate::UMessage, crate::UFrameMetadataError> {
131 crate::frame::metadata::try_project_frame_to_umessage(
132 self.metadata().clone(),
133 self.payload().cloned(),
134 )
135 }
136}
137
138impl<S> UOwnedFrame<S> {
139 pub(crate) fn check(&self) -> Result<(), UFrameMetadataError> {
147 self.metadata.validate()?;
148 match (
149 self.payload.is_some(),
150 self.metadata.payload_encoding().is_some(),
151 ) {
152 (true, true) | (false, false) => Ok(()),
153 (true, false) => Err(UFrameMetadataError::PayloadWithoutEncoding),
154 (false, true) => Err(UFrameMetadataError::EncodingWithoutPayload),
155 }
156 }
157
158 #[must_use]
160 pub fn metadata(&self) -> &UFrameMetadata {
161 &self.metadata
162 }
163
164 #[must_use]
166 pub fn payload(&self) -> Option<&Bytes> {
167 self.payload.as_ref()
168 }
169
170 #[must_use]
175 pub fn payload_bytes(&self) -> &[u8] {
176 self.payload.as_deref().unwrap_or_default()
177 }
178
179 #[must_use]
181 pub fn has_payload(&self) -> bool {
182 self.payload.is_some()
183 }
184
185 #[must_use]
187 pub fn into_metadata(self) -> UFrameMetadata {
188 self.metadata
189 }
190
191 #[must_use]
193 pub fn into_payload(self) -> Option<Bytes> {
194 self.payload
195 }
196
197 #[must_use]
199 pub fn into_parts(self) -> (UFrameMetadata, Option<Bytes>) {
200 (self.metadata, self.payload)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::{PayloadEncoding, UMessageBuilder, UUri};
208
209 fn topic() -> UUri {
210 UUri::try_from_parts("vehicle", 0x4210, 0x01, 0x9000).expect("failed to create topic")
211 }
212
213 fn metadata_without_encoding() -> UFrameMetadata {
214 let message = UMessageBuilder::publish(topic()).build().expect("message");
215 crate::frame::metadata::try_project_attributes_to_frame_metadata(message.attributes(), None)
216 .expect("metadata")
217 }
218
219 fn metadata_with_encoding() -> UFrameMetadata {
220 let message = UMessageBuilder::publish(topic()).build().expect("message");
221 crate::frame::metadata::try_project_attributes_to_frame_metadata(
222 message.attributes(),
223 Some(PayloadEncoding::RAW),
224 )
225 .expect("metadata")
226 }
227
228 #[test]
229 fn owned_frame_rejects_payload_without_encoding() {
230 let error =
231 UOwnedFrame::with_payload(metadata_without_encoding(), Bytes::from_static(b"payload"))
232 .unwrap_err();
233
234 assert_eq!(error, UFrameMetadataError::PayloadWithoutEncoding);
235 }
236
237 #[test]
238 fn owned_frame_rejects_encoding_without_payload() {
239 let error = UOwnedFrame::without_payload(metadata_with_encoding()).unwrap_err();
240
241 assert_eq!(error, UFrameMetadataError::EncodingWithoutPayload);
242 }
243
244 #[test]
245 fn owned_frame_accepts_absent_payload_without_encoding() {
246 let frame = UOwnedFrame::without_payload(metadata_without_encoding()).unwrap();
247
248 assert!(!frame.has_payload());
249 assert_eq!(frame.payload_bytes(), &[] as &[u8]);
250 assert!(frame.metadata().payload_encoding().is_none());
251 }
252
253 #[test]
254 fn owned_frame_accepts_present_empty_payload_with_standard_encoding() {
255 let frame = UOwnedFrame::with_payload(metadata_with_encoding(), Bytes::new()).unwrap();
256
257 assert!(frame.has_payload());
258 assert_eq!(frame.payload(), Some(&Bytes::new()));
259 assert_eq!(frame.payload_bytes(), &[] as &[u8]);
260 assert_eq!(
261 frame.metadata().payload_encoding(),
262 Some(&PayloadEncoding::RAW)
263 );
264 }
265}
266
267#[cfg(any(test, feature = "test-util"))]
268type OwnedListenerRegistration = (
269 crate::UUri,
270 Option<crate::UUri>,
271 std::sync::Arc<dyn crate::UOwnedListener>,
272);
273
274#[cfg(any(test, feature = "test-util"))]
281#[derive(Default)]
282pub struct InMemoryOwnedTransport {
283 listeners: std::sync::RwLock<Vec<OwnedListenerRegistration>>,
284}
285
286#[cfg(any(test, feature = "test-util"))]
287impl core::fmt::Debug for InMemoryOwnedTransport {
288 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
289 f.debug_struct("InMemoryOwnedTransport")
290 .finish_non_exhaustive()
291 }
292}
293
294#[cfg(any(test, feature = "test-util"))]
295#[async_trait::async_trait]
296impl crate::UOwnedTransportImpl for InMemoryOwnedTransport {
297 async fn send_validated_owned(&self, frame: crate::UOwnedFrame) -> Result<(), crate::UStatus> {
298 let matching: Vec<_> = {
299 let metadata = frame.metadata();
300 let source = metadata.source();
301 let sink = metadata.sink();
302 self.listeners
303 .read()
304 .expect("lock")
305 .iter()
306 .filter(|(source_filter, sink_filter, _)| {
307 source_filter.matches(source)
308 && match (sink_filter, sink) {
309 (Some(pattern), Some(candidate)) => pattern.matches(candidate),
310 (None, None) => true,
311 _ => false,
312 }
313 })
314 .map(|(_, _, listener)| listener.clone())
315 .collect()
316 };
317 for listener in matching {
318 listener.on_receive_owned(frame.clone()).await;
319 }
320 Ok(())
321 }
322
323 async fn register_validated_owned_listener(
324 &self,
325 source_filter: &crate::UUri,
326 sink_filter: Option<&crate::UUri>,
327 listener: std::sync::Arc<dyn crate::UOwnedListener>,
328 ) -> Result<(), crate::UStatus> {
329 self.listeners.write().expect("lock").push((
330 source_filter.to_owned(),
331 sink_filter.map(ToOwned::to_owned),
332 listener,
333 ));
334 Ok(())
335 }
336
337 async fn unregister_validated_owned_listener(
338 &self,
339 source_filter: &crate::UUri,
340 sink_filter: Option<&crate::UUri>,
341 listener: std::sync::Arc<dyn crate::UOwnedListener>,
342 ) -> Result<(), crate::UStatus> {
343 let target = std::sync::Arc::as_ptr(&listener) as *const () as usize;
344 let mut listeners = self.listeners.write().expect("lock");
345 let before = listeners.len();
346 listeners.retain(|(sf, kf, l)| {
347 !(sf == source_filter
348 && kf.as_ref() == sink_filter
349 && std::sync::Arc::as_ptr(l) as *const () as usize == target)
350 });
351 if listeners.len() < before {
352 Ok(())
353 } else {
354 Err(crate::UStatus::fail_with_code(
355 crate::UCode::NotFound,
356 "no such owned listener",
357 ))
358 }
359 }
360}
361
362#[cfg(test)]
363mod in_memory_owned_transport_tests {
364 use std::sync::Arc;
365
366 use super::*;
367 use crate::{UMessageBuilder, UOwnedListener, UOwnedTransport, UUri};
368
369 struct Capture(std::sync::Mutex<Vec<UOwnedFrame>>);
370
371 #[async_trait::async_trait]
372 impl UOwnedListener for Capture {
373 async fn on_receive_owned(&self, frame: UOwnedFrame) {
374 self.0.lock().expect("capture lock poisoned").push(frame);
375 }
376 }
377
378 fn frame(topic: &UUri) -> UOwnedFrame {
379 let message = UMessageBuilder::publish(topic.clone())
380 .build()
381 .expect("failed to build message");
382 let metadata = crate::frame::metadata::try_project_attributes_to_frame_metadata(
383 message.attributes(),
384 Some(crate::PayloadEncoding::RAW),
385 )
386 .expect("failed to project metadata");
387 UOwnedFrame::with_payload(metadata, b"x".to_vec()).expect("failed to build frame")
388 }
389
390 async fn registered(transport: &InMemoryOwnedTransport, topic: &UUri) -> Arc<Capture> {
391 let capture = Arc::new(Capture(std::sync::Mutex::new(Vec::new())));
392 transport
393 .register_owned_listener(topic, None, capture.clone())
394 .await
395 .expect("listener registration succeeds");
396 capture
397 }
398
399 #[tokio::test]
400 async fn delivers_frames_matching_the_registered_filter() {
401 let transport = InMemoryOwnedTransport::default();
402 let topic = UUri::try_from_parts("demo", 0x1_0001, 1, 0x8001).expect("valid topic URI");
403 let capture = registered(&transport, &topic).await;
404
405 transport
406 .send_owned(frame(&topic))
407 .await
408 .expect("matching send succeeds");
409
410 assert_eq!(capture.0.lock().expect("capture lock").len(), 1);
411 }
412
413 #[tokio::test]
414 async fn does_not_deliver_frames_for_other_topics() {
415 let transport = InMemoryOwnedTransport::default();
416 let topic = UUri::try_from_parts("demo", 0x1_0001, 1, 0x8001).expect("valid topic URI");
417 let other = UUri::try_from_parts("demo", 0x1_0001, 1, 0x8002).expect("valid other URI");
418 let capture = registered(&transport, &topic).await;
419
420 transport
421 .send_owned(frame(&other))
422 .await
423 .expect("non-matching send succeeds");
424
425 assert!(capture.0.lock().expect("capture lock").is_empty());
426 }
427
428 #[tokio::test]
429 async fn unregistering_an_unknown_listener_returns_not_found() {
430 let transport = InMemoryOwnedTransport::default();
431 let topic = UUri::try_from_parts("demo", 0x1_0001, 1, 0x8001).expect("valid topic URI");
432 let capture = registered(&transport, &topic).await;
433
434 transport
435 .unregister_owned_listener(&topic, None, capture.clone())
436 .await
437 .expect("first unregister succeeds");
438 let error = transport
439 .unregister_owned_listener(&topic, None, capture)
440 .await
441 .expect_err("second unregister must fail");
442
443 assert_eq!(error.code(), crate::UCode::NotFound);
444 }
445}