use std::sync::Arc;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{TS, common::to_value::ToValue, core::item::AnyItem, item::Eventable};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, TS)]
pub enum MEventType {
SET,
DEL,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct MEvent {
pub item: Value,
pub change_type: MEventType,
pub item_type: Arc<str>,
#[serde(default = "utc_now_iso")]
pub created_at: Arc<str>,
#[serde(default = "generate_random_uuid")]
pub tx: Arc<str>,
pub source_id: Option<Arc<str>>,
}
pub fn intern_entity_type(entity_type: &'static str) -> Arc<str> {
static INTERNED: std::sync::OnceLock<dashmap::DashMap<&'static str, Arc<str>>> =
std::sync::OnceLock::new();
INTERNED
.get_or_init(dashmap::DashMap::new)
.entry(entity_type)
.or_insert_with(|| Arc::from(entity_type))
.clone()
}
fn generate_random_uuid() -> Arc<str> {
Arc::from(uuid::Uuid::new_v4().to_string())
}
fn utc_now_iso() -> Arc<str> {
Arc::from(Utc::now().to_rfc3339())
}
impl MEvent {
pub fn from_str_trim(s: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(s)
}
pub fn from_cbor(s: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>> {
ciborium::de::from_reader(s)
}
#[must_use]
pub fn item_json(&self) -> Value {
self.item.clone()
}
pub fn from_item(item: &impl Eventable, change_type: MEventType, source_id: &str) -> Self {
Self {
item: serde_json::to_value(item).unwrap_or_else(|error| {
tracing::error!(%error, "failed to serialize event item");
Value::Null
}),
change_type,
item_type: intern_entity_type(item.entity_type()),
created_at: Arc::from(Utc::now().to_rfc3339()),
tx: Arc::from(uuid::Uuid::new_v4().to_string()),
source_id: Some(Arc::from(source_id)),
}
}
pub fn del(item: &impl Eventable, source_id: &str) -> Self {
Self {
item: serde_json::to_value(item).unwrap_or_else(|error| {
tracing::error!(%error, "failed to serialize deleted event item");
Value::Null
}),
change_type: MEventType::DEL,
item_type: intern_entity_type(item.entity_type()),
created_at: Arc::from(Utc::now().to_rfc3339()),
tx: Arc::from(uuid::Uuid::new_v4().to_string()),
source_id: Some(Arc::from(source_id)),
}
}
pub fn del_from_any(item: &Arc<dyn AnyItem>, source_id: &str) -> Self {
Self {
item: item.to_value(),
change_type: MEventType::DEL,
item_type: intern_entity_type(item.entity_type()),
created_at: Arc::from(Utc::now().to_rfc3339()),
tx: Arc::from(uuid::Uuid::new_v4().to_string()),
source_id: Some(Arc::from(source_id)),
}
}
#[must_use]
pub fn set_from_value(entity_type: &str, value: Value, source_id: &str) -> Self {
Self {
item: value,
change_type: MEventType::SET,
item_type: Arc::from(entity_type),
created_at: Arc::from(Utc::now().to_rfc3339()),
tx: Arc::from(uuid::Uuid::new_v4().to_string()),
source_id: Some(Arc::from(source_id)),
}
}
#[must_use]
pub const fn change_type(&self) -> MEventType {
self.change_type
}
#[must_use]
pub fn item_type(&self) -> String {
self.item_type.to_string()
}
pub fn sanitize_null_bytes(&mut self) {
fn sanitize_string(s: &mut String) {
if s.as_bytes().contains(&0) {
*s = s.replace('\0', "");
}
}
fn sanitize_value(v: &mut Value) {
match v {
Value::String(s) => sanitize_string(s),
Value::Array(arr) => arr.iter_mut().for_each(sanitize_value),
Value::Object(map) => {
let has_bad_key = map.keys().any(|k| k.as_bytes().contains(&0));
if has_bad_key {
*map = std::mem::take(map)
.into_iter()
.map(|(k, mut v)| {
sanitize_value(&mut v);
(k.replace('\0', ""), v)
})
.collect();
} else {
map.values_mut().for_each(sanitize_value);
}
}
_ => {}
}
}
fn sanitize_arc(s: &mut Arc<str>) {
if s.as_bytes().contains(&0) {
*s = Arc::from(s.replace('\0', ""));
}
}
sanitize_arc(&mut self.item_type);
sanitize_arc(&mut self.created_at);
sanitize_arc(&mut self.tx);
if let Some(ref mut sid) = self.source_id {
sanitize_arc(sid);
}
sanitize_value(&mut self.item);
}
}