1use std::{
35 any::{Any, TypeId},
36 collections::HashMap,
37 fmt::Debug,
38 marker::PhantomData,
39 sync::Arc,
40};
41
42use nautilus_core::UUID4;
43
44use crate::{
45 capture::encoder::{Encode, EncodeError, EncodedPayload, TypedEncoder},
46 entry::PayloadType,
47 headers::Headers,
48};
49
50pub trait HeadersExtractor: Send + Sync {
59 fn extract(&self, message: &dyn Any) -> Headers;
63}
64
65pub struct TypedHeadersExtractor<T: 'static, F> {
67 func: F,
68 _phantom: PhantomData<fn(&T)>,
69}
70
71impl<T: 'static, F> TypedHeadersExtractor<T, F>
72where
73 F: Fn(&T) -> Headers + Send + Sync,
74{
75 #[must_use]
77 pub const fn new(func: F) -> Self {
78 Self {
79 func,
80 _phantom: PhantomData,
81 }
82 }
83}
84
85impl<T: 'static, F> Debug for TypedHeadersExtractor<T, F> {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct(stringify!(TypedHeadersExtractor))
88 .field("type", &std::any::type_name::<T>())
89 .finish_non_exhaustive()
90 }
91}
92
93impl<T: 'static, F> HeadersExtractor for TypedHeadersExtractor<T, F>
94where
95 F: Fn(&T) -> Headers + Send + Sync,
96{
97 fn extract(&self, message: &dyn Any) -> Headers {
98 message
99 .downcast_ref::<T>()
100 .map(&self.func)
101 .unwrap_or_default()
102 }
103}
104
105#[derive(Debug, Default)]
108struct EmptyHeadersExtractor;
109
110impl HeadersExtractor for EmptyHeadersExtractor {
111 fn extract(&self, _: &dyn Any) -> Headers {
112 Headers::empty()
113 }
114}
115
116pub trait IdentityExtractor: Send + Sync {
122 fn extract(&self, message: &dyn Any) -> Option<UUID4>;
125}
126
127pub struct TypedIdentityExtractor<T: 'static, F> {
129 func: F,
130 _phantom: PhantomData<fn(&T)>,
131}
132
133impl<T: 'static, F> TypedIdentityExtractor<T, F>
134where
135 F: Fn(&T) -> Option<UUID4> + Send + Sync,
136{
137 #[must_use]
139 pub const fn new(func: F) -> Self {
140 Self {
141 func,
142 _phantom: PhantomData,
143 }
144 }
145}
146
147impl<T: 'static, F> Debug for TypedIdentityExtractor<T, F> {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 f.debug_struct(stringify!(TypedIdentityExtractor))
150 .field("type", &std::any::type_name::<T>())
151 .finish_non_exhaustive()
152 }
153}
154
155impl<T: 'static, F> IdentityExtractor for TypedIdentityExtractor<T, F>
156where
157 F: Fn(&T) -> Option<UUID4> + Send + Sync,
158{
159 fn extract(&self, message: &dyn Any) -> Option<UUID4> {
160 message.downcast_ref::<T>().and_then(&self.func)
161 }
162}
163
164#[derive(Clone)]
167struct Registered {
168 payload_type: PayloadType,
169 encoder: Arc<dyn Encode>,
170 headers: Arc<dyn HeadersExtractor>,
171 identity: Option<Arc<dyn IdentityExtractor>>,
172}
173
174impl Debug for Registered {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.debug_struct(stringify!(Registered))
177 .field("payload_type", &self.payload_type.as_str())
178 .finish_non_exhaustive()
179 }
180}
181
182#[derive(Clone, Debug, Default)]
189pub struct EncoderRegistry {
190 by_type: HashMap<TypeId, Registered>,
191}
192
193impl EncoderRegistry {
194 #[must_use]
196 pub fn new() -> Self {
197 Self::default()
198 }
199
200 pub fn register<T, F>(&mut self, payload_type: PayloadType, func: F)
208 where
209 T: 'static,
210 F: Fn(&T) -> Result<EncodedPayload, EncodeError> + Send + Sync + 'static,
211 {
212 let encoder: Arc<dyn Encode> = Arc::new(TypedEncoder::<T, F>::new(func));
213 let headers = self
214 .preserved_headers::<T>()
215 .unwrap_or_else(|| Arc::new(EmptyHeadersExtractor) as Arc<dyn HeadersExtractor>);
216 let identity = self.preserved_identity::<T>();
217 self.by_type.insert(
218 TypeId::of::<T>(),
219 Registered {
220 payload_type,
221 encoder,
222 headers,
223 identity,
224 },
225 );
226 }
227
228 pub fn register_with_headers<T, F, H>(
231 &mut self,
232 payload_type: PayloadType,
233 func: F,
234 headers_fn: H,
235 ) where
236 T: 'static,
237 F: Fn(&T) -> Result<EncodedPayload, EncodeError> + Send + Sync + 'static,
238 H: Fn(&T) -> Headers + Send + Sync + 'static,
239 {
240 let encoder: Arc<dyn Encode> = Arc::new(TypedEncoder::<T, F>::new(func));
241 let headers: Arc<dyn HeadersExtractor> =
242 Arc::new(TypedHeadersExtractor::<T, H>::new(headers_fn));
243 let identity = self.preserved_identity::<T>();
244 self.by_type.insert(
245 TypeId::of::<T>(),
246 Registered {
247 payload_type,
248 encoder,
249 headers,
250 identity,
251 },
252 );
253 }
254
255 pub fn register_encoder<T: 'static>(
261 &mut self,
262 payload_type: PayloadType,
263 encoder: Arc<dyn Encode>,
264 ) {
265 let headers = self
266 .preserved_headers::<T>()
267 .unwrap_or_else(|| Arc::new(EmptyHeadersExtractor) as Arc<dyn HeadersExtractor>);
268 let identity = self.preserved_identity::<T>();
269 self.by_type.insert(
270 TypeId::of::<T>(),
271 Registered {
272 payload_type,
273 encoder,
274 headers,
275 identity,
276 },
277 );
278 }
279
280 pub fn register_headers<T, H>(&mut self, headers_fn: H)
287 where
288 T: 'static,
289 H: Fn(&T) -> Headers + Send + Sync + 'static,
290 {
291 if let Some(reg) = self.by_type.get_mut(&TypeId::of::<T>()) {
292 reg.headers = Arc::new(TypedHeadersExtractor::<T, H>::new(headers_fn));
293 }
294 }
295
296 pub fn register_identity<T, F>(&mut self, identity_fn: F)
302 where
303 T: 'static,
304 F: Fn(&T) -> Option<UUID4> + Send + Sync + 'static,
305 {
306 if let Some(reg) = self.by_type.get_mut(&TypeId::of::<T>()) {
307 reg.identity = Some(Arc::new(TypedIdentityExtractor::<T, F>::new(identity_fn)));
308 }
309 }
310
311 fn preserved_headers<T: 'static>(&self) -> Option<Arc<dyn HeadersExtractor>> {
312 self.by_type
313 .get(&TypeId::of::<T>())
314 .map(|reg| Arc::clone(®.headers))
315 }
316
317 fn preserved_identity<T: 'static>(&self) -> Option<Arc<dyn IdentityExtractor>> {
318 self.by_type
319 .get(&TypeId::of::<T>())
320 .and_then(|reg| reg.identity.clone())
321 }
322
323 #[must_use]
325 pub fn len(&self) -> usize {
326 self.by_type.len()
327 }
328
329 #[must_use]
331 pub fn is_empty(&self) -> bool {
332 self.by_type.is_empty()
333 }
334
335 #[must_use]
337 pub fn contains<T: 'static>(&self) -> bool {
338 self.by_type.contains_key(&TypeId::of::<T>())
339 }
340
341 pub fn encode<T: 'static>(
352 &self,
353 message: &T,
354 ) -> Result<Option<(PayloadType, EncodedPayload)>, EncodeError> {
355 let Some(reg) = self.by_type.get(&TypeId::of::<T>()) else {
356 return Ok(None);
357 };
358
359 let encoded = reg.encoder.encode(message as &dyn Any)?;
360 let payload_type = encoded.payload_type.unwrap_or(reg.payload_type);
361 Ok(Some((payload_type, encoded)))
362 }
363
364 pub fn encode_any(
374 &self,
375 message: &dyn Any,
376 ) -> Result<Option<(PayloadType, EncodedPayload)>, EncodeError> {
377 let Some(reg) = self.by_type.get(&message.type_id()) else {
378 return Ok(None);
379 };
380
381 let encoded = reg.encoder.encode(message)?;
382 let payload_type = encoded.payload_type.unwrap_or(reg.payload_type);
383 Ok(Some((payload_type, encoded)))
384 }
385
386 #[must_use]
390 pub fn headers_for_any(&self, message: &dyn Any) -> Option<Headers> {
391 self.by_type
392 .get(&message.type_id())
393 .map(|reg| reg.headers.extract(message))
394 }
395
396 #[must_use]
400 pub fn identity_for_any(&self, message: &dyn Any) -> Option<UUID4> {
401 self.by_type
402 .get(&message.type_id())
403 .and_then(|reg| reg.identity.as_ref())
404 .and_then(|identity| identity.extract(message))
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use std::sync::Arc;
411
412 use bytes::Bytes;
413 use rstest::rstest;
414 use ustr::Ustr;
415
416 use super::*;
417
418 #[derive(Debug)]
419 struct Sample(u8);
420
421 #[derive(Debug)]
422 struct Other;
423
424 #[derive(Debug)]
425 struct StatefulEncoder {
426 prefix: u8,
427 }
428
429 impl Encode for StatefulEncoder {
430 fn encode(&self, message: &dyn Any) -> Result<EncodedPayload, EncodeError> {
431 let sample = message
432 .downcast_ref::<Sample>()
433 .ok_or(EncodeError::TypeMismatch {
434 expected: std::any::type_name::<Sample>(),
435 })?;
436 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
437 self.prefix,
438 sample.0,
439 ])))
440 }
441 }
442
443 #[rstest]
444 fn unknown_type_returns_none() {
445 let registry = EncoderRegistry::new();
446
447 assert!(registry.encode(&Sample(1)).expect("encode").is_none());
448 assert!(!registry.contains::<Sample>());
449 }
450
451 #[rstest]
452 fn registered_type_returns_payload_type_and_payload() {
453 let mut registry = EncoderRegistry::new();
454 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
455 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
456 s.0,
457 ])))
458 });
459
460 let (tag, encoded) = registry.encode(&Sample(9)).expect("encode").expect("hit");
461
462 assert_eq!(tag.as_str(), "Sample");
463 assert_eq!(encoded.payload.as_ref(), &[9]);
464 assert!(registry.contains::<Sample>());
465 assert_eq!(registry.len(), 1);
466 }
467
468 #[rstest]
469 fn re_registering_replaces_prior_encoder() {
470 let mut registry = EncoderRegistry::new();
471 registry.register::<Sample, _>(Ustr::from("Old"), |s| {
472 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
473 s.0,
474 ])))
475 });
476 registry.register::<Sample, _>(Ustr::from("New"), |s| {
477 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
478 s.0, s.0,
479 ])))
480 });
481
482 let (tag, encoded) = registry.encode(&Sample(3)).expect("encode").expect("hit");
483
484 assert_eq!(tag.as_str(), "New");
485 assert_eq!(encoded.payload.as_ref(), &[3, 3]);
486 assert_eq!(registry.len(), 1);
487 }
488
489 #[rstest]
490 fn register_encoder_replaces_encoder_and_preserves_extractors() {
491 let mut registry = EncoderRegistry::new();
492 registry.register::<Sample, _>(Ustr::from("Old"), |s| {
493 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
494 s.0,
495 ])))
496 });
497 let correlation_id = UUID4::new();
498 let causation_id = UUID4::new();
499 registry.register_headers::<Sample, _>(move |_| Headers {
500 correlation_id: Some(correlation_id),
501 causation_id: Some(causation_id),
502 });
503 let identity = UUID4::new();
504 registry.register_identity::<Sample, _>(move |_| Some(identity));
505
506 registry.register_encoder::<Sample>(
507 Ustr::from("Stateful"),
508 Arc::new(StatefulEncoder { prefix: 0xA5 }),
509 );
510 let sample = Sample(7);
511 let (payload_type, encoded) = registry.encode(&sample).expect("encode").expect("hit");
512 let headers = registry
513 .headers_for_any(&sample as &dyn Any)
514 .expect("headers");
515
516 assert_eq!(payload_type.as_str(), "Stateful");
517 assert_eq!(
518 encoded,
519 EncodedPayload::without_indices(Bytes::from_static(&[0xA5, 7])),
520 );
521 assert_eq!(
522 headers,
523 Headers {
524 correlation_id: Some(correlation_id),
525 causation_id: Some(causation_id),
526 },
527 );
528 assert_eq!(
529 registry.identity_for_any(&sample as &dyn Any),
530 Some(identity)
531 );
532 assert_eq!(registry.len(), 1);
533 }
534
535 #[rstest]
536 fn registry_is_empty_by_default() {
537 let registry = EncoderRegistry::new();
538
539 assert!(registry.is_empty());
540 assert_eq!(registry.len(), 0);
541 assert!(!registry.contains::<Other>());
542 }
543
544 #[rstest]
545 fn encode_any_dispatches_by_concrete_type_id() {
546 let mut registry = EncoderRegistry::new();
550 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
551 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
552 s.0,
553 ])))
554 });
555
556 let sample = Sample(5);
557 let (tag, encoded) = registry
558 .encode_any(&sample as &dyn Any)
559 .expect("encode_any")
560 .expect("hit");
561
562 assert_eq!(tag.as_str(), "Sample");
563 assert_eq!(encoded.payload.as_ref(), &[5]);
564 }
565
566 #[rstest]
567 fn encode_any_returns_none_for_unregistered_type() {
568 let registry = EncoderRegistry::new();
572
573 let unregistered = Other;
574 let outcome = registry
575 .encode_any(&unregistered as &dyn Any)
576 .expect("encode_any");
577
578 assert!(outcome.is_none());
579 }
580
581 #[rstest]
582 fn encoder_payload_type_override_overrides_registered_tag() {
583 let mut registry = EncoderRegistry::new();
589 registry.register::<Sample, _>(Ustr::from("Wrapper"), |s| {
590 Ok(EncodedPayload::with_payload_type(
591 Ustr::from("Inner"),
592 Bytes::copy_from_slice(&[s.0]),
593 Vec::new(),
594 ))
595 });
596
597 let (tag, _) = registry.encode(&Sample(1)).expect("encode").expect("hit");
598 assert_eq!(tag.as_str(), "Inner");
599
600 let (any_tag, _) = registry
601 .encode_any(&Sample(1) as &dyn Any)
602 .expect("encode_any")
603 .expect("hit");
604 assert_eq!(any_tag.as_str(), "Inner");
605 }
606
607 #[rstest]
608 fn registered_type_without_headers_extractor_returns_empty_headers() {
609 let mut registry = EncoderRegistry::new();
610 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
611 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
612 s.0,
613 ])))
614 });
615
616 let headers = registry
617 .headers_for_any(&Sample(1) as &dyn Any)
618 .expect("hit");
619 assert_eq!(headers, Headers::empty());
620 }
621
622 #[rstest]
623 fn headers_for_any_returns_none_for_unregistered_type() {
624 let registry = EncoderRegistry::new();
625 let outcome = registry.headers_for_any(&Other as &dyn Any);
626
627 assert!(outcome.is_none());
628 }
629
630 #[rstest]
631 fn register_with_headers_uses_extractor() {
632 let mut registry = EncoderRegistry::new();
636 let causation = nautilus_core::UUID4::new();
637 let causation_captured = causation;
638 registry.register_with_headers::<Sample, _, _>(
639 Ustr::from("Sample"),
640 |s| {
641 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
642 s.0,
643 ])))
644 },
645 move |_| Headers {
646 correlation_id: None,
647 causation_id: Some(causation_captured),
648 },
649 );
650
651 let headers = registry
652 .headers_for_any(&Sample(1) as &dyn Any)
653 .expect("hit");
654 assert_eq!(headers.causation_id, Some(causation));
655 }
656
657 #[rstest]
658 fn register_headers_overrides_default_extractor_post_register() {
659 let mut registry = EncoderRegistry::new();
663 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
664 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
665 s.0,
666 ])))
667 });
668 let correlation = nautilus_core::UUID4::new();
669 let correlation_captured = correlation;
670 registry.register_headers::<Sample, _>(move |_| Headers {
671 correlation_id: Some(correlation_captured),
672 causation_id: None,
673 });
674
675 let headers = registry
676 .headers_for_any(&Sample(1) as &dyn Any)
677 .expect("hit");
678 assert_eq!(headers.correlation_id, Some(correlation));
679 }
680
681 #[rstest]
682 fn identity_for_any_returns_none_without_extractor() {
683 let mut registry = EncoderRegistry::new();
684 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
685 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
686 s.0,
687 ])))
688 });
689
690 assert!(registry.identity_for_any(&Sample(1) as &dyn Any).is_none());
691 assert!(registry.identity_for_any(&Other as &dyn Any).is_none());
692 }
693
694 #[rstest]
695 fn register_identity_extracts_and_survives_re_register() {
696 let mut registry = EncoderRegistry::new();
699 registry.register::<Sample, _>(Ustr::from("Old"), |s| {
700 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
701 s.0,
702 ])))
703 });
704 let identity = nautilus_core::UUID4::new();
705 registry.register_identity::<Sample, _>(move |_| Some(identity));
706
707 assert_eq!(
708 registry.identity_for_any(&Sample(1) as &dyn Any),
709 Some(identity),
710 );
711
712 registry.register::<Sample, _>(Ustr::from("New"), |s| {
713 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
714 s.0, s.0,
715 ])))
716 });
717
718 assert_eq!(
719 registry.identity_for_any(&Sample(1) as &dyn Any),
720 Some(identity),
721 );
722 }
723
724 #[rstest]
725 fn register_identity_for_unregistered_type_is_silent_noop() {
726 let mut registry = EncoderRegistry::new();
727 registry.register_identity::<Sample, _>(|_| Some(nautilus_core::UUID4::new()));
728
729 assert!(!registry.contains::<Sample>());
730 assert!(registry.identity_for_any(&Sample(1) as &dyn Any).is_none());
731 }
732
733 #[rstest]
734 fn register_headers_for_unregistered_type_is_silent_noop() {
735 let mut registry = EncoderRegistry::new();
736 registry.register_headers::<Sample, _>(|_| Headers::empty());
737
738 assert!(!registry.contains::<Sample>());
739 assert!(registry.headers_for_any(&Sample(1) as &dyn Any).is_none());
740 }
741
742 #[rstest]
743 fn re_registering_preserves_existing_headers_extractor() {
744 let mut registry = EncoderRegistry::new();
748 registry.register::<Sample, _>(Ustr::from("Old"), |s| {
749 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
750 s.0,
751 ])))
752 });
753 let causation = nautilus_core::UUID4::new();
754 let causation_captured = causation;
755 registry.register_headers::<Sample, _>(move |_| Headers {
756 correlation_id: None,
757 causation_id: Some(causation_captured),
758 });
759 registry.register::<Sample, _>(Ustr::from("New"), |s| {
760 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
761 s.0, s.0,
762 ])))
763 });
764
765 let (tag, _) = registry.encode(&Sample(3)).expect("encode").expect("hit");
766 assert_eq!(tag.as_str(), "New");
767 let headers = registry
768 .headers_for_any(&Sample(3) as &dyn Any)
769 .expect("hit");
770 assert_eq!(headers.causation_id, Some(causation));
771 }
772}