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 Ok(UNIX_EPOCH + std::time::Duration::from_millis(ms))
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn new_sets_defaults() {
82 let meta = ObjectMeta::new("test-id".into());
83 assert_eq!(meta.id, "test-id");
84 assert_eq!(meta.resource_version, 1);
85 }
86
87 #[test]
88 fn bump_resource_version_increments() {
89 let mut meta = ObjectMeta::new("t".into());
90 meta.bump_resource_version();
91 assert_eq!(meta.resource_version, 2);
92 }
93
94 #[test]
95 fn serde_roundtrip() {
96 let meta = ObjectMeta::new("id-1".into());
97 let json = serde_json::to_string(&meta).unwrap();
98 let back: ObjectMeta = serde_json::from_str(&json).unwrap();
99 assert_eq!(back.id, meta.id);
100 assert_eq!(back.resource_version, 1);
101 }
102
103 #[test]
104 fn serialize_handles_pre_epoch_timestamp() {
105 use std::time::{Duration, UNIX_EPOCH};
106 let mut meta = ObjectMeta::new("pre-epoch".into());
107 meta.created_at = UNIX_EPOCH - Duration::from_secs(10);
108 meta.updated_at = UNIX_EPOCH - Duration::from_secs(5);
109
110 let json = serde_json::to_string(&meta).expect("must not fail");
111 assert!(json.contains(r#""createdAt":0"#));
112 assert!(json.contains(r#""updatedAt":0"#));
113 }
114}