1use std::ops::Deref;
4use std::sync::Arc;
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Default, PartialEq)]
15pub struct SharedJson(Arc<serde_json::Value>);
16
17impl SharedJson {
18 #[must_use]
20 pub fn new(value: serde_json::Value) -> Self {
21 Self(Arc::new(value))
22 }
23
24 #[must_use]
26 pub fn as_value(&self) -> &serde_json::Value {
27 self.0.as_ref()
28 }
29
30 pub fn make_mut(&mut self) -> &mut serde_json::Value {
32 Arc::make_mut(&mut self.0)
33 }
34
35 #[must_use]
37 pub fn into_inner(self) -> serde_json::Value {
38 match Arc::try_unwrap(self.0) {
39 Ok(value) => value,
40 Err(value) => value.as_ref().clone(),
41 }
42 }
43}
44
45impl Deref for SharedJson {
46 type Target = serde_json::Value;
47
48 fn deref(&self) -> &Self::Target {
49 self.as_value()
50 }
51}
52
53impl AsRef<serde_json::Value> for SharedJson {
54 fn as_ref(&self) -> &serde_json::Value {
55 self.as_value()
56 }
57}
58
59impl From<serde_json::Value> for SharedJson {
60 fn from(value: serde_json::Value) -> Self {
61 Self::new(value)
62 }
63}
64
65impl From<SharedJson> for serde_json::Value {
66 fn from(value: SharedJson) -> Self {
67 value.into_inner()
68 }
69}
70
71impl PartialEq<serde_json::Value> for SharedJson {
72 fn eq(&self, other: &serde_json::Value) -> bool {
73 self.as_value() == other
74 }
75}
76
77impl Serialize for SharedJson {
78 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
79 where
80 S: Serializer,
81 {
82 self.as_value().serialize(serializer)
83 }
84}
85
86impl<'de> Deserialize<'de> for SharedJson {
87 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 serde_json::Value::deserialize(deserializer).map(Self::new)
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct FoldContext {
101 pub as_of: DateTime<Utc>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub correlation_id: Option<Uuid>,
107
108 #[serde(default)]
112 pub extra: SharedJson,
113}
114
115impl Default for FoldContext {
116 fn default() -> Self {
117 Self {
118 as_of: Utc::now(),
119 correlation_id: None,
120 extra: SharedJson::default(),
121 }
122 }
123}
124
125impl FoldContext {
126 pub fn new() -> Self {
128 Self::default()
129 }
130
131 pub fn at(as_of: DateTime<Utc>) -> Self {
133 Self {
134 as_of,
135 ..Default::default()
136 }
137 }
138
139 pub fn with_correlation_id(mut self, id: Uuid) -> Self {
141 self.correlation_id = Some(id);
142 self
143 }
144
145 pub fn with_extra(mut self, extra: impl Into<SharedJson>) -> Self {
147 self.extra = extra.into();
148 self
149 }
150
151 #[must_use]
153 pub fn extra(&self) -> &serde_json::Value {
154 self.extra.as_value()
155 }
156
157 pub fn extra_mut(&mut self) -> &mut serde_json::Value {
159 self.extra.make_mut()
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_context_at_time() {
169 let past = Utc::now() - chrono::Duration::hours(1);
170 let ctx = FoldContext::at(past);
171 assert_eq!(ctx.as_of, past);
172 }
173
174 #[test]
175 fn test_shared_json_round_trip() {
176 let ctx = FoldContext::new().with_extra(serde_json::json!({"count": 3, "flag": true}));
177 let encoded = serde_json::to_string(&ctx).unwrap();
178 let decoded: FoldContext = serde_json::from_str(&encoded).unwrap();
179 assert_eq!(decoded.extra, serde_json::json!({"count": 3, "flag": true}));
180 }
181
182 #[test]
183 fn test_shared_json_make_mut() {
184 let mut ctx = FoldContext::new().with_extra(serde_json::json!({"count": 1}));
185 let _clone = ctx.clone();
186 *ctx.extra_mut() = serde_json::json!({"count": 2});
187 assert_eq!(ctx.extra, serde_json::json!({"count": 2}));
188 }
189
190 #[test]
191 fn test_shared_json_clone_is_cheap_arc_refcount() {
192 let value = serde_json::json!({"large": "payload", "nested": {"a": 1, "b": 2}});
193 let shared = SharedJson::new(value.clone());
194 let clone = shared.clone();
195 assert_eq!(
196 shared.as_value() as *const _,
197 clone.as_value() as *const _,
198 "clone should share the same Arc allocation"
199 );
200 assert_eq!(*shared, *clone);
201 drop(clone);
202 let extracted = shared.into_inner();
203 assert_eq!(extracted, value);
204 }
205
206 #[test]
207 fn test_shared_json_extra_mut_creates_independent_copy() {
208 let original = FoldContext::new().with_extra(serde_json::json!({"x": 1}));
209 let mut mutated = original.clone();
210 assert_eq!(original.extra(), mutated.extra());
211 *mutated.extra_mut() = serde_json::json!({"x": 99});
212 assert_eq!(original.extra(), &serde_json::json!({"x": 1}));
213 assert_eq!(mutated.extra(), &serde_json::json!({"x": 99}));
214 }
215
216 #[test]
217 fn test_shared_json_from_value_transparent() {
218 let value = serde_json::json!([1, 2, 3]);
219 let shared: SharedJson = value.clone().into();
220 assert_eq!(shared.as_value(), &value);
221 }
222
223 #[test]
224 fn test_shared_json_default_is_null() {
225 let default = SharedJson::default();
226 assert_eq!(default.as_value(), &serde_json::Value::Null);
227 }
228}