use std::sync::Arc;
use crate::reducer::DispatchOp;
use crate::store::StoreError;
pub type MiddlewareFn<State, Action> = Arc<
dyn Fn(&State, &Action) -> Result<DispatchOp<State, Action>, StoreError>
+ Send
+ Sync
+ 'static,
>;
pub trait MiddlewareFnFactory<State, Action>
where
State: Send + Sync + Clone,
Action: Send + Sync + Clone + 'static,
{
fn create(&self, inner: MiddlewareFn<State, Action>) -> MiddlewareFn<State, Action>;
}
#[cfg(test)]
mod tests {
use crate::{BackpressurePolicy, DispatchOp, Effect, Reducer, StoreImpl};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use super::*;
struct TestReducer;
impl Reducer<i32, i32> for TestReducer {
fn reduce(&self, _state: &i32, action: &i32) -> DispatchOp<i32, i32> {
DispatchOp::Dispatch(*action, vec![])
}
}
struct TestLoggerMiddleware {
logs: Arc<Mutex<Vec<String>>>,
#[allow(dead_code)]
errors: Arc<Mutex<Vec<StoreError>>>,
}
impl TestLoggerMiddleware {
fn new() -> Self {
Self {
logs: Arc::new(Mutex::new(vec![])),
errors: Arc::new(Mutex::new(vec![])),
}
}
}
impl<State, Action> MiddlewareFnFactory<State, Action> for TestLoggerMiddleware
where
State: Send + Sync + Clone + 'static,
Action: Send + Sync + Clone + std::fmt::Debug + 'static,
{
fn create(&self, inner: MiddlewareFn<State, Action>) -> MiddlewareFn<State, Action> {
let logs_clone = self.logs.clone();
Arc::new(move |state: &State, action: &Action| {
let log = format!("before: - Action: {:?}", action);
println!("{}", log);
logs_clone.lock().unwrap().push(log);
let result = inner(state, action);
let log = format!("after: - Action: {:?}", action);
println!("{}", log);
logs_clone.lock().unwrap().push(log);
result
})
}
}
#[test]
fn test_logger_middleware() {
let reducer = Box::new(TestReducer);
let logger = Arc::new(TestLoggerMiddleware::new());
let store = StoreImpl::new_with(
0,
vec![reducer],
"test_logger_middleware".to_string(),
2,
BackpressurePolicy::BlockOnFull,
vec![logger.clone()],
)
.unwrap();
let _ = store.dispatch(1);
match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(logger.logs.lock().unwrap().len(), 2);
assert!(logger.logs.lock().unwrap().first().unwrap().contains("before: - Action"));
assert!(logger.logs.lock().unwrap().last().unwrap().contains("after: - Action"));
}
#[derive(Debug, Clone)]
enum MiddlewareAction {
ReqThisAction(i32),
ResponseAsThis(i32),
}
struct MiddlewareReducer {}
impl MiddlewareReducer {
fn new() -> Self {
Self {}
}
}
impl Reducer<i32, MiddlewareAction> for MiddlewareReducer {
fn reduce(
&self,
_state: &i32,
action: &MiddlewareAction,
) -> DispatchOp<i32, MiddlewareAction> {
match action {
MiddlewareAction::ReqThisAction(value) => DispatchOp::Dispatch(*value, vec![]),
MiddlewareAction::ResponseAsThis(value) => DispatchOp::Dispatch(*value, vec![]),
}
}
}
struct MiddlewareBeforeDispatch;
impl MiddlewareBeforeDispatch {
fn new() -> Arc<Self> {
Arc::new(Self {})
}
}
impl<State> MiddlewareFnFactory<State, MiddlewareAction> for MiddlewareBeforeDispatch
where
State: Send + Sync + Clone + 'static,
{
fn create(
&self,
inner: MiddlewareFn<State, MiddlewareAction>,
) -> MiddlewareFn<State, MiddlewareAction> {
Arc::new(move |state: &State, action: &MiddlewareAction| {
match action {
MiddlewareAction::ReqThisAction(v) => {
let value = *v + 1;
let effect = Effect::Action(MiddlewareAction::ResponseAsThis(value));
Ok(DispatchOp::Keep(state.clone(), vec![effect]))
}
_ => {
inner(state, action)
}
}
})
}
}
#[test]
fn test_middleware_before_reduce() {
let reducer = Box::new(MiddlewareReducer::new());
let effect_middleware = MiddlewareBeforeDispatch::new();
let store = StoreImpl::new_with(
0,
vec![reducer],
"test_middleware_before_reduce".to_string(),
2,
BackpressurePolicy::BlockOnFull,
vec![effect_middleware],
)
.unwrap();
let _ = store.dispatch(MiddlewareAction::ReqThisAction(42));
assert_eq!(store.get_state(), 0);
thread::sleep(Duration::from_millis(1000));
match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state(), 43);
}
#[derive(Debug, Clone)]
struct EffectState {
value: i32,
}
impl Default for EffectState {
fn default() -> Self {
Self { value: 0 }
}
}
#[derive(Debug, Clone)]
enum EffectAction {
ActionProduceEffectFunction(i32),
ResponseForTheEffect(i32),
}
struct EffectReducer {}
impl EffectReducer {
fn new() -> Self {
Self {}
}
}
impl Reducer<EffectState, EffectAction> for EffectReducer {
fn reduce(
&self,
_state: &EffectState,
action: &EffectAction,
) -> DispatchOp<EffectState, EffectAction> {
match action {
EffectAction::ActionProduceEffectFunction(value) => {
let new_state = EffectState { value: *value };
let value_clone = *value;
let effect = Effect::Function(
"key1".to_string(),
Box::new(move || {
println!("effect: {:?}", value_clone);
let action = EffectAction::ResponseForTheEffect(value_clone + 1);
Ok(Box::new(action) as Box<dyn std::any::Any>)
}),
);
DispatchOp::Dispatch(new_state, vec![effect])
}
EffectAction::ResponseForTheEffect(value) => {
let new_state = EffectState { value: *value };
DispatchOp::Dispatch(new_state, vec![])
}
}
}
}
struct EffectMiddleware;
impl EffectMiddleware {
fn new() -> Self {
Self {}
}
}
impl MiddlewareFnFactory<EffectState, EffectAction> for EffectMiddleware {
fn create(
&self,
inner: MiddlewareFn<EffectState, EffectAction>,
) -> MiddlewareFn<EffectState, EffectAction> {
Arc::new(move |state: &EffectState, action: &EffectAction| {
let dispatch_op = inner(state, action)?;
Ok(dispatch_op)
})
}
}
#[test]
fn test_effect_middleware() {
let reducer = Box::new(EffectReducer::new());
let effect_middleware = Arc::new(EffectMiddleware::new());
let store = StoreImpl::new_with(
EffectState::default(),
vec![reducer],
"test_effect_middleware".to_string(),
2,
BackpressurePolicy::BlockOnFull,
vec![effect_middleware.clone()],
)
.unwrap();
let _ = store.dispatch(EffectAction::ActionProduceEffectFunction(42));
thread::sleep(Duration::from_millis(1000));
match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state().value, 43);
}
#[test]
fn test_middleware_poisoned() {
let reducer = Box::new(TestReducer);
let logger = Arc::new(TestLoggerMiddleware::new());
let store = StoreImpl::new_with(
0,
vec![reducer],
"test_middleware_poisoned".to_string(),
2,
BackpressurePolicy::BlockOnFull,
vec![logger.clone()],
)
.unwrap();
let logger_clone = logger.clone();
let handle = thread::spawn(move || {
let _lock = logger_clone.logs.lock().unwrap();
panic!("Intentionally poison the mutex");
});
let _ = handle.join().expect_err("Thread should panic");
assert!(logger.logs.lock().is_err());
let _ = store.dispatch(1);
match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
match logger.logs.lock() {
Ok(_) => panic!("Mutex should be poisoned"),
Err(err) => {
println!("Mutex is poisoned as expected");
assert_eq!(err.get_ref().len(), 0);
}
};
}
}