use std::{error::Error, fmt};
use serde_json::Value;
use crate::{
Command, Decider, Events, ExecuteError, Precondition, State, Subject, UpcastEvent,
check_preconditions, replay, replay_stored, store::unencodable,
};
#[cfg(test)]
mod tests;
pub struct Fixture<'decider, C, S: State, D> {
decider: &'decider Decider<C, S, D>,
state: S,
}
impl<C, S: State, D> fmt::Debug for Fixture<'_, C, S, D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Fixture")
.field("decider", self.decider)
.finish_non_exhaustive()
}
}
#[track_caller]
pub fn given<C, S: State, D>(
decider: &Decider<C, S, D>,
history: impl IntoIterator<Item = S::Event>,
) -> Fixture<'_, C, S, D> {
let state = match replay(history) {
Ok(state) => state,
Err(failure) => panic!("given: {}", chain(&failure)),
};
Fixture { decider, state }
}
#[track_caller]
pub fn given_stored<C, S: State, D>(
decider: &Decider<C, S, D>,
history: impl IntoIterator<Item = UpcastEvent>,
) -> Fixture<'_, C, S, D> {
let state = match replay_stored(history) {
Ok(state) => state,
Err(failure) => panic!("given stored: {}", chain(&failure)),
};
Fixture { decider, state }
}
impl<C: Command, S: State, D> Fixture<'_, C, S, D> {
pub async fn when(self, command: C) -> Decision<S, D> {
let subject = command.subject();
let preconditions = command.preconditions();
let decided = match check_preconditions(&command) {
Ok(()) => self
.decider
.decide(command, self.state.clone())
.await
.map_err(ExecuteError::Decider),
Err(refusal) => Err(refusal.into()),
};
Decision {
state: self.state,
preconditions,
result: decided.and_then(|events| encode(&subject, events)),
}
}
}
struct Decided<E> {
events: Vec<E>,
data: Vec<Value>,
}
fn encode<E: Events, D>(subject: &Subject, events: Vec<E>) -> Result<Decided<E>, ExecuteError<D>> {
let data = events
.iter()
.map(|event| {
event
.to_data()
.map_err(|cause| unencodable(event.event_type(), subject.as_str(), cause))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Decided { events, data })
}
pub struct Decision<S: State, D> {
state: S,
preconditions: Vec<Precondition>,
result: Result<Decided<S::Event>, ExecuteError<D>>,
}
impl<S: State, D: fmt::Debug> fmt::Debug for Decision<S, D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let result = match &self.result {
Ok(decided) => describe(&decided.events),
Err(failure) => describe_failure(failure),
};
formatter
.debug_struct("Decision")
.field("preconditions", &self.preconditions)
.field("result", &result)
.finish_non_exhaustive()
}
}
impl<S: State, D: fmt::Debug> Decision<S, D> {
pub fn result(&self) -> Result<&[S::Event], &ExecuteError<D>> {
self.result
.as_ref()
.map(|decided| decided.events.as_slice())
}
#[track_caller]
pub fn then_events(&self, expected: impl IntoIterator<Item = S::Event>) -> &Self {
let decided = self.decided("expected events");
let expected: Vec<S::Event> = expected.into_iter().collect();
assert!(
decided.events.len() == expected.len(),
"expected {} event(s), got {}: {}",
expected.len(),
decided.events.len(),
describe(&decided.events),
);
for (index, (wanted, (got, got_data))) in expected
.iter()
.zip(decided.events.iter().zip(&decided.data))
.enumerate()
{
assert!(
got.event_type() == wanted.event_type(),
"event {index}: got type {:?}, want {:?}",
got.event_type(),
wanted.event_type(),
);
let wanted_data = match wanted.to_data() {
Ok(data) => data,
Err(cause) => panic!("event {index}: {cause}"),
};
assert!(
*got_data == wanted_data,
"event {index}: got {got_data}, want {wanted_data}"
);
}
self
}
#[track_caller]
pub fn then_nothing(&self) -> &Self {
let decided = self.decided("expected nothing to happen");
assert!(
decided.events.is_empty(),
"expected nothing to happen, got {}",
describe(&decided.events)
);
self
}
#[track_caller]
#[expect(
clippy::needless_pass_by_value,
reason = "a test hands over the error it expects as a value, as the decider returns it"
)]
pub fn then_failed(&self, expected: D) -> &Self
where
D: PartialEq,
{
match &self.result {
Err(ExecuteError::Decider(refusal)) if *refusal == expected => {}
Err(failure) => panic!(
"expected an error matching {expected:?}, got {}",
describe_failure(failure)
),
Ok(decided) => panic!(
"expected an error matching {expected:?}, got {}",
describe(&decided.events)
),
}
self
}
#[track_caller]
pub fn then_some_event(&self, matches: impl Fn(&S::Event) -> bool) -> &Self {
let decided = self.decided("expected events");
assert!(
decided.events.iter().any(matches),
"no event matched, got {}",
describe(&decided.events)
);
self
}
#[track_caller]
pub fn then_every_event(&self, matches: impl Fn(&S::Event) -> bool) -> &Self {
let decided = self.decided("expected events");
assert!(!decided.events.is_empty(), "expected events, got none");
if let Some(index) = decided.events.iter().position(|event| !matches(event)) {
panic!("event {index} did not match: {}", describe(&decided.events));
}
self
}
#[track_caller]
pub fn then_no_event(&self, matches: impl Fn(&S::Event) -> bool) -> &Self {
let decided = self.decided("expected events");
if let Some(index) = decided.events.iter().position(matches) {
panic!(
"event {index} matched although it should not: {}",
describe(&decided.events)
);
}
self
}
pub fn then_state(&self, check: impl FnOnce(&S)) -> &Self {
check(&self.state);
self
}
#[track_caller]
pub fn then_preconditions(&self, expected: impl IntoIterator<Item = Precondition>) -> &Self {
let expected: Vec<Precondition> = expected.into_iter().collect();
assert!(
self.preconditions.len() == expected.len(),
"expected {} precondition(s), got {}: {:?}",
expected.len(),
self.preconditions.len(),
self.preconditions,
);
for (index, (got, wanted)) in self.preconditions.iter().zip(&expected).enumerate() {
assert!(
got == wanted,
"precondition {index}: got {got:?}, want {wanted:?}"
);
}
self
}
#[track_caller]
fn decided(&self, expected: &str) -> &Decided<S::Event> {
match &self.result {
Ok(decided) => decided,
Err(failure) => panic!("{expected}, got error: {}", describe_failure(failure)),
}
}
}
fn describe<E: Events>(events: &[E]) -> String {
if events.is_empty() {
return "no events".to_owned();
}
let types: Vec<&str> = events.iter().map(Events::event_type).collect();
format!("{} event(s): {types:?}", events.len())
}
fn describe_failure<D: fmt::Debug>(failure: &ExecuteError<D>) -> String {
match failure {
ExecuteError::Decider(refusal) => format!("{refusal:?}"),
ExecuteError::Transient(failure) => chain(failure),
ExecuteError::Permanent(failure) => chain(failure),
ExecuteError::OutcomeUnknown(failure) => {
format!("outcome unknown: {}", failure.message())
}
}
}
fn chain(error: &dyn Error) -> String {
let mut messages = vec![error.to_string()];
let mut source = error.source();
while let Some(cause) = source {
messages.push(cause.to_string());
source = cause.source();
}
messages.join(": ")
}