use crate::dispatcher::Dispatcher;
use crate::reactor::Reactor;
use crate::reducer::Reducer;
use core::mem::replace;
use derive_more::Deref;
#[cfg(feature = "async")]
use pin_project::*;
#[cfg_attr(feature = "async", pin_project)]
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deref)]
pub struct Store<S, R: Reactor<S>> {
#[deref]
state: S,
#[cfg_attr(feature = "async", pin)]
reactor: R,
}
impl<S, R: Reactor<S>> Store<S, R> {
pub fn new(state: S, reactor: R) -> Self {
Self { state, reactor }
}
pub fn subscribe(&mut self, reactor: impl Into<R>) -> R {
replace(&mut self.reactor, reactor.into())
}
}
impl<A, S, R> Dispatcher<A> for Store<S, R>
where
S: Reducer<A>,
R: Reactor<S>,
{
type Output = Result<(), R::Error>;
fn dispatch(&mut self, action: A) -> Self::Output {
self.state.reduce(action);
self.reactor.react(&self.state)
}
}
#[cfg(feature = "async")]
mod sink {
use super::*;
use futures::sink::Sink;
use futures::task::{Context, Poll};
use std::pin::Pin;
impl<A, S, R, E> Sink<A> for Store<S, R>
where
S: Reducer<A>,
R: Reactor<S, Error = E> + for<'s> Sink<&'s S, Error = E>,
{
type Error = E;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().reactor.poll_ready(cx)
}
#[project]
fn start_send(self: Pin<&mut Self>, action: A) -> Result<(), Self::Error> {
#[project]
let Store { state, reactor } = self.project();
state.reduce(action);
reactor.start_send(state)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().reactor.poll_flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().reactor.poll_close(cx)
}
}
}
#[cfg(feature = "async")]
pub use sink::*;
#[cfg(test)]
mod tests {
use super::*;
use crate::reactor::MockReactor;
use crate::reducer::MockReducer;
use mockall::predicate::*;
use proptest::prelude::*;
#[cfg(feature = "async")]
use futures::{executor::block_on, sink::SinkExt};
#[test]
fn default() {
Store::<MockReducer<()>, MockReactor<_, ()>>::default();
}
#[test]
fn deref() {
let store = Store::new(MockReducer::<()>::new(), MockReactor::<_, ()>::new());
assert_eq!(&*store as *const _, &store.state as *const _);
}
proptest! {
#[test]
fn new(a: usize, b: usize) {
let mut reducer = MockReducer::<()>::new();
reducer.expect_id().return_const(a);
let mut reactor = MockReactor::<_, ()>::new();
reactor.expect_id().return_const(b);
let store = Store::new(reducer, reactor);
assert_eq!(store.state.id(), a);
assert_eq!(store.reactor.id(), b);
}
#[test]
fn clone(a: usize, b: usize) {
let mut reducer = MockReducer::<()>::new();
reducer.expect_id().return_const(a);
reducer.expect_clone().times(1).returning(move || {
let mut mock = MockReducer::new();
mock.expect_id().return_const(a);
mock
});
let mut reactor = MockReactor::<_, ()>::new();
reactor.expect_id().return_const(b);
reactor.expect_clone().times(1).returning(move || {
let mut mock = MockReactor::new();
mock.expect_id().return_const(b);
mock
});
#[allow(clippy::redundant_clone)]
let store = Store::new(reducer, reactor).clone();
assert_eq!(store.state.id(), a);
assert_eq!(store.reactor.id(), b);
}
#[test]
fn subscribe(a: usize, b: usize) {
let mut mock = MockReactor::<_, ()>::new();
mock.expect_id().return_const(a);
let mut store = Store::new(MockReducer::<()>::new(), mock);
let mut mock = MockReactor::<_, ()>::new();
mock.expect_id().return_const(b);
assert_eq!(store.subscribe(mock).id(), a);
assert_eq!(store.reactor.id(), b);
}
#[test]
fn dispatch(action: u8, result: Result<(), u8>, id: usize) {
let mut reducer = MockReducer::new();
reducer.expect_id().return_const(id);
reducer.expect_clone().never();
reducer
.expect_reduce()
.with(eq(action))
.times(1)
.return_const(());
let mut reactor = MockReactor::new();
reactor
.expect_react()
.with(function(move |x: &MockReducer<_>| x.id() == id))
.times(1)
.return_const(result);
let mut store = Store::new(reducer, reactor);
assert_eq!(Dispatcher::dispatch(&mut store, action), result);
}
#[cfg(feature = "async")]
#[test]
fn sink(action: u8, result: Result<(), u8>, id: usize) {
let mut reducer = MockReducer::new();
reducer.expect_id().return_const(id);
reducer.expect_clone().returning(move || {
let mut mock = MockReducer::new();
mock.expect_id().return_const(id);
mock.expect_reduce().never();
mock.expect_clone().never();
mock
});
reducer
.expect_reduce()
.with(eq(action))
.times(1)
.return_const(());
let mut reactor = MockReactor::new();
reactor
.expect_react()
.with(function(move |x: &MockReducer<_>| x.id() == id))
.times(1)
.return_const(result);
let mut store = Store::new(reducer, Reactor::<_, Error = _>::from_sink(reactor));
assert_eq!(block_on(store.send(action)), result);
assert_eq!(block_on(store.close()), Ok(()));
}
}
}