use aws_sdk_sns::types::MessageAttributeValue as SnsAttributeValue;
use ruststream::{OutgoingMessage, PairError, PublishPolicy, Publisher};
use crate::broker::{ConnectedSqsBroker, Core, CoreCell};
use crate::error::{SqsError, sdk_err};
use crate::message::{ENCODING_ATTRIBUTE, PARTITION_KEY_HEADER, encode_attributes, encode_body};
#[derive(Clone)]
pub struct SqsPublisher {
cell: CoreCell,
}
impl std::fmt::Debug for SqsPublisher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SqsPublisher").finish_non_exhaustive()
}
}
impl SqsPublisher {
pub(crate) fn new(cell: CoreCell) -> Self {
Self { cell }
}
fn core(&self) -> Result<&Core, SqsError> {
let core = self.cell.get().ok_or(SqsError::NotConnected)?;
core.ensure_open()?;
Ok(core)
}
}
fn is_fifo(name: &str) -> bool {
name.to_ascii_lowercase().ends_with(".fifo")
}
fn dedup_id() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static SEQ: AtomicU64 = AtomicU64::new(0);
format!(
"rs-{}-{}",
std::process::id(),
SEQ.fetch_add(1, Ordering::Relaxed)
)
}
impl Publisher for SqsPublisher {
type Error = SqsError;
async fn publish(&self, msg: OutgoingMessage<'_>) -> Result<(), Self::Error> {
let core = self.core()?;
let url = core.queue_url(msg.name()).await?;
let (body, base64_marker) = encode_body(msg.payload());
let (attributes, group) = encode_attributes(msg.headers(), base64_marker);
let mut send = core.sqs.send_message().queue_url(&url).message_body(body);
if !attributes.is_empty() {
send = send.set_message_attributes(Some(attributes));
}
if is_fifo(msg.name()) || is_fifo(&url) {
send = send
.message_group_id(group.unwrap_or_else(|| "default".to_owned()))
.message_deduplication_id(dedup_id());
}
send.send()
.await
.map(|_| ())
.map_err(|e| SqsError::Publish {
destination: msg.name().to_owned(),
source: sdk_err(&e),
})
}
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct SqsPublish;
impl PublishPolicy<ConnectedSqsBroker> for SqsPublish {
type Live = SqsPublisher;
async fn pair(self, connected: &ConnectedSqsBroker) -> Result<Self::Live, PairError> {
Ok(connected.publisher())
}
}
#[derive(Clone)]
pub struct SnsPublisher {
cell: CoreCell,
}
impl std::fmt::Debug for SnsPublisher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SnsPublisher").finish_non_exhaustive()
}
}
impl SnsPublisher {
pub(crate) fn new(cell: CoreCell) -> Self {
Self { cell }
}
fn core(&self) -> Result<&Core, SqsError> {
let core = self.cell.get().ok_or(SqsError::NotConnected)?;
core.ensure_open()?;
Ok(core)
}
}
impl Publisher for SnsPublisher {
type Error = SqsError;
async fn publish(&self, msg: OutgoingMessage<'_>) -> Result<(), Self::Error> {
let core = self.core()?;
let arn = core.topic_arn(msg.name()).await?;
let (body, base64_marker) = encode_body(msg.payload());
let mut publish = core.sns.publish().topic_arn(&arn).message(body);
let mut group = None;
for (name, value) in msg.headers().iter() {
let text = String::from_utf8_lossy(value).into_owned();
if name == PARTITION_KEY_HEADER {
group = Some(text);
continue;
}
let attribute = SnsAttributeValue::builder()
.data_type("String")
.string_value(text)
.build();
if let Ok(attribute) = attribute {
publish = publish.message_attributes(name, attribute);
}
}
if base64_marker
&& let Ok(marker) = SnsAttributeValue::builder()
.data_type("String")
.string_value("base64")
.build()
{
publish = publish.message_attributes(ENCODING_ATTRIBUTE, marker);
}
if is_fifo(&arn) {
publish = publish
.message_group_id(group.unwrap_or_else(|| "default".to_owned()))
.message_deduplication_id(dedup_id());
}
publish
.send()
.await
.map(|_| ())
.map_err(|e| SqsError::Publish {
destination: msg.name().to_owned(),
source: sdk_err(&e),
})
}
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct SnsPublish;
impl PublishPolicy<ConnectedSqsBroker> for SnsPublish {
type Live = SnsPublisher;
async fn pair(self, connected: &ConnectedSqsBroker) -> Result<Self::Live, PairError> {
Ok(connected.sns_publisher())
}
}