use std::ops::Deref;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::Uuid;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct SharedJson(Arc<serde_json::Value>);
impl SharedJson {
#[must_use]
pub fn new(value: serde_json::Value) -> Self {
Self(Arc::new(value))
}
#[must_use]
pub fn as_value(&self) -> &serde_json::Value {
self.0.as_ref()
}
pub fn make_mut(&mut self) -> &mut serde_json::Value {
Arc::make_mut(&mut self.0)
}
#[must_use]
pub fn into_inner(self) -> serde_json::Value {
match Arc::try_unwrap(self.0) {
Ok(value) => value,
Err(value) => value.as_ref().clone(),
}
}
}
impl Deref for SharedJson {
type Target = serde_json::Value;
fn deref(&self) -> &Self::Target {
self.as_value()
}
}
impl AsRef<serde_json::Value> for SharedJson {
fn as_ref(&self) -> &serde_json::Value {
self.as_value()
}
}
impl From<serde_json::Value> for SharedJson {
fn from(value: serde_json::Value) -> Self {
Self::new(value)
}
}
impl From<SharedJson> for serde_json::Value {
fn from(value: SharedJson) -> Self {
value.into_inner()
}
}
impl PartialEq<serde_json::Value> for SharedJson {
fn eq(&self, other: &serde_json::Value) -> bool {
self.as_value() == other
}
}
impl Serialize for SharedJson {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.as_value().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for SharedJson {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
serde_json::Value::deserialize(deserializer).map(Self::new)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FoldContext {
pub as_of: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<Uuid>,
#[serde(default)]
pub extra: SharedJson,
}
impl Default for FoldContext {
fn default() -> Self {
Self {
as_of: Utc::now(),
correlation_id: None,
extra: SharedJson::default(),
}
}
}
impl FoldContext {
pub fn new() -> Self {
Self::default()
}
pub fn at(as_of: DateTime<Utc>) -> Self {
Self {
as_of,
..Default::default()
}
}
pub fn with_correlation_id(mut self, id: Uuid) -> Self {
self.correlation_id = Some(id);
self
}
pub fn with_extra(mut self, extra: impl Into<SharedJson>) -> Self {
self.extra = extra.into();
self
}
#[must_use]
pub fn extra(&self) -> &serde_json::Value {
self.extra.as_value()
}
pub fn extra_mut(&mut self) -> &mut serde_json::Value {
self.extra.make_mut()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_at_time() {
let past = Utc::now() - chrono::Duration::hours(1);
let ctx = FoldContext::at(past);
assert_eq!(ctx.as_of, past);
}
#[test]
fn test_shared_json_round_trip() {
let ctx = FoldContext::new().with_extra(serde_json::json!({"count": 3, "flag": true}));
let encoded = serde_json::to_string(&ctx).unwrap();
let decoded: FoldContext = serde_json::from_str(&encoded).unwrap();
assert_eq!(decoded.extra, serde_json::json!({"count": 3, "flag": true}));
}
#[test]
fn test_shared_json_make_mut() {
let mut ctx = FoldContext::new().with_extra(serde_json::json!({"count": 1}));
let _clone = ctx.clone();
*ctx.extra_mut() = serde_json::json!({"count": 2});
assert_eq!(ctx.extra, serde_json::json!({"count": 2}));
}
#[test]
fn test_shared_json_clone_is_cheap_arc_refcount() {
let value = serde_json::json!({"large": "payload", "nested": {"a": 1, "b": 2}});
let shared = SharedJson::new(value.clone());
let clone = shared.clone();
assert_eq!(
shared.as_value() as *const _,
clone.as_value() as *const _,
"clone should share the same Arc allocation"
);
assert_eq!(*shared, *clone);
drop(clone);
let extracted = shared.into_inner();
assert_eq!(extracted, value);
}
#[test]
fn test_shared_json_extra_mut_creates_independent_copy() {
let original = FoldContext::new().with_extra(serde_json::json!({"x": 1}));
let mut mutated = original.clone();
assert_eq!(original.extra(), mutated.extra());
*mutated.extra_mut() = serde_json::json!({"x": 99});
assert_eq!(original.extra(), &serde_json::json!({"x": 1}));
assert_eq!(mutated.extra(), &serde_json::json!({"x": 99}));
}
#[test]
fn test_shared_json_from_value_transparent() {
let value = serde_json::json!([1, 2, 3]);
let shared: SharedJson = value.clone().into();
assert_eq!(shared.as_value(), &value);
}
#[test]
fn test_shared_json_default_is_null() {
let default = SharedJson::default();
assert_eq!(default.as_value(), &serde_json::Value::Null);
}
}