Skip to content
Clone
bcbe85c
161 lines 4.8 KB Rust
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<Self> {
let conn = RocketMqConn::init(config).await?;
Ok(Self::new(conn))
}
pub async fn optional(config: Option<&RocketMqConfig>) -> QueueResult<Option<Self>> {
match config {
Some(config) => Self::connect(config).await.map(Some),
None => Ok(None),
}
}
pub fn envelope<T>(&self, kind: &str, payload: T) -> Envelope<T> {
Envelope::new(kind, payload)
}
#[tracing::instrument(skip_all, fields(kind = kind, request_id = %request_id.unwrap_or_default()))]
pub async fn send_labeled<T>(
&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<T>(
&self,
kind: &str,
labels: Labels,
idempotency_key: Option<String>,
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<T>(
&self,
envelope: &Envelope<T>,
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<T: Serialize>(
&self,
subject: &str,
envelope: &Envelope<T>,
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<T: Serialize>(
&self,
envelope: &Envelope<T>,
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()
}