1use crate::error::ErrorKind;
7use serde::{Deserialize, Serialize};
8use time::OffsetDateTime;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
12#[serde(rename_all = "snake_case", tag = "outcome")]
13pub enum SyncOutcome {
14 Updated,
16 NotModified,
18 Failed {
19 kind: ErrorKind,
20 message: String,
21 },
22}
23
24#[derive(Debug, Clone, Default, Serialize, Deserialize)]
25#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
26pub struct SyncState {
27 #[serde(with = "time::serde::rfc3339::option")]
28 #[cfg_attr(feature = "ts", ts(type = "string | null"))]
29 pub last_attempt: Option<OffsetDateTime>,
30 #[serde(with = "time::serde::rfc3339::option")]
31 #[cfg_attr(feature = "ts", ts(type = "string | null"))]
32 pub last_success: Option<OffsetDateTime>,
33 pub last_outcome: Option<SyncOutcome>,
34 pub etag: Option<String>,
36 pub last_modified: Option<String>,
37 pub failure_streak: u32,
39}
40
41impl SyncState {
42 pub fn record(&mut self, at: OffsetDateTime, outcome: SyncOutcome) {
43 self.last_attempt = Some(at);
44 match &outcome {
45 SyncOutcome::Updated | SyncOutcome::NotModified => {
46 self.last_success = Some(at);
47 self.failure_streak = 0;
48 }
49 SyncOutcome::Failed { .. } => {
50 self.failure_streak = self.failure_streak.saturating_add(1);
51 }
52 }
53 self.last_outcome = Some(outcome);
54 }
55
56 pub fn is_failing(&self) -> bool {
57 self.failure_streak > 0
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use time::macros::datetime;
65
66 #[test]
67 fn a_not_modified_response_still_counts_as_success() {
68 let mut state = SyncState::default();
69 state.record(
70 datetime!(2026-08-23 10:00 UTC),
71 SyncOutcome::Failed {
72 kind: ErrorKind::Network,
73 message: "timeout".into(),
74 },
75 );
76 assert_eq!(state.failure_streak, 1);
77
78 state.record(datetime!(2026-08-23 10:05 UTC), SyncOutcome::NotModified);
79
80 assert_eq!(state.failure_streak, 0);
81 assert_eq!(state.last_success, Some(datetime!(2026-08-23 10:05 UTC)));
82 }
83}