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