use crate::{AcquisitionMetadata, FlowRef, Operation};
use serde_json::Value;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
#[derive(Clone)]
pub struct MaterializationHandler {
materialized: std::sync::Arc<std::sync::Mutex<std::collections::HashSet<String>>>,
}
impl MaterializationHandler {
pub fn new() -> Self {
Self {
materialized: std::sync::Arc::new(std::sync::Mutex::new(
std::collections::HashSet::new(),
)),
}
}
pub fn materialize_as_operation(
&self,
flow_ref: &FlowRef,
content: Vec<u8>,
metadata: &AcquisitionMetadata,
) -> Result<Operation, String> {
let value: Value = serde_json::from_slice(&content).unwrap_or_else(|_| {
Value::String(String::from_utf8_lossy(&content).into_owned())
});
let key = format!("{}:{}", flow_ref.namespace, flow_ref.object_id);
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);
let mut op = Operation::update(
now_ms as u64, metadata.source_peer.id.clone(), 1, key.clone(),
value,
flow_ref.namespace.clone(),
flow_ref.namespace.clone(),
);
if let Some(vector_clock) = &metadata.vector_clock {
op = op.with_vector_clock(vector_clock.clone());
}
let mut materialized = self.materialized.lock().map_err(|e| e.to_string())?;
materialized.insert(key);
Ok(op)
}
pub fn is_materialized(&self, flow_ref: &FlowRef) -> Result<bool, String> {
let key = format!("{}:{}", flow_ref.namespace, flow_ref.object_id);
let materialized = self.materialized.lock().map_err(|e| e.to_string())?;
Ok(materialized.contains(&key))
}
pub fn materialized_count(&self) -> Result<usize, String> {
let materialized = self.materialized.lock().map_err(|e| e.to_string())?;
Ok(materialized.len())
}
}
impl Default for MaterializationHandler {
fn default() -> Self {
Self::new()
}
}
pub trait ReactiveCollectionIntegration {
fn on_content_materialized(&self, operation: &Operation) -> Result<(), String>;
fn affects_collection(&self, namespace: &str) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PeerId;
#[test]
fn test_materialization_handler_creation() {
let handler = MaterializationHandler::new();
assert_eq!(handler.materialized_count().unwrap(), 0);
}
#[test]
fn test_materialize_as_operation() {
let handler = MaterializationHandler::new();
let flow_ref = FlowRef::new("docs", "doc-123");
let content = br#"{"title": "Test Document", "content": "Test content"}"#.to_vec();
let peer = PeerId::new("peer-a");
let metadata = AcquisitionMetadata::new(peer, true);
let op = handler
.materialize_as_operation(&flow_ref, content, &metadata)
.unwrap();
assert_eq!(op.key, "docs:doc-123");
assert_eq!(op.capability, "docs");
assert!(op.value.is_some());
}
#[test]
fn test_materialize_non_json_content() {
let handler = MaterializationHandler::new();
let flow_ref = FlowRef::new("binary", "file-456");
let content = b"raw binary data".to_vec();
let peer = PeerId::new("peer-b");
let metadata = AcquisitionMetadata::new(peer, true);
let op = handler
.materialize_as_operation(&flow_ref, content, &metadata)
.unwrap();
assert_eq!(op.key, "binary:file-456");
assert!(op.value.is_some());
}
#[test]
fn test_is_materialized() {
let handler = MaterializationHandler::new();
let flow_ref = FlowRef::new("docs", "doc-789");
assert!(!handler.is_materialized(&flow_ref).unwrap());
let content = b"content".to_vec();
let peer = PeerId::new("peer-c");
let metadata = AcquisitionMetadata::new(peer, true);
handler
.materialize_as_operation(&flow_ref, content, &metadata)
.unwrap();
assert!(handler.is_materialized(&flow_ref).unwrap());
}
#[test]
fn test_materialized_count() {
let handler = MaterializationHandler::new();
for i in 0..5 {
let flow_ref = FlowRef::new("docs", &format!("doc-{}", i));
let content = b"content".to_vec();
let peer = PeerId::new("peer-x");
let metadata = AcquisitionMetadata::new(peer, true);
handler
.materialize_as_operation(&flow_ref, content, &metadata)
.unwrap();
}
assert_eq!(handler.materialized_count().unwrap(), 5);
}
}