use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::{
Account, Budget, Company, Country, Instrument, Merchant, Reminder, ReminderMarker, Tag,
Transaction, User,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Deletion {
pub id: String,
pub object: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub stamp: DateTime<Utc>,
pub user: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DiffRequest {
#[serde(with = "chrono::serde::ts_seconds")]
pub current_client_timestamp: DateTime<Utc>,
#[serde(with = "chrono::serde::ts_seconds")]
pub server_timestamp: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub force_fetch: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub account: Vec<Account>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tag: Vec<Tag>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub merchant: Vec<Merchant>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transaction: Vec<Transaction>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reminder: Vec<Reminder>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reminder_marker: Vec<ReminderMarker>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub budget: Vec<Budget>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deletion: Vec<Deletion>,
}
impl DiffRequest {
#[inline]
#[must_use]
pub const fn sync_only(
server_timestamp: DateTime<Utc>,
current_client_timestamp: DateTime<Utc>,
) -> Self {
Self {
current_client_timestamp,
server_timestamp,
force_fetch: Vec::new(),
account: Vec::new(),
tag: Vec::new(),
merchant: Vec::new(),
transaction: Vec::new(),
reminder: Vec::new(),
reminder_marker: Vec::new(),
budget: Vec::new(),
deletion: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DiffResponse {
#[serde(with = "chrono::serde::ts_seconds")]
pub server_timestamp: DateTime<Utc>,
#[serde(default)]
pub instrument: Vec<Instrument>,
#[serde(default)]
pub country: Vec<Country>,
#[serde(default)]
pub company: Vec<Company>,
#[serde(default)]
pub user: Vec<User>,
#[serde(default)]
pub account: Vec<Account>,
#[serde(default)]
pub tag: Vec<Tag>,
#[serde(default)]
pub merchant: Vec<Merchant>,
#[serde(default)]
pub transaction: Vec<Transaction>,
#[serde(default)]
pub reminder: Vec<Reminder>,
#[serde(default)]
pub reminder_marker: Vec<ReminderMarker>,
#[serde(default)]
pub budget: Vec<Budget>,
#[serde(default)]
pub deletion: Vec<Deletion>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_sync_only_request() {
let req = DiffRequest::sync_only(
DateTime::UNIX_EPOCH,
DateTime::from_timestamp(1_700_000_000, 0).unwrap(),
);
let json = serde_json::to_value(&req).unwrap();
assert_eq!(json["currentClientTimestamp"], 1_700_000_000);
assert_eq!(json["serverTimestamp"], 0);
assert!(json.get("account").is_none());
assert!(json.get("transaction").is_none());
assert!(json.get("deletion").is_none());
}
#[test]
fn deserialize_diff_response_minimal() {
let json = r#"{
"serverTimestamp": 1700000100
}"#;
let resp: DiffResponse = serde_json::from_str(json).unwrap();
assert_eq!(
resp.server_timestamp,
DateTime::from_timestamp(1_700_000_100, 0).unwrap()
);
assert!(resp.instrument.is_empty());
assert!(resp.transaction.is_empty());
}
#[test]
fn deserialize_diff_response_with_entities() {
let json = r#"{
"serverTimestamp": 1700000100,
"instrument": [
{
"id": 1,
"changed": 1700000000,
"title": "Russian Ruble",
"shortTitle": "RUB",
"symbol": "\u20bd",
"rate": 1.0
}
],
"deletion": [
{
"id": "tx-old",
"object": "transaction",
"stamp": 1700000050,
"user": 123
}
]
}"#;
let resp: DiffResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.instrument.len(), 1);
assert_eq!(resp.deletion.len(), 1);
assert_eq!(resp.deletion[0].object, "transaction");
}
#[test]
fn deletion_serde_roundtrip() {
let deletion = Deletion {
id: "some-id".to_owned(),
object: "account".to_owned(),
stamp: DateTime::from_timestamp(1_700_000_000, 0).unwrap(),
user: 123,
};
let json = serde_json::to_string(&deletion).unwrap();
let deserialized: Deletion = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, deletion);
}
#[test]
fn diff_request_roundtrip() {
let req = DiffRequest::sync_only(
DateTime::from_timestamp(100, 0).unwrap(),
DateTime::from_timestamp(200, 0).unwrap(),
);
let json = serde_json::to_string(&req).unwrap();
let deserialized: DiffRequest = serde_json::from_str(&json).unwrap();
assert_eq!(
deserialized.server_timestamp,
DateTime::from_timestamp(100, 0).unwrap()
);
assert_eq!(
deserialized.current_client_timestamp,
DateTime::from_timestamp(200, 0).unwrap()
);
}
}