use core::fmt;
use reliar_core::SerializedEnvelope;
use crate::claim_token::ClaimToken;
use crate::record_id::OutboxRecordId;
use crate::store::{DeadReason, RecordRef};
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, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum OutboxState {
Pending,
Leased,
Published,
Dead,
}
impl fmt::Display for OutboxState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Pending => "pending",
Self::Leased => "leased",
Self::Published => "published",
Self::Dead => "dead",
})
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct OutboxRecord {
pub id: OutboxRecordId,
pub envelope: SerializedEnvelope,
pub created_at: time::OffsetDateTime,
pub ordering_key: Option<String>,
pub attempts: u32,
pub available_at: time::OffsetDateTime,
pub locked_by: Option<WorkerId>,
pub claim_token: Option<ClaimToken>,
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 record_ref(&self) -> RecordRef {
match self.claim_token {
Some(token) => RecordRef::claimed(self.id, self.created_at, token),
None => RecordRef::new(self.id, self.created_at),
}
}
#[must_use]
pub fn state(&self, now: time::OffsetDateTime) -> OutboxState {
if self.dead_at.is_some() {
OutboxState::Dead
} else if self.published_at.is_some() {
OutboxState::Published
} else if self.locked_by.is_some() && self.available_at > now {
OutboxState::Leased
} else {
OutboxState::Pending
}
}
pub fn builder(
id: OutboxRecordId,
envelope: SerializedEnvelope,
created_at: time::OffsetDateTime,
) -> OutboxRecordBuilder {
OutboxRecordBuilder::new(id, envelope, created_at)
}
}
#[must_use]
#[derive(Debug)]
pub struct OutboxRecordBuilder {
id: OutboxRecordId,
envelope: SerializedEnvelope,
created_at: time::OffsetDateTime,
ordering_key: Option<String>,
attempts: u32,
available_at: time::OffsetDateTime,
locked_by: Option<WorkerId>,
claim_token: Option<ClaimToken>,
published_at: Option<time::OffsetDateTime>,
dead_at: Option<time::OffsetDateTime>,
dead_reason: Option<DeadReason>,
last_error: Option<String>,
}
impl OutboxRecordBuilder {
fn new(
id: OutboxRecordId,
envelope: SerializedEnvelope,
created_at: time::OffsetDateTime,
) -> Self {
Self {
id,
envelope,
available_at: created_at,
created_at,
ordering_key: None,
attempts: 0,
locked_by: None,
claim_token: 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 locked_by(mut self, by: Option<WorkerId>) -> Self {
self.locked_by = by;
self
}
pub const fn claim_token(mut self, token: Option<ClaimToken>) -> Self {
self.claim_token = token;
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 {
id: self.id,
envelope: self.envelope,
created_at: self.created_at,
ordering_key: self.ordering_key,
attempts: self.attempts,
available_at: self.available_at,
locked_by: self.locked_by,
claim_token: self.claim_token,
published_at: self.published_at,
dead_at: self.dead_at,
dead_reason: self.dead_reason,
last_error: self.last_error,
}
}
}