use crate::middleware::{EventBus, EventSubscriber, StoreEvent};
use crate::store::{Store, StoreId};
use std::sync::Arc;
struct CoordinationRule {
source_store_id: StoreId,
source_mutation: Option<String>,
handler: Arc<dyn Fn(&StoreEvent) + Send + Sync>,
}
struct CoordinationSubscriber {
source_store_id: StoreId,
source_mutation: Option<String>,
handler: Arc<dyn Fn(&StoreEvent) + Send + Sync>,
}
impl EventSubscriber for CoordinationSubscriber {
fn on_event(&self, event: &StoreEvent) {
(self.handler)(event);
}
fn name(&self) -> &'static str {
"StoreCoordinator"
}
fn filter(&self, event: &StoreEvent) -> bool {
match event {
StoreEvent::MutationCompleted {
store_id,
name,
success,
..
} => {
if *store_id != self.source_store_id || !success {
return false;
}
match &self.source_mutation {
Some(expected) => *name == expected.as_str(),
None => true,
}
}
StoreEvent::StateChanged { store_id, .. } => {
*store_id == self.source_store_id && self.source_mutation.is_none()
}
_ => false,
}
}
}
pub struct StoreCoordinator {
rules: Vec<CoordinationRule>,
event_bus: Arc<EventBus>,
}
impl Default for StoreCoordinator {
fn default() -> Self {
Self::new()
}
}
impl StoreCoordinator {
pub fn new() -> Self {
Self {
rules: Vec::new(),
event_bus: Arc::new(EventBus::new()),
}
}
pub fn with_event_bus(event_bus: Arc<EventBus>) -> Self {
Self {
rules: Vec::new(),
event_bus,
}
}
pub fn on_change<Source: Store, Target: Store>(
&mut self,
source: &Source,
target: &Target,
handler: impl Fn(&Target, &StoreEvent) + Send + Sync + 'static,
) -> &mut Self {
let target = target.clone();
self.rules.push(CoordinationRule {
source_store_id: source.id(),
source_mutation: None,
handler: Arc::new(move |event| {
handler(&target, event);
}),
});
self
}
pub fn on_mutation<Source: Store, Target: Store>(
&mut self,
source: &Source,
mutation_name: &str,
target: &Target,
handler: impl Fn(&Target) + Send + Sync + 'static,
) -> &mut Self {
let target = target.clone();
self.rules.push(CoordinationRule {
source_store_id: source.id(),
source_mutation: Some(mutation_name.to_string()),
handler: Arc::new(move |_event| {
handler(&target);
}),
});
self
}
pub fn activate(&self) {
for rule in &self.rules {
let subscriber = CoordinationSubscriber {
source_store_id: rule.source_store_id,
source_mutation: rule.source_mutation.clone(),
handler: Arc::clone(&rule.handler),
};
self.event_bus.subscribe(subscriber);
}
}
pub fn event_bus(&self) -> &Arc<EventBus> {
&self.event_bus
}
pub fn rule_count(&self) -> usize {
self.rules.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use leptos::prelude::*;
#[derive(Clone, Debug, Default)]
struct SourceStore {
state: RwSignal<i32>,
}
impl Store for SourceStore {
type State = i32;
fn state(&self) -> ReadSignal<Self::State> {
self.state.read_only()
}
}
#[derive(Clone, Debug, Default)]
struct TargetStore {
state: RwSignal<i32>,
}
impl Store for TargetStore {
type State = i32;
fn state(&self) -> ReadSignal<Self::State> {
self.state.read_only()
}
}
fn with_owner<F: FnOnce()>(f: F) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(async {
let owner = Owner::new();
owner.with(f);
});
}
#[test]
fn test_coordinator_rule_count() {
with_owner(|| {
let source = SourceStore::default();
let target = TargetStore::default();
let mut coord = StoreCoordinator::new();
assert_eq!(coord.rule_count(), 0);
coord.on_change(&source, &target, |_t, _e| {});
assert_eq!(coord.rule_count(), 1);
coord.on_mutation(&source, "increment", &target, |_t| {});
assert_eq!(coord.rule_count(), 2);
});
}
#[test]
fn test_coordinator_event_filtering() {
with_owner(|| {
let source = SourceStore::default();
let source_id = source.id();
let subscriber = CoordinationSubscriber {
source_store_id: source_id,
source_mutation: Some("increment".to_string()),
handler: Arc::new(|_| {}),
};
assert!(subscriber.filter(&StoreEvent::MutationCompleted {
store_id: source_id,
name: "increment",
duration_ms: 0,
success: true,
}));
assert!(!subscriber.filter(&StoreEvent::MutationCompleted {
store_id: source_id,
name: "decrement",
duration_ms: 0,
success: true,
}));
assert!(!subscriber.filter(&StoreEvent::MutationCompleted {
store_id: source_id,
name: "increment",
duration_ms: 0,
success: false,
}));
let other_id = StoreId::with_instance::<TargetStore>(999);
assert!(!subscriber.filter(&StoreEvent::MutationCompleted {
store_id: other_id,
name: "increment",
duration_ms: 0,
success: true,
}));
assert!(!subscriber.filter(&StoreEvent::StateChanged {
store_id: source_id,
store_name: "SourceStore",
timestamp: 0,
}));
});
}
#[test]
fn test_coordinator_wildcard_filtering() {
with_owner(|| {
let source = SourceStore::default();
let source_id = source.id();
let subscriber = CoordinationSubscriber {
source_store_id: source_id,
source_mutation: None,
handler: Arc::new(|_| {}),
};
assert!(subscriber.filter(&StoreEvent::MutationCompleted {
store_id: source_id,
name: "increment",
duration_ms: 0,
success: true,
}));
assert!(subscriber.filter(&StoreEvent::MutationCompleted {
store_id: source_id,
name: "decrement",
duration_ms: 0,
success: true,
}));
assert!(!subscriber.filter(&StoreEvent::MutationCompleted {
store_id: source_id,
name: "increment",
duration_ms: 0,
success: false,
}));
assert!(subscriber.filter(&StoreEvent::StateChanged {
store_id: source_id,
store_name: "SourceStore",
timestamp: 0,
}));
let other_id = StoreId::with_instance::<TargetStore>(999);
assert!(!subscriber.filter(&StoreEvent::StateChanged {
store_id: other_id,
store_name: "TargetStore",
timestamp: 0,
}));
assert!(!subscriber.filter(&StoreEvent::MutationStarted {
store_id: source_id,
name: "increment",
timestamp: 0,
}));
});
}
#[test]
fn test_coordinator_activate_registers_subscribers() {
with_owner(|| {
let source = SourceStore::default();
let target = TargetStore::default();
let mut coord = StoreCoordinator::new();
coord.on_change(&source, &target, |_t, _e| {});
coord.on_mutation(&source, "increment", &target, |_t| {});
assert_eq!(coord.event_bus().subscriber_count(), 0);
coord.activate();
assert_eq!(coord.event_bus().subscriber_count(), 2);
});
}
}