use serde::Serialize; use crate::{label, topic}; use config::rocketmq::RocketMqConfig; use queue::{EVENT_PREFIX, Envelope, Labels, Publisher, QueueError, QueueResult, RocketMqConn}; pub const DURABLE_DOMAINS: &[&str] = &[ "auth", "email", "guild", "chat", "git", "issue", "notify", "organization", "pull", ]; #[derive(Clone, Debug)] pub struct EventHandler { publisher: Publisher, } impl EventHandler { pub fn new(mq: RocketMqConn) -> Self { Self { publisher: Publisher::new(mq), } } pub async fn connect(config: &RocketMqConfig) -> QueueResult { let conn = RocketMqConn::init(config).await?; Ok(Self::new(conn)) } pub async fn optional(config: Option<&RocketMqConfig>) -> QueueResult> { match config { Some(config) => Self::connect(config).await.map(Some), None => Ok(None), } } pub fn envelope(&self, kind: &str, payload: T) -> Envelope { Envelope::new(kind, payload) } #[tracing::instrument(skip_all, fields(kind = kind, request_id = %request_id.unwrap_or_default()))] pub async fn send_labeled( &self, kind: &str, labels: Labels, payload: T, request_id: Option<&str>, ) -> QueueResult<()> where T: Serialize, { self.send_keyed(kind, labels, None, payload, request_id) .await } #[tracing::instrument(skip_all, fields(kind = kind, request_id = %request_id.unwrap_or_default()))] pub async fn send_keyed( &self, kind: &str, labels: Labels, idempotency_key: Option, payload: T, request_id: Option<&str>, ) -> QueueResult<()> where T: Serialize, { let mut envelope = self .envelope(kind, payload) .with_source(domain_of(kind)) .with_labels(labels); if let Some(key) = idempotency_key { envelope = envelope.with_idempotency_key(key); } if let Some(trace_id) = envelope::context::request_id().or_else(|| { request_id .filter(|request_id| !request_id.is_empty()) .map(str::to_owned) }) { envelope = envelope.with_trace_id(trace_id); } self.send_envelope(&envelope, request_id).await } #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))] pub async fn send_envelope( &self, envelope: &Envelope, request_id: Option<&str>, ) -> QueueResult<()> where T: Serialize, { let subject = format!("{EVENT_PREFIX}{}", envelope.kind); envelope.verify_subject(EVENT_PREFIX, &subject)?; self.dispatch(envelope, request_id).await } #[tracing::instrument(skip_all, fields(subject = %subject, request_id = %request_id.unwrap_or_default()))] pub async fn publish_durable( &self, subject: &str, envelope: &Envelope, request_id: Option<&str>, ) -> QueueResult<()> { envelope.verify_event_subject(subject)?; self.dispatch(envelope, request_id).await } pub async fn shutdown(&self) -> QueueResult<()> { self.publisher.conn().shutdown().await; Ok(()) } #[tracing::instrument(skip_all, fields(kind = %envelope.kind, request_id = %request_id.unwrap_or_default()))] async fn dispatch( &self, envelope: &Envelope, request_id: Option<&str>, ) -> QueueResult<()> { ensure_kind(&envelope.kind)?; let payload = serde_json::to_value(&envelope.payload).map_err(QueueError::from)?; let area = topic::of(&envelope.kind); let tag = label::tag(&envelope.kind, Some(&payload)); let request_id = envelope::context::request_id().or_else(|| { request_id .filter(|request_id| !request_id.is_empty()) .map(str::to_owned) }); let properties = envelope.properties(Some(&payload), request_id.as_deref()); let topic = self.publisher.topic(area); let body = envelope.encode()?; self.publisher .publish(&topic, tag, vec![envelope.message_id()], properties, body) .await } } fn ensure_kind(kind: &str) -> QueueResult<()> { let mut parts = kind.split('.'); let domain = parts.next().unwrap_or_default(); let name = parts.next().unwrap_or_default(); let operate = parts.next().unwrap_or_default(); if domain.is_empty() || name.is_empty() || operate.is_empty() { return Err(QueueError::payload(format!( "event kind `{kind}` is not a concrete event" ))); } Ok(()) } fn domain_of(kind: &str) -> &str { kind.split('.').next().unwrap_or_default() }