use crate::Location;
use core::error::Error;
pub trait StackError: Error {
#[must_use]
fn location(&self) -> &Location;
#[must_use]
fn type_name(&self) -> &'static str;
#[must_use]
fn stack_source(&self) -> Option<&dyn StackError> {
None
}
#[must_use]
fn depth(&self) -> usize {
let mut count = 0;
let mut current = self.source();
while let Some(e) = current {
count += 1;
current = e.source();
}
count
}
}
#[cfg(feature = "alloc")]
mod alloc_impls {
use super::*;
use alloc::boxed::Box;
use alloc::sync::Arc;
impl<T: StackError> StackError for Box<T> {
fn location(&self) -> &Location {
self.as_ref().location()
}
fn type_name(&self) -> &'static str {
self.as_ref().type_name()
}
fn stack_source(&self) -> Option<&dyn StackError> {
self.as_ref().stack_source()
}
}
impl<T: ?Sized + StackError> StackError for Arc<T> {
fn location(&self) -> &Location {
self.as_ref().location()
}
fn type_name(&self) -> &'static str {
self.as_ref().type_name()
}
fn stack_source(&self) -> Option<&dyn StackError> {
self.as_ref().stack_source()
}
}
impl Error for Box<dyn StackError> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Error::source(Box::as_ref(self))
}
}
impl StackError for Box<dyn StackError> {
fn location(&self) -> &Location {
self.as_ref().location()
}
fn type_name(&self) -> &'static str {
self.as_ref().type_name()
}
fn stack_source(&self) -> Option<&dyn StackError> {
self.as_ref().stack_source()
}
}
impl Error for Box<dyn StackError + Send + Sync> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Error::source(Box::as_ref(self))
}
}
impl StackError for Box<dyn StackError + Send + Sync> {
fn location(&self) -> &Location {
self.as_ref().location()
}
fn type_name(&self) -> &'static str {
self.as_ref().type_name()
}
fn stack_source(&self) -> Option<&dyn StackError> {
self.as_ref().stack_source()
}
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
use crate::StackReport;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use alloc::sync::Arc;
use snafu::prelude::*;
#[derive(Debug, Snafu)]
#[snafu(display("Simple test error: {}", message))]
struct SimpleError {
message: String,
#[snafu(implicit)]
location: Location,
}
impl StackError for SimpleError {
fn location(&self) -> &Location {
&self.location
}
fn type_name(&self) -> &'static str {
"SimpleError"
}
}
#[derive(Debug, Snafu)]
#[snafu(display("Wrapper error: {}", message))]
struct WrapperError {
message: String,
source: Box<dyn StackError + Send + Sync>,
#[snafu(implicit)]
location: Location,
}
impl StackError for WrapperError {
fn location(&self) -> &Location {
&self.location
}
fn type_name(&self) -> &'static str {
"WrapperError"
}
fn stack_source(&self) -> Option<&dyn StackError> {
Some(self.source.as_ref())
}
}
#[test]
fn test_basic_location() {
let error = SimpleSnafu {
message: "Something went wrong",
}
.build();
assert_eq!(error.location().file(), file!());
assert!(error.location().line() > 0);
assert!(format!("{}", error).contains("Simple test error"));
assert!(format!("{}", error).contains("Something went wrong"));
handle_stack_error(error)
}
#[test]
fn test_error_boxing() {
let concrete_error = SimpleSnafu {
message: "Original error",
}
.build();
let boxed_error: Box<dyn StackError> = Box::new(concrete_error);
assert_eq!(boxed_error.location().file(), file!());
assert!(boxed_error.location().line() > 0);
assert!(format!("{}", boxed_error).contains("Simple test error"));
assert!(format!("{}", boxed_error).contains("Original error"));
handle_stack_error(boxed_error)
}
#[test]
fn test_error_chaining() {
fn gen_root_error() -> Result<(), Box<dyn StackError + Send + Sync + 'static>> {
let root_error = SimpleSnafu {
message: "Root cause",
}
.build();
Err(Box::new(root_error))
}
let root_error = gen_root_error();
let root_location = root_error.unwrap_err().location().line();
let wrapper_error = gen_root_error()
.context(WrapperSnafu {
message: "Something failed",
})
.unwrap_err();
assert!(wrapper_error.location().file().ends_with("stack_error.rs"));
assert_ne!(wrapper_error.location().line(), root_location);
let report = format!("{:?}", StackReport::from(wrapper_error));
let file = file!();
assert!(report.contains("Error: WrapperError: Wrapper error: Something failed"));
assert!(report.contains(&format!(", at {file}:")));
assert!(report.contains("Caused by"));
assert!(report.contains("1| SimpleError: Simple test error: Root cause"));
}
#[test]
fn test_arc_errors() {
let error = SimpleSnafu {
message: "Arc-wrapped error",
}
.build();
let original_location = error.location().line();
let arc_error = Arc::new(error);
assert_eq!(arc_error.location().line(), original_location);
let cloned_arc = arc_error.clone();
assert_eq!(cloned_arc.location().line(), original_location);
handle_stack_error(arc_error);
let arc_error: Arc<dyn StackError> = Arc::new(SimpleSnafu { message: "Simple" }.build());
handle_stack_error(arc_error);
}
#[test]
fn test_from_implementation() {
let concrete_error = SimpleSnafu {
message: "Converted error",
}
.build();
let original_location = concrete_error.location().line();
let boxed_error: Box<dyn StackError + Send + Sync + 'static> = Box::new(concrete_error);
assert_eq!(boxed_error.location().line(), original_location);
handle_stack_error(boxed_error);
}
#[test]
fn test_depth_one() {
fn gen_root() -> Result<(), Box<dyn StackError + Send + Sync + 'static>> {
let root = SimpleSnafu { message: "root" }.build();
Err(Box::new(root))
}
let wrapper = gen_root()
.context(WrapperSnafu { message: "wrapper" })
.unwrap_err();
assert_eq!(wrapper.depth(), 1);
}
#[test]
fn test_box_concrete_stack_error() {
let concrete = SimpleSnafu {
message: "boxed concrete",
}
.build();
let original_line = concrete.location().line();
let boxed: Box<SimpleError> = Box::new(concrete);
assert_eq!(boxed.location().line(), original_line);
assert_eq!(boxed.type_name(), "SimpleError");
assert!(boxed.stack_source().is_none());
handle_stack_error(boxed);
}
fn handle_stack_error<T: StackError>(_: T) {}
#[test]
fn test_box_dyn_stack_error_non_send_sync() {
let concrete = SimpleSnafu {
message: "boxed non-send-sync",
}
.build();
let original_line = concrete.location().line();
let boxed: Box<dyn StackError> = Box::new(concrete);
assert_eq!(boxed.location().line(), original_line);
assert_eq!(boxed.type_name(), "SimpleError");
assert!(boxed.stack_source().is_none());
let err: &dyn Error = &boxed;
assert!(format!("{err}").contains("boxed non-send-sync"));
}
}