#[cfg(feature = "syn")]
use std::any::Any;
use std::fmt::{Debug, Display};
pub struct TestResult<T, E> {
inner: Result<T, E>,
context: Option<String>,
source: Option<String>,
}
impl<T: Debug, E: Display + Debug + 'static> TestResult<T, E> {
pub fn new(result: Result<T, E>) -> Self {
Self {
inner: result,
context: None,
source: None,
}
}
pub fn with_context(mut self, context: &str) -> Self {
self.context = Some(context.to_string());
self
}
pub fn with_source(mut self, source: &str) -> Self {
self.source = Some(source.to_string());
self
}
fn format_context(&self) -> String {
self.context
.as_ref()
.map(|c| format!("\nContext: {}", c))
.unwrap_or_default()
}
fn format_err(&self, err: &E) -> String {
format_error_impl(err, self.source.as_deref())
}
pub fn inspect(self) -> Self {
let ctx = self.format_context();
match &self.inner {
Ok(val) => {
println!("\nđ INSPECT SUCCESS: {}\nValue: {:?}\n", ctx, val);
}
Err(e) => {
let msg = self.format_err(e);
println!(
"\nđ INSPECT FAILURE: {}\nMessage: {}\nDebug: {:?}\n",
ctx, msg, e
);
}
}
self
}
pub fn assert_success(self) -> T {
let ctx = self.format_context();
match self.inner {
Ok(val) => {
println!("âšī¸ Asserting success.\n Actual: {:?}", val);
val
}
Err(ref e) => {
let msg = self.format_err(e);
panic!(
"\nđ´ TEST FAILED (Expected Success, but got Error):{}\nMessage: {}\nError Debug: {:?}\n",
ctx, msg, e
);
}
}
}
pub fn assert_success_is<Exp>(self, expected: Exp) -> T
where
T: PartialEq<Exp>,
Exp: Debug,
{
let ctx = self.format_context();
let val = self.assert_success();
println!("âšī¸ Checking equality.\n Expected: {:?}", expected);
if val != expected {
panic!(
"\nđ´ TEST FAILED (Value Mismatch):{}\nExpected: {:?}\nGot: {:?}\n",
ctx, expected, val
);
}
val
}
pub fn assert_success_with<F>(self, f: F) -> T
where
F: FnOnce(&T),
{
let val = self.assert_success();
println!("âšī¸ Asserting success with closure.");
f(&val);
val
}
pub fn assert_success_debug(self, expected_debug: &str) -> T {
let ctx = self.format_context();
let val = self.assert_success();
let actual_debug = format!("{:?}", val);
println!(
"âšī¸ Checking debug representation.\n Expected: {:?}",
expected_debug
);
if actual_debug != expected_debug {
panic!(
"\nđ´ TEST FAILED (Debug Mismatch):{}\nExpected: {:?}\nGot: {:?}\n",
ctx, expected_debug, actual_debug
);
}
val
}
pub fn assert_failure(self) -> E {
let ctx = self.format_context();
match self.inner {
Ok(val) => {
panic!(
"\nđ´ TEST FAILED (Expected Failure, but got Success):{}\nParsed Value: {:?}\n",
ctx, val
);
}
Err(e) => {
println!("âšī¸ Asserting failure.\n Error: {:?}", e);
e
}
}
}
pub fn assert_failure_contains(self, expected_msg_part: &str) {
let ctx = self.format_context();
let source = self.source.clone();
let err = self.assert_failure();
let actual_msg = err.to_string();
println!(
"âšī¸ Checking error message contains {:?}.\n Actual message: {:?}",
expected_msg_part, actual_msg
);
if !actual_msg.contains(expected_msg_part) {
let formatted = format_error_impl(&err, source.as_deref());
panic!(
"\nđ´ TEST FAILED (Error Message Mismatch):{}\nExpected part: {:?}\nActual msg: {:?}\nError Debug: {:?}\nFormatted: \n{}\n",
ctx, expected_msg_part, actual_msg, err, formatted
);
}
}
pub fn assert_success_contains(self, expected_part: &str) -> T
where
T: Display,
{
let ctx = self.format_context();
let val = self.assert_success();
let val_str = val.to_string();
println!(
"âšī¸ Checking success string contains {:?}.\n Actual string: {:?}",
expected_part, val_str
);
if !val_str.contains(expected_part) {
panic!(
"\nđ´ TEST FAILED (Content Mismatch):{}\nExpected to contain: {:?}\nGot: {:?}\n",
ctx, expected_part, val_str
);
}
val
}
pub fn assert_failure_not_contains(self, unexpected_part: &str) {
let ctx = self.format_context();
let source = self.source.clone();
let err = self.assert_failure();
let actual_msg = err.to_string();
println!(
"âšī¸ Checking error message NOT contains {:?}.\n Actual message: {:?}",
unexpected_part, actual_msg
);
if actual_msg.contains(unexpected_part) {
let formatted = format_error_impl(&err, source.as_deref());
panic!(
"\nđ´ TEST FAILED (Unexpected Error Message Content):{}\nUnexpected part: {:?}\nActual msg: {:?}\nError Debug: {:?}\nFormatted:\n{}\n",
ctx, unexpected_part, actual_msg, err, formatted
);
}
}
}
pub trait Testable<T, E> {
fn test(self) -> TestResult<T, E>;
}
#[cfg(feature = "syn")]
impl<T: Debug> Testable<T, syn::Error> for syn::Result<T> {
fn test(self) -> TestResult<T, syn::Error> {
TestResult::new(self)
}
}
fn format_error_impl<E: Display + Debug + 'static>(err: &E, source: Option<&str>) -> String {
#[cfg(feature = "syn")]
if let Some(src) = source {
if let Some(syn_err) = (err as &dyn Any).downcast_ref::<syn::Error>() {
return pretty_print_syn_error(syn_err, src);
}
}
format!("{}", err)
}
#[cfg(feature = "syn")]
fn pretty_print_syn_error(err: &syn::Error, source: &str) -> String {
let start = err.span().start();
let end = err.span().end();
if start.line == 0 {
return err.to_string();
}
let line_idx = start.line - 1;
let lines: Vec<&str> = source.lines().collect();
if line_idx >= lines.len() {
return err.to_string();
}
let line = lines[line_idx];
let col = start.column;
let width = if start.line == end.line {
end.column.saturating_sub(col).max(1)
} else {
1
};
format!(
"{}\n --> line {}:{}\n |\n {} | {}\n | {}{}",
err,
start.line,
col,
start.line,
line,
" ".repeat(col),
"^".repeat(width)
)
}