use crate::ability::AbilityId;
use crate::battle::{BattleRules, Version};
use crate::creature::CreatureId;
use crate::entity::EntityId;
use crate::event::{DefaultOutput, Event, EventId, EventSinkId};
use crate::metric::MetricIdType;
use crate::object::ObjectId;
use crate::player::PlayerId;
use crate::power::PowerId;
use crate::space::Position;
use crate::status::StatusId;
use crate::team::TeamId;
use std::ops::Range;
use std::result::Result;
use std::{fmt, fmt::Debug};
pub type WeaselErrorType<R> = WeaselError<
Version<R>,
TeamId<R>,
EntityId<R>,
CreatureId<R>,
ObjectId<R>,
Position<R>,
AbilityId<R>,
PowerId<R>,
StatusId<R>,
MetricIdType<R>,
Box<dyn Event<R> + Send>,
>;
pub type WeaselResult<T, R> = Result<T, WeaselErrorType<R>>;
#[derive(Debug, Clone, PartialEq)]
pub enum WeaselError<V, TI, EI, CI, OI, PI, AI, WI, SI, MI, E> {
GenericError,
DuplicatedCreature(CI),
DuplicatedObject(OI),
DuplicatedTeam(TI),
TeamNotFound(TI),
CreatureNotFound(CI),
ObjectNotFound(OI),
NewCreatureUnaccepted(TI, Box<Self>),
ConvertedCreatureUnaccepted(TI, CI, Box<Self>),
InvalidCreatureConversion(TI, CI),
TeamNotEmpty(TI),
PositionError(Option<PI>, PI, Box<Self>),
EntityNotFound(EI),
NonContiguousEventId(EventId, EventId),
TurnInProgress,
NoTurnInProgress,
ActorNotEligible(EI),
ActorNotReady(EI),
AbilityNotKnown(EI, AI),
AbilityNotActivable(EI, AI, Box<Self>),
TeamNotReady(TI),
PowerNotKnown(TI, WI),
PowerNotInvocable(TI, WI, Box<Self>),
StatusNotPresent(EI, SI),
EmptyEventProcessor,
NotACharacter(EI),
NotAnActor(EI),
NotACreature(EI),
NotAnObject(EI),
KinshipRelation,
SelfRelation,
IncompatibleVersions(V, V),
BattleEnded,
WrongMetricType(MI),
ConditionUnsatisfied,
DuplicatedEventSink(EventSinkId),
InvalidEventRange(Range<EventId>, EventId),
EventSinkNotFound(EventSinkId),
AuthenticationError(Option<PlayerId>, TI),
MissingAuthentication,
ServerOnlyEvent,
UserEventPackingError(E, String),
UserEventUnpackingError(String),
InvalidEvent(E, Box<Self>),
MultiError(Vec<Self>),
UserError(String),
EventSinkError(String),
}
impl<V, TI, EI, CI, OI, PI, AI, WI, SI, MI, E> fmt::Display
for WeaselError<V, TI, EI, CI, OI, PI, AI, WI, SI, MI, E>
where
V: Debug,
TI: Debug,
EI: Debug,
CI: Debug,
OI: Debug,
PI: Debug,
AI: Debug,
WI: Debug,
SI: Debug,
MI: Debug,
E: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use WeaselError::*;
match self {
GenericError => write!(f, "generic error"),
DuplicatedCreature(id) => write!(f, "duplicated creature with id {:?}", id),
DuplicatedObject(id) => write!(f, "duplicated object with id {:?}", id),
DuplicatedTeam(id) => write!(f, "duplicated team with id {:?}", id),
TeamNotFound(id) => write!(f, "team {:?} not found", id),
CreatureNotFound(id) => write!(f, "creature {:?} not found", id),
ObjectNotFound(id) => write!(f, "object {:?} not found", id),
NewCreatureUnaccepted(id, error) => write!(
f,
"team {:?} does not accept new creatures due to {:?}",
id, error
),
ConvertedCreatureUnaccepted(team_id, creature_id, error) => write!(
f,
"team {:?} does not welcome the creature {:?} due to {:?}",
team_id, creature_id, error
),
InvalidCreatureConversion(team_id, creature_id) => write!(
f,
"creature {:?} is already part of team {:?}",
creature_id, team_id
),
TeamNotEmpty(id) => write!(f, "team {:?} has at least one creature", id),
PositionError(source, destination, error) => write!(
f,
"can't move entity from position {:?} to position {:?} due to {:?}",
source, destination, error
),
EntityNotFound(id) => write!(f, "entity {:?} not found", id),
NonContiguousEventId(id, expected) => {
write!(f, "event has id {:?}, expected {:?}", id, expected)
}
TurnInProgress => write!(f, "a turn is already in progress"),
NoTurnInProgress => write!(f, "no turn is in progress"),
ActorNotEligible(id) => write!(f, "actor {:?} is not eligible to start a new turn", id),
ActorNotReady(id) => write!(f, "actor {:?} can't act outside of his turn", id),
AbilityNotKnown(actor_id, ability_id) => write!(
f,
"actor {:?} doesn't know ability {:?}",
actor_id, ability_id
),
AbilityNotActivable(actor_id, ability_id, error) => write!(
f,
"actor {:?} can't activate ability {:?} due to {:?}",
actor_id, ability_id, error
),
TeamNotReady(id) => write!(f, "team {:?} can't act in this moment", id),
PowerNotKnown(team_id, power_id) => {
write!(f, "team {:?} doesn't know power {:?}", team_id, power_id)
}
PowerNotInvocable(team_id, power_id, error) => write!(
f,
"team {:?} can't invoke power {:?} due to {:?}",
team_id, power_id, error
),
StatusNotPresent(character_id, status_id) => write!(
f,
"character {:?} is not afflicted by status {:?}",
character_id, status_id
),
NotACharacter(id) => write!(f, "entity {:?} is not a character", id),
NotAnActor(id) => write!(f, "entity {:?} is not an actor", id),
NotACreature(id) => write!(f, "entity {:?} is not a creature", id),
NotAnObject(id) => write!(f, "entity {:?} is not an object", id),
EmptyEventProcessor => write!(f, "() is not a valid event processor to process events"),
KinshipRelation => write!(f, "kinship relation can't be explicitly set"),
SelfRelation => write!(f, "a team can't explicitly set a relation towards itself"),
IncompatibleVersions(client, server) => write!(
f,
"client version {:?} is different from server version {:?}",
client, server
),
BattleEnded => write!(f, "the battle has ended"),
WrongMetricType(id) => write!(
f,
"metric {:?} exists already with a different counter type",
id
),
ConditionUnsatisfied => write!(
f,
"the condition to apply this event prototype is not satisfied"
),
DuplicatedEventSink(id) => write!(f, "duplicated event sink with id {:?}", id),
InvalidEventRange(range, history_len) => write!(
f,
"event history (0..{}) doesn't contain the event range {:?}",
history_len, range
),
EventSinkNotFound(id) => write!(f, "event sink {:?} not found", id),
AuthenticationError(player, team) => write!(
f,
"player {:?} doesn't have control over team {:?}",
player, team
),
MissingAuthentication => write!(f, "event is not linked to any player"),
ServerOnlyEvent => write!(f, "event can be fired only by the server"),
UserEventPackingError(event, error) => {
write!(f, "failed to pack user event {:?}: {}", event, error)
}
UserEventUnpackingError(error) => write!(f, "failed to unpack user event: {}", error),
InvalidEvent(event, error) => write!(f, "{:?} failed due to {:?}, ", event, error),
MultiError(v) => {
write!(f, "[")?;
for err in v {
write!(f, "{:?}, ", err)?;
}
write!(f, "]")
}
UserError(msg) => write!(f, "user error: {}", msg),
EventSinkError(msg) => write!(f, "sink error: {}", msg),
}
}
}
impl<V, TI, EI, CI, OI, PI, AI, WI, SI, MI, E>
WeaselError<V, TI, EI, CI, OI, PI, AI, WI, SI, MI, E>
{
pub fn unfold(self) -> Self {
match self {
Self::InvalidEvent(_, inner) => inner.unfold(),
Self::MultiError(v) => {
Self::MultiError(v.into_iter().map(|err| err.unfold()).collect())
}
_ => self,
}
}
pub fn filter<F>(self, op: F) -> Result<(), Self>
where
F: Fn(&Self) -> bool + Copy,
{
if !op(&self) {
Ok(())
} else {
match self {
Self::InvalidEvent(event, error) => {
let new_error = error.filter(op);
if new_error.is_err() {
Err(Self::InvalidEvent(
event,
Box::new(new_error.err().unwrap()),
))
} else {
Ok(())
}
}
Self::MultiError(v) => {
let mut new_errors = Vec::new();
for error in v {
let new_error = error.filter(op);
if new_error.is_err() {
new_errors.push(new_error.err().unwrap());
}
}
if new_errors.is_empty() {
Ok(())
} else if new_errors.len() == 1 {
Err(new_errors.pop().unwrap())
} else {
Err(Self::MultiError(new_errors))
}
}
_ => Err(self),
}
}
}
}
impl<R> DefaultOutput<R> for WeaselResult<(), R>
where
R: BattleRules,
{
type Error = WeaselErrorType<R>;
fn ok() -> Self {
Ok(())
}
fn err(self) -> Option<Self::Error> {
self.err()
}
fn result(self) -> WeaselResult<(), R> {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::battle::BattleRules;
use crate::event::{DummyEvent, EventTrigger};
use crate::{battle_rules, rules::empty::*};
#[test]
#[allow(clippy::let_unit_value)]
fn unfold() {
battle_rules! {}
let mut processor = ();
let trigger = DummyEvent::trigger(&mut processor);
let error: WeaselErrorType<CustomRules> =
WeaselError::InvalidEvent(trigger.event(), Box::new(WeaselError::EmptyEventProcessor));
assert_eq!(error.clone().unfold(), WeaselError::EmptyEventProcessor);
let error: WeaselErrorType<CustomRules> = WeaselError::MultiError(vec![error]);
assert_eq!(
error.unfold(),
WeaselError::MultiError(vec![WeaselError::EmptyEventProcessor])
);
}
#[test]
#[allow(clippy::let_unit_value)]
fn filter() {
battle_rules! {}
let mut processor = ();
let trigger = DummyEvent::trigger(&mut (processor));
let filter_fn = |err: &WeaselErrorType<_>| !matches!(err, WeaselError::EmptyEventProcessor);
let error_to_filter: WeaselErrorType<CustomRules> =
WeaselError::InvalidEvent(trigger.event(), Box::new(WeaselError::EmptyEventProcessor));
assert_eq!(error_to_filter.clone().filter(filter_fn).err(), None);
let error: WeaselErrorType<CustomRules> =
WeaselError::MultiError(vec![error_to_filter.clone()]);
assert_eq!(error.filter(filter_fn).err(), None);
let error: WeaselErrorType<CustomRules> =
WeaselError::InvalidEvent(trigger.event(), Box::new(WeaselError::TurnInProgress));
let error: WeaselErrorType<CustomRules> =
WeaselError::MultiError(vec![error_to_filter, error]);
assert_eq!(
error.filter(filter_fn).err(),
Some(WeaselError::InvalidEvent(
trigger.event(),
Box::new(WeaselError::TurnInProgress)
))
);
}
}