use std::fmt;
use super::{CallSite, CallType, ErrorKind, ExecutionError, ExecutionFail};
use crate::runtime::InstanceId;
#[derive(Debug)]
pub struct ErrorMatch {
kind: ErrorKind,
description: StringMatch,
runtime_id: Option<u32>,
instance_id: Option<InstanceId>,
call_type: Option<CallType>,
}
impl ErrorMatch {
pub fn from_fail<F: ExecutionFail + ?Sized>(fail: &F) -> Self {
Self::new(fail.kind(), fail.description())
}
pub fn any_unexpected() -> Self {
Self {
kind: ErrorKind::Unexpected,
description: StringMatch::Any,
runtime_id: None,
instance_id: None,
call_type: None,
}
}
pub(super) fn new(kind: ErrorKind, description: String) -> Self {
Self {
kind,
description: StringMatch::Exact(description),
runtime_id: None,
instance_id: None,
call_type: None,
}
}
pub fn with_any_description(mut self) -> Self {
self.description = StringMatch::Any;
self
}
pub fn with_description_containing(mut self, pat: impl Into<String>) -> Self {
self.description = StringMatch::Contains(pat.into());
self
}
pub fn with_description_matching<P>(mut self, pat: P) -> Self
where
P: Fn(&str) -> bool + 'static,
{
self.description = StringMatch::Generic(Box::new(pat));
self
}
pub fn in_runtime(mut self, runtime_id: u32) -> Self {
self.runtime_id = Some(runtime_id);
self
}
pub fn for_service(mut self, instance_id: InstanceId) -> Self {
self.instance_id = Some(instance_id);
self
}
pub fn in_call(mut self, call_type: CallType) -> Self {
self.call_type = Some(call_type);
self
}
}
impl PartialEq<ErrorMatch> for ExecutionError {
fn eq(&self, error_match: &ErrorMatch) -> bool {
let kind_matches = self.kind == error_match.kind;
let runtime_matches = match (error_match.runtime_id, self.runtime_id) {
(None, _) => true,
(Some(match_id), Some(id)) => match_id == id,
_ => false,
};
let instance_matches = match (error_match.instance_id, &self.call_site) {
(None, _) => true,
(Some(match_id), Some(CallSite { instance_id, .. })) => match_id == *instance_id,
_ => false,
};
let call_type_matches = match (&error_match.call_type, &self.call_site) {
(None, _) => true,
(Some(match_type), Some(CallSite { call_type, .. })) => match_type == call_type,
_ => false,
};
kind_matches
&& runtime_matches
&& instance_matches
&& call_type_matches
&& error_match.description.matches(&self.description)
}
}
impl PartialEq<ExecutionError> for ErrorMatch {
fn eq(&self, other: &ExecutionError) -> bool {
other.eq(self)
}
}
enum StringMatch {
Any,
Exact(String),
Contains(String),
Generic(Box<dyn Fn(&str) -> bool>),
}
impl fmt::Debug for StringMatch {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Any => formatter.write_str("Any"),
Self::Exact(s) => formatter.debug_tuple("Exact").field(s).finish(),
Self::Contains(s) => formatter.debug_tuple("Contains").field(s).finish(),
Self::Generic(_) => formatter.debug_tuple("Generic").field(&"_").finish(),
}
}
}
impl StringMatch {
pub(super) fn matches(&self, s: &str) -> bool {
match self {
Self::Any => true,
Self::Exact(ref_str) => ref_str == s,
Self::Contains(needle) => s.contains(needle),
Self::Generic(match_fn) => match_fn(s),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::cognitive_complexity)] fn execution_error_matching() {
let mut error = ExecutionError {
kind: ErrorKind::Unexpected,
description: "Panic!".to_string(),
runtime_id: None,
call_site: None,
};
let mut matcher = ErrorMatch {
kind: ErrorKind::Unexpected,
description: StringMatch::Any,
runtime_id: None,
instance_id: None,
call_type: None,
};
assert_eq!(error, matcher);
matcher.description = StringMatch::Exact("Panic!".to_owned());
assert_eq!(error, matcher);
matcher.description = StringMatch::Exact("Panic".to_owned());
assert_ne!(error, matcher);
matcher.description = StringMatch::Contains("nic!".to_owned());
assert_eq!(error, matcher);
matcher.description = StringMatch::Contains("nic?".to_owned());
assert_ne!(error, matcher);
matcher.description = StringMatch::Generic(Box::new(|s| s.eq_ignore_ascii_case("panic!")));
assert_eq!(error, matcher);
error.runtime_id = Some(1);
assert_eq!(error, matcher);
matcher.runtime_id = Some(0);
assert_ne!(error, matcher);
matcher.runtime_id = Some(1);
assert_eq!(error, matcher);
error.call_site = Some(CallSite::new(100, CallType::Constructor));
assert_eq!(error, matcher);
matcher.instance_id = Some(99);
assert_ne!(error, matcher);
matcher.instance_id = Some(100);
assert_eq!(error, matcher);
matcher.call_type = Some(CallType::AfterTransactions);
assert_ne!(error, matcher);
matcher.call_type = Some(CallType::Constructor);
assert_eq!(error, matcher);
error.call_site = Some(CallSite::new(
100,
CallType::Method {
interface: "exonum.Configure".to_owned(),
id: 1,
},
));
matcher.call_type = None;
assert_eq!(error, matcher);
matcher.call_type = Some(CallType::Method {
interface: "exonum.Configure".to_owned(),
id: 0,
});
assert_ne!(error, matcher);
matcher.call_type = Some(CallType::Method {
interface: "exonum.v2.Configure".to_owned(),
id: 1,
});
assert_ne!(error, matcher);
matcher.call_type = Some(CallType::Method {
interface: "exonum.Configure".to_owned(),
id: 1,
});
assert_eq!(error, matcher);
}
}