use async_trait::async_trait; use domain::errors::DomainError; use event_transport::{MessageSource, RawMessage, Transport}; use futures::stream::BoxStream; // ── NatsTransport — raw NATS publish backend ──────────────────────────────── pub struct NatsTransport { client: async_nats::Client, } impl NatsTransport { pub fn new(client: async_nats::Client) -> Self { Self { client } } } #[async_trait] impl Transport for NatsTransport { async fn publish_bytes(&self, subject: &str, bytes: &[u8]) -> Result<(), DomainError> { self.client .publish(subject.to_string(), bytes.to_vec().into()) .await .map_err(|e| DomainError::Internal(e.to_string())) } } // ── NatsMessageSource — raw NATS subscribe backend ────────────────────────── pub struct NatsMessageSource { client: async_nats::Client, } impl NatsMessageSource { pub fn new(client: async_nats::Client) -> Self { Self { client } } } impl MessageSource for NatsMessageSource { fn messages(&self) -> BoxStream<'_, Result> { let client = self.client.clone(); Box::pin(async_stream::try_stream! { let mut sub = client .subscribe(">") .await .map_err(|e| DomainError::Internal(e.to_string()))?; use futures::StreamExt; while let Some(msg) = sub.next().await { let subject = msg.subject.to_string(); let payload = msg.payload.to_vec(); // Basic NATS: at-most-once — ack/nack are no-ops. yield RawMessage { subject, payload, ack: Box::new(|| {}), nack: Box::new(|| {}), }; } }) } } #[cfg(test)] mod tests { use super::*; use domain::{ events::DomainEvent, value_objects::{LikeId, ThoughtId, UserId}, }; use event_payload::EventPayload; #[test] fn payload_from_domain_event_has_correct_subject() { let event = DomainEvent::ThoughtCreated { thought_id: ThoughtId::new(), user_id: UserId::new(), in_reply_to_id: None, }; let payload = EventPayload::from(&event); assert_eq!(payload.subject(), "thoughts.created"); } #[test] fn domain_event_roundtrip_via_payload() { let uid = UserId::new(); let tid = ThoughtId::new(); let event = DomainEvent::LikeAdded { like_id: LikeId::new(), user_id: uid.clone(), thought_id: tid.clone(), }; let payload = EventPayload::from(&event); let back = DomainEvent::try_from(payload).unwrap(); if let DomainEvent::LikeAdded { user_id, thought_id, .. } = back { assert_eq!(user_id, uid); assert_eq!(thought_id, tid); } else { panic!("wrong variant"); } } }