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