ridewithgps_client/
sync.rs

1//! Sync-related types and methods
2
3use crate::{Result, RideWithGpsClient};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7/// Types of items that can be synchronized
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
9#[serde(rename_all = "lowercase")]
10pub enum ItemType {
11    /// Route
12    Route,
13
14    /// Trip
15    Trip,
16
17    /// Event
18    Event,
19
20    /// Collection
21    Collection,
22}
23
24/// A synchronized item
25#[derive(Debug, Clone, Deserialize, Serialize)]
26pub struct SyncItem {
27    /// Item ID
28    pub id: u64,
29
30    /// Item type
31    pub item_type: ItemType,
32
33    /// When the item was updated
34    pub updated_at: DateTime<Utc>,
35
36    /// Whether the item was deleted
37    pub deleted: Option<bool>,
38}
39
40/// Response from the sync endpoint
41#[derive(Debug, Clone, Deserialize, Serialize)]
42pub struct SyncResponse {
43    /// List of changed items
44    pub items: Vec<SyncItem>,
45
46    /// Server datetime for use in next sync request
47    pub server_datetime: DateTime<Utc>,
48}
49
50impl RideWithGpsClient {
51    /// Get items that have changed since a specific datetime
52    ///
53    /// This endpoint is useful for efficiently synchronizing a local library
54    /// with the server by only fetching items that have changed.
55    ///
56    /// # Arguments
57    ///
58    /// * `since` - DateTime since which to fetch changes
59    ///
60    /// # Example
61    ///
62    /// ```rust,no_run
63    /// use ridewithgps_client::RideWithGpsClient;
64    /// use chrono::{Utc, TimeZone};
65    ///
66    /// let client = RideWithGpsClient::new(
67    ///     "https://ridewithgps.com",
68    ///     "your-api-key",
69    ///     Some("your-auth-token")
70    /// );
71    ///
72    /// // Get all changes since January 1, 2025
73    /// let since = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
74    /// let sync = client.sync(&since).unwrap();
75    ///
76    /// println!("Found {} changed items", sync.items.len());
77    /// for item in sync.items {
78    ///     println!("{:?} {} updated at {}",
79    ///         item.item_type, item.id, item.updated_at);
80    /// }
81    ///
82    /// // Use server_datetime for next sync
83    /// let next_sync = client.sync(&sync.server_datetime).unwrap();
84    /// ```
85    pub fn sync(&self, since: &DateTime<Utc>) -> Result<SyncResponse> {
86        let since_str = since.to_rfc3339();
87        let url = format!(
88            "/api/v1/sync.json?since={}",
89            urlencoding::encode(&since_str)
90        );
91        self.get(&url)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use chrono::TimeZone;
99
100    #[test]
101    fn test_sync_item_deserialization() {
102        let json = r#"{
103            "id": 123,
104            "item_type": "route",
105            "updated_at": "2025-01-15T10:30:00Z",
106            "deleted": false
107        }"#;
108
109        let item: SyncItem = serde_json::from_str(json).unwrap();
110        assert_eq!(item.id, 123);
111        assert_eq!(item.item_type, ItemType::Route);
112        assert_eq!(item.deleted, Some(false));
113    }
114
115    #[test]
116    fn test_sync_response_deserialization() {
117        let json = r#"{
118            "items": [
119                {
120                    "id": 123,
121                    "item_type": "route",
122                    "updated_at": "2025-01-15T10:30:00Z",
123                    "deleted": false
124                },
125                {
126                    "id": 456,
127                    "item_type": "trip",
128                    "updated_at": "2025-01-15T11:00:00Z",
129                    "deleted": true
130                }
131            ],
132            "server_datetime": "2025-01-15T12:00:00Z"
133        }"#;
134
135        let response: SyncResponse = serde_json::from_str(json).unwrap();
136        assert_eq!(response.items.len(), 2);
137        assert_eq!(response.items[0].item_type, ItemType::Route);
138        assert_eq!(response.items[1].item_type, ItemType::Trip);
139        assert_eq!(
140            response.server_datetime,
141            Utc.with_ymd_and_hms(2025, 1, 15, 12, 0, 0).unwrap()
142        );
143    }
144
145    #[test]
146    fn test_item_type_serialization() {
147        assert_eq!(
148            serde_json::to_string(&ItemType::Route).unwrap(),
149            r#""route""#
150        );
151        assert_eq!(serde_json::to_string(&ItemType::Trip).unwrap(), r#""trip""#);
152    }
153}