codex_state/model/
backfill_state.rs1use anyhow::Result;
2use chrono::DateTime;
3use chrono::Utc;
4use sqlx::Row;
5use sqlx::sqlite::SqliteRow;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct BackfillState {
10 pub status: BackfillStatus,
12 pub last_watermark: Option<String>,
14 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#[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}