Skip to content
Merged

451 #456

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 94 additions & 3 deletions src/app_error/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,20 +228,111 @@ mod tests {

#[cfg(feature = "std")]
#[test]
fn error_downcast_mut_returns_none() {
fn error_downcast_mut_returns_source_for_owned_source() {
use std::io::Error as IoError;
let io_err = IoError::other("test");
let mut err = Error::new(AppErrorKind::Internal, "fail").with_context(io_err);
let source = err.downcast_mut::<IoError>().expect("owned io source");
assert_eq!(source.to_string(), "test");
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_mut_mutation_visible_via_downcast_ref() {
use std::io::Error as IoError;
let io_err = IoError::other("before");
let mut err = Error::new(AppErrorKind::Internal, "fail").with_context(io_err);
let source = err.downcast_mut::<IoError>().expect("owned io source");
*source = IoError::other("after");
let source = err.downcast_ref::<IoError>().expect("io source");
assert_eq!(source.to_string(), "after");
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_mut_returns_none_for_wrong_type() {
use std::{fmt::Error as FmtError, io::Error as IoError};
let io_err = IoError::other("test");
let mut err = Error::new(AppErrorKind::Internal, "fail").with_context(io_err);
assert!(err.downcast_mut::<FmtError>().is_none());
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_mut_returns_none_without_source() {
use std::io::Error as IoError;
let mut err = Error::new(AppErrorKind::Internal, "fail");
assert!(err.downcast_mut::<IoError>().is_none());
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_mut_returns_none_for_shared_arc_source() {
use std::{io::Error as IoError, sync::Arc};
let shared = Arc::new(IoError::other("shared"));
let source: Arc<dyn core::error::Error + Send + Sync + 'static> = shared.clone();
let mut err = Error::new(AppErrorKind::Internal, "fail").with_source_arc(source);
assert!(err.downcast_mut::<IoError>().is_none());
assert_eq!(Arc::strong_count(&shared), 2);
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_mut_returns_source_for_unique_arc_source() {
use std::{io::Error as IoError, sync::Arc};
let shared: Arc<dyn core::error::Error + Send + Sync + 'static> =
Arc::new(IoError::other("unique"));
let mut err = Error::new(AppErrorKind::Internal, "fail").with_source_arc(shared);
let source = err.downcast_mut::<IoError>().expect("unique arc source");
assert_eq!(source.to_string(), "unique");
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_returns_err() {
fn error_downcast_returns_boxed_source_for_owned_source() {
use std::io::Error as IoError;
let io_err = IoError::other("test");
let err = Error::new(AppErrorKind::Internal, "fail").with_context(io_err);
assert!(err.downcast::<IoError>().is_err());
let source = err.downcast::<IoError>().expect("owned io source");
assert_eq!(source.to_string(), "test");
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_returns_err_with_source_intact_for_wrong_type() {
use std::{fmt::Error as FmtError, io::Error as IoError};
let io_err = IoError::other("test");
let err = Error::new(AppErrorKind::Internal, "fail").with_context(io_err);
let err = err.downcast::<FmtError>().expect_err("wrong type");
assert_eq!(err.kind, AppErrorKind::Internal);
assert!(err.is::<IoError>());
assert_eq!(
err.downcast_ref::<IoError>()
.expect("io source")
.to_string(),
"test"
);
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_returns_err_without_source() {
use std::io::Error as IoError;
let err = Error::new(AppErrorKind::Internal, "fail");
let err = err.downcast::<IoError>().expect_err("no source");
assert_eq!(err.kind, AppErrorKind::Internal);
}

#[cfg(feature = "std")]
#[test]
fn error_downcast_returns_err_with_source_intact_for_shared_arc() {
use std::{io::Error as IoError, sync::Arc};
let shared = Arc::new(IoError::other("shared"));
let source: Arc<dyn core::error::Error + Send + Sync + 'static> = shared.clone();
let err = Error::new(AppErrorKind::Internal, "fail").with_source_arc(source);
let err = err.downcast::<IoError>().expect_err("shared source");
assert!(err.is::<IoError>());
assert_eq!(Arc::strong_count(&shared), 2);
}

#[test]
Expand Down
33 changes: 26 additions & 7 deletions src/app_error/core/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT

use alloc::{borrow::Cow, string::String, sync::Arc};
use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
use core::error::Error as CoreError;
#[cfg(feature = "backtrace")]
use std::backtrace::Backtrace;
Expand All @@ -14,7 +14,7 @@ use serde_json::{Value as JsonValue, to_value};

use super::{
error::Error,
types::{CapturedBacktrace, ContextAttachment, MessageEditPolicy}
types::{CapturedBacktrace, ContextAttachment, MessageEditPolicy, StoredSource}
};
use crate::{
AppCode, AppErrorKind, RetryAdvice,
Expand Down Expand Up @@ -240,14 +240,18 @@ impl Error {
ContextAttachment::Owned(source) => {
match source.downcast::<Arc<dyn CoreError + Send + Sync + 'static>>() {
Ok(shared) => self.with_source_arc(*shared),
Err(source) => self.with_source_arc(Arc::from(source))
Err(source) => self.with_boxed_source(source)
}
}
ContextAttachment::Shared(source) => self.with_source_arc(source)
}
}

/// Attach a source error for diagnostics.
/// Attach an owned source error for diagnostics.
///
/// The source stays exclusively owned by this error, so it can later be
/// recovered by value via [`downcast`](Self::downcast) or mutated via
/// [`downcast_mut`](Self::downcast_mut).
///
/// Prefer [`with_context`](Self::with_context) when capturing upstream
/// diagnostics without additional `Arc` allocations.
Expand All @@ -264,14 +268,29 @@ impl Error {
/// # }
/// ```
#[must_use]
pub fn with_source(mut self, source: impl CoreError + Send + Sync + 'static) -> Self {
self.source = Some(Arc::new(source));
pub fn with_source(self, source: impl CoreError + Send + Sync + 'static) -> Self {
self.with_boxed_source(Box::new(source))
}

/// Attach an already boxed source error without re-boxing.
pub(crate) fn with_boxed_source(
mut self,
source: Box<dyn CoreError + Send + Sync + 'static>
) -> Self {
self.source = Some(StoredSource::Owned(source));
self.mark_dirty();
self
}

/// Attach a shared source error without cloning the underlying `Arc`.
///
/// A shared source can be borrowed via
/// [`downcast_ref`](Self::downcast_ref) but never extracted by value:
/// [`downcast`](Self::downcast) returns `Err(self)` for shared sources and
/// [`downcast_mut`](Self::downcast_mut) succeeds only while the `Arc` is
/// uniquely referenced. Use [`with_source`](Self::with_source) when the
/// caller owns the source and may want it back.
///
/// # Examples
///
/// ```rust
Expand All @@ -288,7 +307,7 @@ impl Error {
/// ```
#[must_use]
pub fn with_source_arc(mut self, source: Arc<dyn CoreError + Send + Sync + 'static>) -> Self {
self.source = Some(source);
self.source = Some(StoredSource::Shared(source));
self.mark_dirty();
self
}
Expand Down
6 changes: 3 additions & 3 deletions src/app_error/core/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ impl Error {
}
if let Some(source) = &self.source {
writeln!(f)?;
let mut current: &dyn CoreError = source.as_ref();
let mut current: &dyn CoreError = source.as_dyn();
let mut depth = 0;
while depth < 10 {
writeln!(
Expand Down Expand Up @@ -246,7 +246,7 @@ impl Error {
}
if let Some(source) = &self.source {
writeln!(f)?;
let mut current: &dyn CoreError = source.as_ref();
let mut current: &dyn CoreError = source.as_dyn();
let mut depth = 0;
while depth < 10 {
writeln!(f, " Caused by: {}", current)?;
Expand Down Expand Up @@ -300,7 +300,7 @@ impl Error {
}
if let Some(source) = &self.source {
write!(f, r#","source_chain":["#)?;
let mut current: &dyn CoreError = source.as_ref();
let mut current: &dyn CoreError = source.as_dyn();
let mut depth = 0;
let mut first = true;
while depth < 5 {
Expand Down
14 changes: 8 additions & 6 deletions src/app_error/core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//
// SPDX-License-Identifier: MIT

use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
#[cfg(feature = "backtrace")]
use alloc::sync::Arc;
use alloc::{borrow::Cow, boxed::Box, string::String};
use core::{
error::Error as CoreError,
fmt::{Display, Formatter, Result as FmtResult},
Expand All @@ -17,7 +19,7 @@ use serde_json::Value as JsonValue;

#[cfg(not(feature = "backtrace"))]
use super::types::CapturedBacktrace;
use super::types::MessageEditPolicy;
use super::types::{MessageEditPolicy, StoredSource};
use crate::{AppCode, AppErrorKind, RetryAdvice, app_error::metadata::Metadata};

/// Internal representation of error state.
Expand Down Expand Up @@ -48,7 +50,7 @@ pub struct ErrorInner {
/// Optional textual details when JSON is unavailable.
#[cfg(not(feature = "serde_json"))]
pub details: Option<String>,
pub source: Option<Arc<dyn CoreError + Send + Sync + 'static>>,
pub source: Option<StoredSource>,
#[cfg(feature = "backtrace")]
pub backtrace: Option<Arc<Backtrace>>,
#[cfg(feature = "backtrace")]
Expand Down Expand Up @@ -107,7 +109,7 @@ impl Display for Error {
}
if let Some(source) = &self.source {
writeln!(f)?;
let mut current: &dyn CoreError = source.as_ref();
let mut current: &dyn CoreError = source.as_dyn();
let mut depth = 0;
while depth < 10 {
write!(f, " {}: ", style::source_context("Caused by"))?;
Expand Down Expand Up @@ -135,8 +137,8 @@ impl Display for Error {
impl CoreError for Error {
fn source(&self) -> Option<&(dyn CoreError + 'static)> {
self.source
.as_deref()
.map(|source| source as &(dyn CoreError + 'static))
.as_ref()
.map(|source| source.as_dyn() as &(dyn CoreError + 'static))
}
}

Expand Down
Loading
Loading