use aura_core::types::identifiers::AuthorityId;
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
pub type ViewDelta = Box<dyn Any + Send + Sync>;
pub trait ViewDeltaReducer: Send + Sync {
fn handles_type(&self) -> &'static str;
fn reduce_fact(
&self,
binding_type: &str,
binding_data: &[u8],
own_authority: Option<AuthorityId>,
) -> Vec<ViewDelta>;
}
pub trait ComposableDelta: Sized {
type Key: PartialEq;
fn key(&self) -> Self::Key;
fn try_merge(&mut self, other: Self) -> bool;
}
pub fn compact_deltas<T: ComposableDelta + Clone>(deltas: Vec<T>) -> Vec<T> {
let mut output: Vec<T> = Vec::with_capacity(deltas.len());
for delta in deltas {
let key = delta.key();
if let Some(pos) = output.iter().rposition(|existing| existing.key() == key) {
let mut existing = output.remove(pos);
if existing.try_merge(delta.clone()) {
output.insert(pos, existing);
continue;
}
output.insert(pos, existing);
}
output.push(delta);
}
output
}
#[derive(Default)]
pub struct ViewDeltaRegistry {
reducers: HashMap<String, Box<dyn ViewDeltaReducer>>,
}
impl ViewDeltaRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, type_id: &str, reducer: Box<dyn ViewDeltaReducer>) {
self.reducers.insert(type_id.to_string(), reducer);
}
pub fn is_registered(&self, type_id: &str) -> bool {
self.reducers.contains_key(type_id)
}
pub fn get_reducer(&self, type_id: &str) -> Option<&dyn ViewDeltaReducer> {
self.reducers.get(type_id).map(|r| r.as_ref())
}
pub fn reduce(
&self,
binding_type: &str,
binding_data: &[u8],
own_authority: Option<AuthorityId>,
) -> Vec<ViewDelta> {
if let Some(reducer) = self.reducers.get(binding_type) {
reducer.reduce_fact(binding_type, binding_data, own_authority)
} else {
Vec::new()
}
}
pub fn registered_types(&self) -> impl Iterator<Item = &str> {
self.reducers.keys().map(|s| s.as_str())
}
}
impl Debug for ViewDeltaRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ViewDeltaRegistry")
.field(
"registered_types",
&self.reducers.keys().collect::<Vec<_>>(),
)
.finish()
}
}
pub trait IntoViewDelta: Any + Send + Sync + Sized {
fn into_view_delta(self) -> ViewDelta {
Box::new(self)
}
}
impl<T: Any + Send + Sync + Sized> IntoViewDelta for T {}
pub fn downcast_delta<T: 'static>(delta: &ViewDelta) -> Option<&T> {
delta.downcast_ref::<T>()
}
pub fn downcast_delta_owned<T: 'static>(delta: ViewDelta) -> Option<T> {
delta.downcast::<T>().ok().map(|b| *b)
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
enum TestDelta {
ItemAdded { id: String },
ItemRemoved { id: String },
}
impl ComposableDelta for TestDelta {
type Key = String;
fn key(&self) -> Self::Key {
match self {
TestDelta::ItemAdded { id } | TestDelta::ItemRemoved { id } => id.clone(),
}
}
fn try_merge(&mut self, other: Self) -> bool {
match (self, other) {
(TestDelta::ItemAdded { id }, TestDelta::ItemAdded { id: other_id }) => {
*id = other_id;
true
}
(TestDelta::ItemRemoved { id }, TestDelta::ItemRemoved { id: other_id }) => {
*id = other_id;
true
}
_ => false,
}
}
}
struct TestReducer;
impl ViewDeltaReducer for TestReducer {
fn handles_type(&self) -> &'static str {
"test"
}
fn reduce_fact(
&self,
binding_type: &str,
binding_data: &[u8],
_own_authority: Option<AuthorityId>,
) -> Vec<ViewDelta> {
if binding_type != "test" {
return vec![];
}
if let Ok(id) = std::str::from_utf8(binding_data) {
vec![TestDelta::ItemAdded { id: id.to_string() }.into_view_delta()]
} else {
vec![]
}
}
}
#[test]
fn test_compact_deltas_merges_by_key() {
let deltas = vec![
TestDelta::ItemAdded {
id: "a".to_string(),
},
TestDelta::ItemAdded {
id: "a".to_string(),
},
TestDelta::ItemRemoved {
id: "b".to_string(),
},
TestDelta::ItemRemoved {
id: "b".to_string(),
},
];
let compacted = compact_deltas(deltas);
assert_eq!(
compacted,
vec![
TestDelta::ItemAdded {
id: "a".to_string()
},
TestDelta::ItemRemoved {
id: "b".to_string()
},
]
);
}
#[test]
fn test_registry_registration() {
let mut registry = ViewDeltaRegistry::new();
registry.register("test", Box::new(TestReducer));
assert!(registry.is_registered("test"));
assert!(!registry.is_registered("unknown"));
}
#[test]
fn test_registry_reduce() {
let mut registry = ViewDeltaRegistry::new();
registry.register("test", Box::new(TestReducer));
let deltas = registry.reduce("test", b"item123", None);
assert_eq!(deltas.len(), 1);
let delta = downcast_delta::<TestDelta>(&deltas[0]).unwrap();
assert_eq!(
delta,
&TestDelta::ItemAdded {
id: "item123".to_string()
}
);
}
#[test]
fn test_registry_reduce_unknown_type() {
let registry = ViewDeltaRegistry::new();
let deltas = registry.reduce("unknown", b"data", None);
assert!(deltas.is_empty());
}
#[test]
fn test_into_view_delta() {
let delta = TestDelta::ItemRemoved {
id: "xyz".to_string(),
};
let view_delta = delta.clone().into_view_delta();
let recovered = downcast_delta::<TestDelta>(&view_delta).unwrap();
assert_eq!(recovered, &delta);
}
#[test]
fn test_downcast_owned() {
let delta = TestDelta::ItemAdded {
id: "abc".to_string(),
};
let view_delta = delta.clone().into_view_delta();
let recovered = downcast_delta_owned::<TestDelta>(view_delta).unwrap();
assert_eq!(recovered, delta);
}
}