use chrono::Utc;
use crate::error::{PhotonError, Result};
use crate::instrumentation::{dlq_fields, record_handler_failure, FailureReason};
use photon_telemetry::ops_log;
#[derive(Debug, Clone)]
pub struct DlqRecord {
pub event_id: String,
pub topic_name: String,
pub topic_key: Option<String>,
pub seq: i64,
pub subscription_name: Option<String>,
pub error: String,
pub attempt: u32,
pub recorded_at: chrono::DateTime<Utc>,
}
pub struct DlqRecordParams<'a> {
pub event_id: &'a str,
pub topic_name: &'a str,
pub topic_key: Option<&'a str>,
pub seq: i64,
pub subscription_name: Option<&'a str>,
pub reason: FailureReason,
pub error: String,
}
#[derive(Default)]
pub struct DlqSink {
records: std::sync::Mutex<Vec<DlqRecord>>,
}
impl DlqSink {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn record(&self, params: &DlqRecordParams<'_>) -> Result<()> {
record_handler_failure(params.topic_name, params.reason);
{
let mut guard = self
.records
.lock()
.map_err(|_| PhotonError::Internal("dlq lock poisoned".into()))?;
guard.push(DlqRecord {
event_id: params.event_id.to_string(),
topic_name: params.topic_name.to_string(),
topic_key: params.topic_key.map(String::from),
seq: params.seq,
subscription_name: params.subscription_name.map(String::from),
error: params.error.clone(),
attempt: 1,
recorded_at: Utc::now(),
});
}
ops_log().log_event(
"photon_dlq",
&dlq_fields(
params.event_id,
params.topic_name,
params.topic_key,
params.seq,
params.subscription_name,
params.reason,
¶ms.error,
),
);
Ok(())
}
pub fn len(&self) -> usize {
self.records.lock().map_or(0, |g| g.len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn min_seq_for(&self, topic: &str, topic_key: Option<&str>) -> Option<i64> {
self.records
.lock()
.ok()
.and_then(|guard| {
guard
.iter()
.filter(|r| r.topic_name == topic && r.topic_key.as_deref() == topic_key)
.map(|r| r.seq)
.min()
})
}
}