solti_model/resource/
metadata.rs1use std::time::SystemTime;
6
7use serde::{Deserialize, Serialize};
8
9use crate::TaskId;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct ObjectMeta {
22 pub id: TaskId,
24 pub resource_version: u64,
26 #[serde(with = "time_serde")]
28 pub created_at: SystemTime,
29 #[serde(with = "time_serde")]
31 pub updated_at: SystemTime,
32}
33
34impl ObjectMeta {
35 pub fn new(id: TaskId) -> Self {
37 let now = SystemTime::now();
38
39 Self {
40 id,
41 resource_version: 1,
42 created_at: now,
43 updated_at: now,
44 }
45 }
46
47 pub fn bump_resource_version(&mut self) {
49 self.updated_at = SystemTime::now();
50 self.resource_version += 1;
51 }
52}
53
54pub(crate) mod time_serde {
55 use serde::{Deserialize, Deserializer, Serialize, Serializer};
56 use std::time::{SystemTime, UNIX_EPOCH};
57
58 pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
59 where
60 S: Serializer,
61 {
62 let since_epoch = time.duration_since(UNIX_EPOCH).unwrap_or_default();
63 let ms = since_epoch.as_secs() * 1_000 + u64::from(since_epoch.subsec_millis());
64 ms.serialize(serializer)
65 }
66
67 pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
68 where
69 D: Deserializer<'de>,
70 {
71 let ms = u64::deserialize(deserializer)?;
72 UNIX_EPOCH
73 .checked_add(std::time::Duration::from_millis(ms))
74 .ok_or_else(|| <D::Error as serde::de::Error>::custom("timestamp out of range"))
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn new_sets_defaults() {
84 let meta = ObjectMeta::new("test-id".into());
85 assert_eq!(meta.id, "test-id");
86 assert_eq!(meta.resource_version, 1);
87 }
88
89 #[test]
90 fn bump_resource_version_increments() {
91 let mut meta = ObjectMeta::new("t".into());
92 meta.bump_resource_version();
93 assert_eq!(meta.resource_version, 2);
94 }
95
96 #[test]
97 fn serde_roundtrip() {
98 let meta = ObjectMeta::new("id-1".into());
99 let json = serde_json::to_string(&meta).unwrap();
100 let back: ObjectMeta = serde_json::from_str(&json).unwrap();
101 assert_eq!(back.id, meta.id);
102 assert_eq!(back.resource_version, 1);
103 }
104
105 #[test]
106 fn serialize_handles_pre_epoch_timestamp() {
107 use std::time::{Duration, UNIX_EPOCH};
108 let mut meta = ObjectMeta::new("pre-epoch".into());
109 meta.created_at = UNIX_EPOCH - Duration::from_secs(10);
110 meta.updated_at = UNIX_EPOCH - Duration::from_secs(5);
111
112 let json = serde_json::to_string(&meta).expect("must not fail");
113 assert!(json.contains(r#""createdAt":0"#));
114 assert!(json.contains(r#""updatedAt":0"#));
115 }
116}