use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt, hash, ops::Deref};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Forgettable<T>(Option<T>);
impl<T> Default for Forgettable<T> {
fn default() -> Self {
Forgettable(None)
}
}
impl<T> From<T> for Forgettable<T> {
fn from(value: T) -> Self {
Forgettable(Some(value))
}
}
impl<T> Forgettable<T> {
pub fn new(value: T) -> Self {
Forgettable(Some(value))
}
pub fn forgotten() -> Self {
Forgettable(None)
}
pub fn value(&self) -> Option<ForgettableRef<'_, T>> {
self.0.as_ref().map(ForgettableRef)
}
pub fn is_set(&self) -> bool {
self.0.is_some()
}
pub fn is_forgotten(&self) -> bool {
self.0.is_none()
}
}
impl<T: Serialize> Forgettable<T> {
#[doc(hidden)]
pub fn __extract_payload_value(&self) -> Option<serde_json::Value> {
self.0
.as_ref()
.map(|v| serde_json::to_value(v).expect("Failed to serialize forgettable field"))
}
}
impl<T: Serialize> Serialize for Forgettable<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_none()
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Forgettable<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Option::<T>::deserialize(deserializer)?;
match value {
Some(v) => Ok(Forgettable(Some(v))),
None => Ok(Forgettable(None)),
}
}
}
#[cfg(feature = "json-schema")]
impl<T: schemars::JsonSchema> schemars::JsonSchema for Forgettable<T> {
fn inline_schema() -> bool {
Option::<T>::inline_schema()
}
fn schema_name() -> std::borrow::Cow<'static, str> {
Option::<T>::schema_name()
}
fn schema_id() -> std::borrow::Cow<'static, str> {
Option::<T>::schema_id()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
Option::<T>::json_schema(generator)
}
}
pub struct ForgettableRef<'a, T>(&'a T);
impl<T: fmt::Debug> fmt::Debug for ForgettableRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for ForgettableRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T> Deref for ForgettableRef<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.0
}
}
impl<T: PartialEq> PartialEq<T> for ForgettableRef<'_, T> {
fn eq(&self, other: &T) -> bool {
self.0 == other
}
}
impl<T: PartialEq> PartialEq for ForgettableRef<'_, T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T: Eq> Eq for ForgettableRef<'_, T> {}
impl<T: hash::Hash> hash::Hash for ForgettableRef<'_, T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[doc(hidden)]
pub fn inject_forgettable_payload(event_json: &mut serde_json::Value, payload: serde_json::Value) {
if let (Some(event_obj), serde_json::Value::Object(payload_obj)) =
(event_json.as_object_mut(), payload)
{
for (key, value) in payload_obj {
event_obj.insert(key, value);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ForgettableRemnants {
pub payload_rows: usize,
pub live_index_columns: Vec<&'static str>,
pub event_fields: Vec<(String, String)>,
}
impl ForgettableRemnants {
pub fn is_empty(&self) -> bool {
self.payload_rows == 0 && self.live_index_columns.is_empty() && self.event_fields.is_empty()
}
}
impl fmt::Display for ForgettableRemnants {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"forgettable data still present at storage level: {} payload row(s), non-NULL index columns {:?}, non-null event fields {:?}",
self.payload_rows, self.live_index_columns, self.event_fields
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn json_value() -> impl Strategy<Value = serde_json::Value> {
let scalar = prop_oneof![
Just(serde_json::Value::Null),
any::<bool>().prop_map(serde_json::Value::Bool),
any::<i64>().prop_map(serde_json::Value::from),
any::<f64>().prop_map(serde_json::Value::from),
".{0,15}".prop_map(serde_json::Value::String),
]
.boxed();
let nested = prop_oneof![
proptest::collection::vec(scalar.clone(), 0..4).prop_map(serde_json::Value::Array),
proptest::collection::vec((".{0,6}", scalar.clone()), 0..4).prop_map(|pairs| {
let mut m = serde_json::Map::new();
for (k, v) in pairs {
m.insert(k, v);
}
serde_json::Value::Object(m)
},),
];
prop_oneof![scalar, nested]
}
#[test]
fn serialize_set_emits_null() {
let value: Forgettable<String> = Forgettable::new("Alice".to_string());
let json = serde_json::to_value(&value).unwrap();
assert_eq!(json, serde_json::json!(null));
}
#[test]
fn serialize_forgotten_emits_null() {
let value: Forgettable<String> = Forgettable::forgotten();
let json = serde_json::to_value(&value).unwrap();
assert_eq!(json, serde_json::json!(null));
}
#[test]
fn deserialize_value() {
let json = serde_json::json!("Alice");
let value: Forgettable<String> = serde_json::from_value(json).unwrap();
assert_eq!(value, Forgettable::new("Alice".to_string()));
}
#[test]
fn deserialize_null() {
let json = serde_json::json!(null);
let value: Forgettable<String> = serde_json::from_value(json).unwrap();
assert_eq!(value, Forgettable::forgotten());
}
#[test]
fn serialize_struct_with_forgettable_emits_null() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Event {
#[serde(rename = "type")]
kind: String,
name: Forgettable<String>,
email: String,
}
let event = Event {
kind: "initialized".to_string(),
name: Forgettable::new("Alice".to_string()),
email: "alice@test.com".to_string(),
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["name"], serde_json::json!(null));
assert_eq!(json["email"], serde_json::json!("alice@test.com"));
let deserialized: Event = serde_json::from_value(json).unwrap();
assert_eq!(deserialized.name, Forgettable::forgotten());
let event_forgotten = Event {
kind: "initialized".to_string(),
name: Forgettable::forgotten(),
email: "alice@test.com".to_string(),
};
let json = serde_json::to_value(&event_forgotten).unwrap();
assert_eq!(json["name"], serde_json::json!(null));
let deserialized: Event = serde_json::from_value(json).unwrap();
assert_eq!(deserialized, event_forgotten);
}
#[test]
fn inject_payload() {
let mut json = serde_json::json!({
"type": "initialized",
"id": "uuid",
"name": null,
"email": "alice@test.com"
});
let payload = serde_json::json!({"name": "Alice"});
inject_forgettable_payload(&mut json, payload);
assert_eq!(json["name"], serde_json::json!("Alice"));
assert_eq!(json["email"], serde_json::json!("alice@test.com"));
}
#[test]
fn value_helpers() {
let set: Forgettable<String> = Forgettable::new("test".to_string());
assert!(set.is_set());
assert!(!set.is_forgotten());
assert_eq!(&*set.value().unwrap(), "test");
let forgotten: Forgettable<String> = Forgettable::forgotten();
assert!(!forgotten.is_set());
assert!(forgotten.is_forgotten());
assert!(forgotten.value().is_none());
}
#[test]
fn extract_payload_value() {
let set: Forgettable<String> = Forgettable::new("Alice".to_string());
assert_eq!(
set.__extract_payload_value(),
Some(serde_json::json!("Alice"))
);
let forgotten: Forgettable<String> = Forgettable::forgotten();
assert_eq!(forgotten.__extract_payload_value(), None);
}
#[test]
fn forgettable_ref_deref() {
let f = Forgettable::new("hello".to_string());
let r = f.value().unwrap();
assert_eq!(r.len(), 5);
assert_eq!(&*r, "hello");
}
#[test]
fn forgettable_ref_display() {
let f = Forgettable::new("Alice".to_string());
let r = f.value().unwrap();
assert_eq!(format!("{r}"), "Alice");
}
#[test]
fn forgettable_ref_partial_eq() {
let f = Forgettable::new("Alice".to_string());
let r = f.value().unwrap();
assert_eq!(r, "Alice".to_string());
}
#[test]
fn default_is_forgotten() {
let f: Forgettable<String> = Default::default();
assert!(f.is_forgotten());
assert!(f.value().is_none());
}
#[test]
fn from_value() {
let f: Forgettable<String> = "Alice".to_string().into();
assert!(f.is_set());
assert_eq!(&*f.value().unwrap(), "Alice");
}
proptest! {
#[test]
fn inject_never_panics_and_merges_only_objects(
event_in in json_value(),
payload in json_value(),
) {
let mut event = event_in.clone();
inject_forgettable_payload(&mut event, payload.clone());
if event_in.is_object() && payload.is_object() {
let payload_obj = payload.as_object().unwrap();
for (k, v) in payload_obj {
prop_assert_eq!(event.get(k), Some(v));
}
for (k, v) in event_in.as_object().unwrap() {
if !payload_obj.contains_key(k) {
prop_assert_eq!(event.get(k), Some(v));
}
}
} else {
prop_assert_eq!(event, event_in);
}
}
#[test]
fn forgettable_always_serializes_to_null(opt in any::<Option<String>>()) {
let v = serde_json::to_value(&opt).expect("serialize option");
let f: Forgettable<String> =
serde_json::from_value(v.clone()).expect("deserialize forgettable");
prop_assert_eq!(f.is_set(), opt.is_some());
prop_assert_eq!(f.is_forgotten(), opt.is_none());
prop_assert_eq!(serde_json::to_value(&f).unwrap(), serde_json::Value::Null);
let from_null: Forgettable<String> =
serde_json::from_value(serde_json::Value::Null).unwrap();
prop_assert!(from_null.is_forgotten());
}
#[test]
fn forgettable_rejects_non_string_value(n in any::<i64>()) {
let res: Result<Forgettable<String>, _> =
serde_json::from_value(serde_json::Value::from(n));
prop_assert!(res.is_err());
}
}
}