use reliar_core::SerializedEnvelope;
use crate::store::{DeadReason, MessageRef};
use crate::worker::WorkerId;
const MAX_ERROR_LEN: usize = 2048;
const TRUNCATION_MARKER: &str = "…[truncated]";
pub(crate) fn truncate_error(error: impl Into<String>) -> String {
let error = error.into();
if error.len() <= MAX_ERROR_LEN {
return error;
}
let budget = MAX_ERROR_LEN.saturating_sub(TRUNCATION_MARKER.len());
let mut end = budget.min(error.len());
while end > 0 && !error.is_char_boundary(end) {
end -= 1;
}
tracing::debug!(
original_len = error.len(),
truncated_len = end,
"outbox error truncated before persisting"
);
let mut truncated = String::with_capacity(end + TRUNCATION_MARKER.len());
truncated.push_str(&error[..end]);
truncated.push_str(TRUNCATION_MARKER);
truncated
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct OutboxRecord {
pub envelope: SerializedEnvelope,
pub sequence: i64,
pub created_at: time::OffsetDateTime,
pub ordering_key: Option<String>,
pub attempts: u32,
pub available_at: time::OffsetDateTime,
pub locked_by: Option<WorkerId>,
pub locked_until: Option<time::OffsetDateTime>,
pub published_at: Option<time::OffsetDateTime>,
pub dead_at: Option<time::OffsetDateTime>,
pub dead_reason: Option<DeadReason>,
pub last_error: Option<String>,
}
impl OutboxRecord {
#[must_use]
pub fn message_ref(&self) -> MessageRef {
MessageRef::new(self.envelope.id, self.created_at)
}
pub fn builder(
envelope: SerializedEnvelope,
sequence: i64,
created_at: time::OffsetDateTime,
) -> OutboxRecordBuilder {
OutboxRecordBuilder::new(envelope, sequence, created_at)
}
}
#[must_use]
#[derive(Debug)]
pub struct OutboxRecordBuilder {
envelope: SerializedEnvelope,
sequence: i64,
created_at: time::OffsetDateTime,
ordering_key: Option<String>,
attempts: u32,
available_at: time::OffsetDateTime,
locked_by: Option<WorkerId>,
locked_until: Option<time::OffsetDateTime>,
published_at: Option<time::OffsetDateTime>,
dead_at: Option<time::OffsetDateTime>,
dead_reason: Option<DeadReason>,
last_error: Option<String>,
}
impl OutboxRecordBuilder {
fn new(envelope: SerializedEnvelope, sequence: i64, created_at: time::OffsetDateTime) -> Self {
Self {
envelope,
sequence,
available_at: created_at,
created_at,
ordering_key: None,
attempts: 0,
locked_by: None,
locked_until: None,
published_at: None,
dead_at: None,
dead_reason: None,
last_error: None,
}
}
pub fn ordering_key(mut self, key: Option<String>) -> Self {
self.ordering_key = key;
self
}
pub const fn attempts(mut self, attempts: u32) -> Self {
self.attempts = attempts;
self
}
pub const fn available_at(mut self, at: time::OffsetDateTime) -> Self {
self.available_at = at;
self
}
pub fn lease(mut self, by: Option<WorkerId>, until: Option<time::OffsetDateTime>) -> Self {
self.locked_by = by;
self.locked_until = until;
self
}
pub const fn published_at(mut self, at: Option<time::OffsetDateTime>) -> Self {
self.published_at = at;
self
}
pub const fn dead(
mut self,
at: Option<time::OffsetDateTime>,
reason: Option<DeadReason>,
) -> Self {
self.dead_at = at;
self.dead_reason = reason;
self
}
pub fn last_error(mut self, error: Option<String>) -> Self {
self.last_error = error.map(truncate_error);
self
}
#[must_use]
pub fn build(self) -> OutboxRecord {
OutboxRecord {
envelope: self.envelope,
sequence: self.sequence,
created_at: self.created_at,
ordering_key: self.ordering_key,
attempts: self.attempts,
available_at: self.available_at,
locked_by: self.locked_by,
locked_until: self.locked_until,
published_at: self.published_at,
dead_at: self.dead_at,
dead_reason: self.dead_reason,
last_error: self.last_error,
}
}
}