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>(
353 &self,
354 message: &T,
355 ) -> Result<Option<(PayloadType, EncodedPayload)>, EncodeError> {
356 let Some(reg) = self.by_type.get(&TypeId::of::<T>()) else {
357 return Ok(None);
358 };
359
360 let encoded = reg.encoder.encode(message as &dyn Any)?;
361 let payload_type = encoded.payload_type.unwrap_or(reg.payload_type);
362 Ok(Some((payload_type, encoded)))
363 }
364
365 pub fn encode_any(
376 &self,
377 message: &dyn Any,
378 ) -> Result<Option<(PayloadType, EncodedPayload)>, EncodeError> {
379 let Some(reg) = self.by_type.get(&message.type_id()) else {
380 return Ok(None);
381 };
382
383 let encoded = reg.encoder.encode(message)?;
384 let payload_type = encoded.payload_type.unwrap_or(reg.payload_type);
385 Ok(Some((payload_type, encoded)))
386 }
387
388 #[must_use]
392 pub fn headers_for_any(&self, message: &dyn Any) -> Option<Headers> {
393 self.by_type
394 .get(&message.type_id())
395 .map(|reg| reg.headers.extract(message))
396 }
397
398 #[must_use]
402 pub fn identity_for_any(&self, message: &dyn Any) -> Option<UUID4> {
403 self.by_type
404 .get(&message.type_id())
405 .and_then(|reg| reg.identity.as_ref())
406 .and_then(|identity| identity.extract(message))
407 }
408}
409
410#[cfg(test)]
411mod tests {
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 #[rstest]
425 fn unknown_type_returns_none() {
426 let registry = EncoderRegistry::new();
427
428 assert!(registry.encode(&Sample(1)).expect("encode").is_none());
429 assert!(!registry.contains::<Sample>());
430 }
431
432 #[rstest]
433 fn registered_type_returns_payload_type_and_payload() {
434 let mut registry = EncoderRegistry::new();
435 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
436 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
437 s.0,
438 ])))
439 });
440
441 let (tag, encoded) = registry.encode(&Sample(9)).expect("encode").expect("hit");
442
443 assert_eq!(tag.as_str(), "Sample");
444 assert_eq!(encoded.payload.as_ref(), &[9]);
445 assert!(registry.contains::<Sample>());
446 assert_eq!(registry.len(), 1);
447 }
448
449 #[rstest]
450 fn re_registering_replaces_prior_encoder() {
451 let mut registry = EncoderRegistry::new();
452 registry.register::<Sample, _>(Ustr::from("Old"), |s| {
453 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
454 s.0,
455 ])))
456 });
457 registry.register::<Sample, _>(Ustr::from("New"), |s| {
458 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
459 s.0, s.0,
460 ])))
461 });
462
463 let (tag, encoded) = registry.encode(&Sample(3)).expect("encode").expect("hit");
464
465 assert_eq!(tag.as_str(), "New");
466 assert_eq!(encoded.payload.as_ref(), &[3, 3]);
467 assert_eq!(registry.len(), 1);
468 }
469
470 #[rstest]
471 fn registry_is_empty_by_default() {
472 let registry = EncoderRegistry::new();
473
474 assert!(registry.is_empty());
475 assert_eq!(registry.len(), 0);
476 assert!(!registry.contains::<Other>());
477 }
478
479 #[rstest]
480 fn encode_any_dispatches_by_concrete_type_id() {
481 let mut registry = EncoderRegistry::new();
485 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
486 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
487 s.0,
488 ])))
489 });
490
491 let sample = Sample(5);
492 let (tag, encoded) = registry
493 .encode_any(&sample as &dyn Any)
494 .expect("encode_any")
495 .expect("hit");
496
497 assert_eq!(tag.as_str(), "Sample");
498 assert_eq!(encoded.payload.as_ref(), &[5]);
499 }
500
501 #[rstest]
502 fn encode_any_returns_none_for_unregistered_type() {
503 let registry = EncoderRegistry::new();
507
508 let unregistered = Other;
509 let outcome = registry
510 .encode_any(&unregistered as &dyn Any)
511 .expect("encode_any");
512
513 assert!(outcome.is_none());
514 }
515
516 #[rstest]
517 fn encoder_payload_type_override_overrides_registered_tag() {
518 let mut registry = EncoderRegistry::new();
524 registry.register::<Sample, _>(Ustr::from("Wrapper"), |s| {
525 Ok(EncodedPayload::with_payload_type(
526 Ustr::from("Inner"),
527 Bytes::copy_from_slice(&[s.0]),
528 Vec::new(),
529 ))
530 });
531
532 let (tag, _) = registry.encode(&Sample(1)).expect("encode").expect("hit");
533 assert_eq!(tag.as_str(), "Inner");
534
535 let (any_tag, _) = registry
536 .encode_any(&Sample(1) as &dyn Any)
537 .expect("encode_any")
538 .expect("hit");
539 assert_eq!(any_tag.as_str(), "Inner");
540 }
541
542 #[rstest]
543 fn registered_type_without_headers_extractor_returns_empty_headers() {
544 let mut registry = EncoderRegistry::new();
545 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
546 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
547 s.0,
548 ])))
549 });
550
551 let headers = registry
552 .headers_for_any(&Sample(1) as &dyn Any)
553 .expect("hit");
554 assert_eq!(headers, Headers::empty());
555 }
556
557 #[rstest]
558 fn headers_for_any_returns_none_for_unregistered_type() {
559 let registry = EncoderRegistry::new();
560 let outcome = registry.headers_for_any(&Other as &dyn Any);
561
562 assert!(outcome.is_none());
563 }
564
565 #[rstest]
566 fn register_with_headers_uses_extractor() {
567 let mut registry = EncoderRegistry::new();
571 let causation = nautilus_core::UUID4::new();
572 let causation_captured = causation;
573 registry.register_with_headers::<Sample, _, _>(
574 Ustr::from("Sample"),
575 |s| {
576 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
577 s.0,
578 ])))
579 },
580 move |_| Headers {
581 correlation_id: None,
582 causation_id: Some(causation_captured),
583 },
584 );
585
586 let headers = registry
587 .headers_for_any(&Sample(1) as &dyn Any)
588 .expect("hit");
589 assert_eq!(headers.causation_id, Some(causation));
590 }
591
592 #[rstest]
593 fn register_headers_overrides_default_extractor_post_register() {
594 let mut registry = EncoderRegistry::new();
598 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
599 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
600 s.0,
601 ])))
602 });
603 let correlation = nautilus_core::UUID4::new();
604 let correlation_captured = correlation;
605 registry.register_headers::<Sample, _>(move |_| Headers {
606 correlation_id: Some(correlation_captured),
607 causation_id: None,
608 });
609
610 let headers = registry
611 .headers_for_any(&Sample(1) as &dyn Any)
612 .expect("hit");
613 assert_eq!(headers.correlation_id, Some(correlation));
614 }
615
616 #[rstest]
617 fn identity_for_any_returns_none_without_extractor() {
618 let mut registry = EncoderRegistry::new();
619 registry.register::<Sample, _>(Ustr::from("Sample"), |s| {
620 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
621 s.0,
622 ])))
623 });
624
625 assert!(registry.identity_for_any(&Sample(1) as &dyn Any).is_none());
626 assert!(registry.identity_for_any(&Other as &dyn Any).is_none());
627 }
628
629 #[rstest]
630 fn register_identity_extracts_and_survives_re_register() {
631 let mut registry = EncoderRegistry::new();
634 registry.register::<Sample, _>(Ustr::from("Old"), |s| {
635 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
636 s.0,
637 ])))
638 });
639 let identity = nautilus_core::UUID4::new();
640 registry.register_identity::<Sample, _>(move |_| Some(identity));
641
642 assert_eq!(
643 registry.identity_for_any(&Sample(1) as &dyn Any),
644 Some(identity),
645 );
646
647 registry.register::<Sample, _>(Ustr::from("New"), |s| {
648 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
649 s.0, s.0,
650 ])))
651 });
652
653 assert_eq!(
654 registry.identity_for_any(&Sample(1) as &dyn Any),
655 Some(identity),
656 );
657 }
658
659 #[rstest]
660 fn register_identity_for_unregistered_type_is_silent_noop() {
661 let mut registry = EncoderRegistry::new();
662 registry.register_identity::<Sample, _>(|_| Some(nautilus_core::UUID4::new()));
663
664 assert!(!registry.contains::<Sample>());
665 assert!(registry.identity_for_any(&Sample(1) as &dyn Any).is_none());
666 }
667
668 #[rstest]
669 fn register_headers_for_unregistered_type_is_silent_noop() {
670 let mut registry = EncoderRegistry::new();
671 registry.register_headers::<Sample, _>(|_| Headers::empty());
672
673 assert!(!registry.contains::<Sample>());
674 assert!(registry.headers_for_any(&Sample(1) as &dyn Any).is_none());
675 }
676
677 #[rstest]
678 fn re_registering_preserves_existing_headers_extractor() {
679 let mut registry = EncoderRegistry::new();
683 registry.register::<Sample, _>(Ustr::from("Old"), |s| {
684 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
685 s.0,
686 ])))
687 });
688 let causation = nautilus_core::UUID4::new();
689 let causation_captured = causation;
690 registry.register_headers::<Sample, _>(move |_| Headers {
691 correlation_id: None,
692 causation_id: Some(causation_captured),
693 });
694 registry.register::<Sample, _>(Ustr::from("New"), |s| {
695 Ok(EncodedPayload::without_indices(Bytes::copy_from_slice(&[
696 s.0, s.0,
697 ])))
698 });
699
700 let (tag, _) = registry.encode(&Sample(3)).expect("encode").expect("hit");
701 assert_eq!(tag.as_str(), "New");
702 let headers = registry
703 .headers_for_any(&Sample(3) as &dyn Any)
704 .expect("hit");
705 assert_eq!(headers.causation_id, Some(causation));
706 }
707}