Skip to main content

origin_sync/
source.rs

1use crate::SyncTarget;
2use async_trait::async_trait;
3use origin_domain::{Result, SyncId, SyncState};
4use std::fmt::Debug;
5use tokio_util::sync::CancellationToken;
6
7/// What one successful sync produced.
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
9pub struct SyncReport {
10    /// How many records changed. Used for the "3 new notifications" kind of message.
11    pub changed: u64,
12    /// Validator to send on the next request, if the service issued one.
13    pub etag: Option<String>,
14    pub last_modified: Option<String>,
15}
16
17impl SyncReport {
18    pub fn changed(changed: u64) -> Self {
19        Self {
20            changed,
21            ..Self::default()
22        }
23    }
24
25    pub fn with_etag(mut self, etag: impl Into<String>) -> Self {
26        self.etag = Some(etag.into());
27        self
28    }
29
30    pub fn with_last_modified(mut self, last_modified: impl Into<String>) -> Self {
31        self.last_modified = Some(last_modified.into());
32        self
33    }
34}
35
36/// The outcome a source reports.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum SyncResult {
39    Updated(SyncReport),
40    /// The service confirmed nothing changed — a validator hit. Counts as success.
41    NotModified,
42}
43
44/// Everything a source is given for one run.
45#[derive(Debug)]
46pub struct SyncContext {
47    pub sync_id: SyncId,
48    pub target: SyncTarget,
49    /// State from the previous run: validators, failure streak, timestamps.
50    pub state: SyncState,
51    cancel: CancellationToken,
52}
53
54impl SyncContext {
55    pub(crate) fn new(
56        sync_id: SyncId,
57        target: SyncTarget,
58        state: SyncState,
59        cancel: CancellationToken,
60    ) -> Self {
61        Self {
62            sync_id,
63            target,
64            state,
65            cancel,
66        }
67    }
68
69    /// Validator from the last successful run, to be sent as `If-None-Match`.
70    pub fn etag(&self) -> Option<&str> {
71        self.state.etag.as_deref()
72    }
73
74    /// Validator from the last successful run, to be sent as `If-Modified-Since`.
75    pub fn last_modified(&self) -> Option<&str> {
76        self.state.last_modified.as_deref()
77    }
78
79    /// Check between pages of a paginated fetch.
80    pub fn is_cancelled(&self) -> bool {
81        self.cancel.is_cancelled()
82    }
83
84    pub async fn cancelled(&self) {
85        self.cancel.cancelled().await;
86    }
87}
88
89/// How a particular kind of data is fetched.
90///
91/// Implementations answer *how*, never *when*. They do not sleep, do not retry and do
92/// not decide whether the machine is online — the engine owns all of that.
93#[async_trait]
94pub trait SyncSource: Debug + Send + Sync + 'static {
95    async fn sync(&self, context: &SyncContext) -> Result<SyncResult>;
96}