use std::time::Duration; use async_nats::{HeaderMap, jetstream}; use config::AppConfig; use serde::Serialize; #[derive(Clone)] pub struct NatsProducer { jetstream: jetstream::Context, } impl NatsProducer { pub async fn new(config: &AppConfig) -> anyhow::Result { let jetstream = connect_jetstream(config).await?; ensure_stream(config, &jetstream).await?; Ok(Self { jetstream }) } pub async fn send( &self, subject: &str, key: &str, payload: &T, headers: Option, ) -> anyhow::Result<()> where T: Serialize + ?Sized, { let payload_bytes = serde_json::to_vec(payload)?; self.send_raw(subject, key, &payload_bytes, headers).await } pub async fn send_raw( &self, subject: &str, key: &str, payload: &[u8], headers: Option, ) -> anyhow::Result<()> { let mut headers = headers.unwrap_or_default(); if !key.is_empty() { headers.append("x-message-key", key); } let subject = subject.to_string(); let publish = if headers.is_empty() { self.jetstream .publish(subject.clone(), payload.to_vec().into()) .await? } else { self.jetstream .publish_with_headers(subject, headers, payload.to_vec().into()) .await? }; tokio::time::timeout(Duration::from_secs(5), publish).await??; Ok(()) } } pub async fn connect_jetstream( config: &AppConfig, ) -> anyhow::Result { let client = match config.nats_token() { Some(token) if !token.is_empty() => { async_nats::ConnectOptions::with_token(token) .connect(config.nats_url()) .await? } _ => async_nats::connect(config.nats_url()).await?, }; Ok(jetstream::new(client)) } pub async fn ensure_stream( config: &AppConfig, jetstream: &jetstream::Context, ) -> anyhow::Result { Ok(jetstream .get_or_create_stream(jetstream::stream::Config { name: config.nats_stream_name(), subjects: config.nats_stream_subjects(), max_age: Duration::from_secs(config.nats_max_age_secs()), ..Default::default() }) .await?) }