Skip to main content

codex_state/model/
backfill_state.rs

1use anyhow::Result;
2use chrono::DateTime;
3use chrono::Utc;
4use sqlx::Row;
5use sqlx::sqlite::SqliteRow;
6
7/// Persisted lifecycle state for rollout metadata backfill.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct BackfillState {
10    /// Current lifecycle status.
11    pub status: BackfillStatus,
12    /// Last processed rollout watermark.
13    pub last_watermark: Option<String>,
14    /// Last successful completion time.
15    pub last_success_at: Option<DateTime<Utc>>,
16}
17
18impl Default for BackfillState {
19    fn default() -> Self {
20        Self {
21            status: BackfillStatus::Pending,
22            last_watermark: None,
23            last_success_at: None,
24        }
25    }
26}
27
28impl BackfillState {
29    pub(crate) fn try_from_row(row: &SqliteRow) -> Result<Self> {
30        let status: String = row.try_get("status")?;
31        let last_success_at = row
32            .try_get::<Option<i64>, _>("last_success_at")?
33            .map(epoch_seconds_to_datetime)
34            .transpose()?;
35        Ok(Self {
36            status: BackfillStatus::parse(status.as_str())?,
37            last_watermark: row.try_get("last_watermark")?,
38            last_success_at,
39        })
40    }
41}
42
43/// Backfill lifecycle status.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum BackfillStatus {
46    Pending,
47    Running,
48    Complete,
49}
50
51impl BackfillStatus {
52    pub const fn as_str(self) -> &'static str {
53        match self {
54            BackfillStatus::Pending => "pending",
55            BackfillStatus::Running => "running",
56            BackfillStatus::Complete => "complete",
57        }
58    }
59
60    pub fn parse(value: &str) -> Result<Self> {
61        match value {
62            "pending" => Ok(Self::Pending),
63            "running" => Ok(Self::Running),
64            "complete" => Ok(Self::Complete),
65            _ => Err(anyhow::anyhow!("invalid backfill status: {value}")),
66        }
67    }
68}
69
70fn epoch_seconds_to_datetime(secs: i64) -> Result<DateTime<Utc>> {
71    DateTime::<Utc>::from_timestamp(secs, 0)
72        .ok_or_else(|| anyhow::anyhow!("invalid unix timestamp: {secs}"))
73}