use core::error::Error;
use core::fmt;
use core::ops::Deref;
use core::panic::Location;
use parking_lot::Mutex;
use sealed::sealed;
use std::any::Any;
use std::borrow::Cow;
use std::collections::HashMap;
use std::io::Write as _;
use std::sync::{Arc, LazyLock};
static ERROR_COUNTS: LazyLock<Mutex<HashMap<&'static str, u64>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub(crate) const REPORTED_KEY: &str = "reported";
#[cold]
pub(crate) fn record_error(type_name: &'static str) {
*ERROR_COUNTS.lock().entry(type_name).or_insert(0) += 1;
}
#[cfg(feature = "metrics-facade")]
#[cold]
pub(crate) fn record_error_metrics(type_name: &'static str) {
metrics::counter!("fast_observe.errors", "type" => type_name).increment(1);
}
#[must_use]
pub fn error_counts() -> Vec<(&'static str, u64)> {
let mut v: Vec<(&'static str, u64)> =
ERROR_COUNTS.lock().iter().map(|(k, c)| (*k, *c)).collect();
v.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0)));
v
}
#[must_use]
pub fn error_counts_by_category() -> Vec<(Option<crate::ErrorCategory>, u64)> {
let counts = ERROR_COUNTS.lock();
let mut buckets: HashMap<Option<crate::ErrorCategory>, u64> = HashMap::new();
for (type_name, count) in counts.iter() {
*buckets
.entry(category_for_type_name(type_name))
.or_insert(0) += count;
}
let mut v: Vec<(Option<crate::ErrorCategory>, u64)> = buckets.into_iter().collect();
v.sort_by(|a, b| {
b.1.cmp(&a.1)
.then_with(|| category_key(a.0).cmp(category_key(b.0)))
});
v
}
fn category_for_type_name(type_name: &str) -> Option<crate::ErrorCategory> {
let leaf = type_name.rsplit("::").next().unwrap_or(type_name);
crate::errors::error_registry()
.find(|entry| entry.name == leaf)
.map(|entry| entry.category)
}
fn category_key(category: Option<crate::ErrorCategory>) -> &'static str {
category.map_or("", Into::into)
}
pub type BoxError = Box<dyn Error + Send + Sync + 'static>;
#[derive(Debug, Clone, PartialEq, Eq, Default, derive_more::Display)]
#[non_exhaustive]
pub enum Context {
#[default]
None,
#[display("{_0}")]
Scope(Cow<'static, str>),
#[display("tick {_0}")]
Tick(u64),
#[display("{_0} at tick {_1}")]
Entity(Cow<'static, str>, u64),
#[display("{_0}")]
Custom(Cow<'static, str>),
}
impl Context {
#[must_use]
pub fn scope(name: impl Into<Cow<'static, str>>) -> Self {
Self::Scope(name.into())
}
#[must_use]
pub const fn tick(s: u64) -> Self {
Self::Tick(s)
}
#[must_use]
pub fn entity(name: impl Into<Cow<'static, str>>, tick: u64) -> Self {
Self::Entity(name.into(), tick)
}
#[must_use]
pub fn custom(msg: impl Into<Cow<'static, str>>) -> Self {
Self::Custom(msg.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::AsRefStr)]
#[strum(serialize_all = "lowercase")]
pub enum FrameKind {
Source,
Wrap,
Attempt,
Batch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Placement {
#[default]
Inline,
Appendix,
Opaque,
Hidden,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinKey {
ScopePath,
ScopeElapsedMs,
TraceId,
SpanTrail,
Backtrace,
}
impl BuiltinKey {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ScopePath => "scope_path",
Self::ScopeElapsedMs => "scope_elapsed_ms",
Self::TraceId => "trace_id",
Self::SpanTrail => "span_trail",
Self::Backtrace => "backtrace",
}
}
}
pub struct Attachment {
key: Option<&'static str>,
display: String,
value: Arc<dyn Any + Send + Sync>,
placement: Placement,
}
impl Attachment {
#[must_use]
pub fn new(value: impl fmt::Display + Send + Sync + 'static) -> Self {
Self {
key: None,
display: value.to_string(),
value: Arc::new(value),
placement: Placement::Inline,
}
}
#[must_use]
pub fn with_key(key: &'static str, value: impl fmt::Display + Send + Sync + 'static) -> Self {
debug_assert!(
!key.is_empty() && !key.contains(['\n', '\r', '=']),
"attachment key {key:?} breaks the report's one-fact-per-line contract"
);
Self {
key: Some(key),
..Self::new(value)
}
}
#[must_use]
pub fn with_placement(mut self, placement: Placement) -> Self {
self.placement = placement;
self
}
#[must_use]
pub fn key(&self) -> Option<&'static str> {
self.key
}
#[must_use]
pub fn display(&self) -> &str {
&self.display
}
#[must_use]
pub fn placement(&self) -> Placement {
self.placement
}
#[must_use]
pub fn downcast<T: 'static>(&self) -> Option<&T> {
self.value.downcast_ref::<T>()
}
}
impl fmt::Debug for Attachment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Attachment")
.field("key", &self.key)
.field("display", &self.display)
.field("placement", &self.placement)
.finish_non_exhaustive()
}
}
impl fmt::Display for Attachment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.key {
Some(key) => write!(f, "{key}: {}", self.display),
None => f.write_str(&self.display),
}
}
}
#[derive(Debug)]
pub struct Frame {
pub(crate) error: BoxError,
pub(crate) location: &'static Location<'static>,
pub(crate) context: Context,
pub(crate) children: Vec<(FrameKind, Arc<Frame>)>,
pub(crate) type_name: &'static str,
pub(crate) attachments: Vec<Attachment>,
}
impl Frame {
fn new(
error: BoxError,
type_name: &'static str,
location: &'static Location<'static>,
context: Context,
children: Vec<(FrameKind, Arc<Frame>)>,
) -> Frame {
Frame {
error,
location,
context,
children,
type_name,
attachments: Vec::new(),
}
}
#[cold]
fn capture(
error: BoxError,
type_name: &'static str,
location: &'static Location<'static>,
) -> Arc<Frame> {
let context = crate::profiling::current_scope_name().map_or(Context::None, Context::Scope);
let children = walk_sources(&*error, location);
let mut frame = Frame::new(error, type_name, location, context, children);
crate::hook::run_capture_hooks(&mut frame);
let frame = Arc::new(frame);
crate::hook::invoke(&frame);
frame
}
#[must_use]
pub fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
&*self.error
}
#[must_use]
pub fn location(&self) -> &'static Location<'static> {
self.location
}
#[must_use]
pub fn context(&self) -> &Context {
&self.context
}
pub fn children(
&self,
) -> impl ExactSizeIterator<Item = &Arc<Frame>> + DoubleEndedIterator + '_ {
self.children.iter().map(|(_, frame)| frame)
}
pub fn child_edges(
&self,
) -> impl ExactSizeIterator<Item = (FrameKind, &Arc<Frame>)> + DoubleEndedIterator + '_ {
self.children.iter().map(|(kind, frame)| (*kind, frame))
}
#[must_use]
pub fn type_name(&self) -> &'static str {
self.type_name
}
#[must_use]
pub fn attachments(&self) -> &[Attachment] {
&self.attachments
}
#[must_use]
pub fn find_attachment<T: 'static>(&self) -> Option<&T> {
self.attachments.iter().find_map(Attachment::downcast::<T>)
}
#[must_use]
pub fn find_attachment_tree<T: 'static>(&self) -> Option<&T> {
fn walk<T: 'static>(frame: &Frame) -> Option<&T> {
frame.find_attachment::<T>().or_else(|| {
frame
.children
.iter()
.find_map(|(_, child)| walk::<T>(child))
})
}
walk::<T>(self)
}
pub fn push_attachment(&mut self, attachment: Attachment) {
self.attachments.push(attachment);
}
#[must_use]
pub fn iter(self: &Arc<Frame>) -> FrameIter {
FrameIter {
stack: vec![Arc::clone(self)],
}
}
}
pub struct FrameIter {
stack: Vec<Arc<Frame>>,
}
impl Iterator for FrameIter {
type Item = Arc<Frame>;
fn next(&mut self) -> Option<Self::Item> {
let frame = self.stack.pop()?;
for (_, child) in frame.children.iter().rev() {
self.stack.push(Arc::clone(child));
}
Some(frame)
}
}
#[cold]
fn frame_from_error(
error: BoxError,
type_name: &'static str,
location: &'static Location<'static>,
) -> Arc<Frame> {
let children = walk_sources(&*error, location);
Arc::new(Frame::new(
error,
type_name,
location,
Context::None,
children,
))
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)?;
if !matches!(self.context, Context::None) {
write!(f, " ({})", self.context)?;
}
Ok(())
}
}
impl Error for Frame {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.children.first().map(|(_, c)| c.as_ref() as &dyn Error)
}
fn provide<'a>(&'a self, request: &mut core::error::Request<'a>) {
self.error.provide(request);
request.provide_ref::<Context>(&self.context);
request.provide_ref::<Location>(self.location);
request.provide_ref::<&'static str>(&self.type_name);
request.provide_ref::<Frame>(self);
}
}
#[must_use = "a Fault is an error — return it, handle it, or swallow it explicitly via `ResultExt::report`"]
pub struct Fault<E: Send + Sync + Sized + 'static = BoxError> {
root: Arc<Frame>,
error: Arc<E>,
}
#[derive(Debug)]
struct SharedError<E: Error + Send + Sync + 'static>(Arc<E>);
impl<E: Error + Send + Sync + 'static> fmt::Display for SharedError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl<E: Error + Send + Sync + 'static> Error for SharedError<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.0.source()
}
fn provide<'a>(&'a self, request: &mut core::error::Request<'a>) {
self.0.provide(request);
}
}
#[derive(Debug)]
struct SharedBoxedError(Arc<BoxError>);
impl fmt::Display for SharedBoxedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl Error for SharedBoxedError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.0.source()
}
fn provide<'a>(&'a self, request: &mut core::error::Request<'a>) {
self.0.provide(request);
}
}
impl<E: Error + Send + Sync + Sized + 'static> From<E> for Fault<E> {
#[track_caller]
fn from(error: E) -> Self {
Fault::new(error)
}
}
impl Fault<BoxError> {
#[track_caller]
#[cold]
pub fn from_boxed(error: BoxError) -> Self {
Self::capture_boxed(error, Location::caller())
}
#[cold]
fn capture_boxed(error: BoxError, location: &'static Location<'static>) -> Self {
let type_name = std::any::type_name_of_val(&*error);
let error = Arc::new(error);
let root = Frame::capture(
Box::new(SharedBoxedError(Arc::clone(&error))),
type_name,
location,
);
Self { root, error }
}
}
impl From<&str> for Fault<BoxError> {
#[track_caller]
fn from(msg: &str) -> Self {
Self::from_boxed(internal_err(msg.to_string()))
}
}
impl From<String> for Fault<BoxError> {
#[track_caller]
fn from(msg: String) -> Self {
Self::from_boxed(internal_err(msg))
}
}
impl<E: Error + Send + Sync + Sized + 'static> Fault<E> {
#[track_caller]
#[cold]
pub fn new(error: E) -> Self {
Self::capture_typed(error, Location::caller())
}
#[cold]
fn capture_typed(error: E, location: &'static Location<'static>) -> Self {
let error = Arc::new(error);
let root = Frame::capture(
Box::new(SharedError(Arc::clone(&error))),
std::any::type_name::<E>(),
location,
);
Self { root, error }
}
}
impl<E: Send + Sync + Sized + 'static> Fault<E> {
#[must_use]
pub fn frame(&self) -> &Frame {
&self.root
}
#[must_use]
pub fn iter(&self) -> FrameIter {
self.root.iter()
}
#[must_use]
pub fn into_frame(self) -> Arc<Frame> {
self.root
}
#[must_use]
pub fn policy(&self) -> Option<crate::Policy> {
frame_category(&self.root).map(crate::ErrorCategory::policy)
}
#[must_use]
pub fn exit_code(&self) -> std::process::ExitCode {
std::process::ExitCode::from(self.exit_code_raw())
}
fn exit_code_raw(&self) -> u8 {
const EX_DATAERR: u8 = 65;
const EX_SOFTWARE: u8 = 70;
const EX_TEMPFAIL: u8 = 75;
const EX_GENERAL: u8 = 1;
match frame_category(&self.root) {
Some(crate::ErrorCategory::Content) => EX_DATAERR,
Some(crate::ErrorCategory::Transient) => EX_TEMPFAIL,
Some(_) => EX_SOFTWARE,
None => EX_GENERAL,
}
}
pub fn set_context(mut self, ctx: Context) -> Self {
if let Some(frame) = Arc::get_mut(&mut self.root) {
frame.context = ctx;
} else {
debug_assert!(
Arc::strong_count(&self.root) == 1,
"set_context on a shared Fault root — context would be lost"
);
}
self
}
pub fn attach<A: fmt::Display + Send + Sync + 'static>(self, value: A) -> Self {
self.attach_inner(None, value, Placement::Inline)
}
pub fn attach_key<A: fmt::Display + Send + Sync + 'static>(
self,
key: &'static str,
value: A,
) -> Self {
self.attach_inner(Some(key), value, Placement::Inline)
}
pub fn attach_placed<A: fmt::Display + Send + Sync + 'static>(
self,
value: A,
placement: Placement,
) -> Self {
self.attach_inner(None, value, placement)
}
fn attach_inner<A: fmt::Display + Send + Sync + 'static>(
mut self,
key: Option<&'static str>,
value: A,
placement: Placement,
) -> Self {
let attachment = match key {
Some(key) => Attachment::with_key(key, value).with_placement(placement),
None => Attachment::new(value).with_placement(placement),
};
if let Some(frame) = Arc::get_mut(&mut self.root) {
frame.attachments.push(attachment);
} else {
debug_assert!(
Arc::strong_count(&self.root) == 1,
"attach on a shared Fault root — attachment would be lost"
);
}
self
}
#[must_use]
pub fn find_attachment<T: 'static>(&self) -> Option<&T> {
self.root.find_attachment::<T>()
}
#[must_use]
pub fn find_attachment_tree<T: 'static>(&self) -> Option<&T> {
self.root.find_attachment_tree::<T>()
}
#[must_use]
pub fn context(&self) -> &Context {
&self.root.context
}
#[must_use]
pub fn root_cause(&self) -> Arc<Frame> {
let mut frame = Arc::clone(&self.root);
loop {
let Some(first) = frame.children().next().cloned() else {
break;
};
frame = first;
}
frame
}
#[track_caller]
#[cold]
pub fn wrap<T: Error + Send + Sync + Sized + 'static>(self, err: T) -> Fault<T> {
let mut fault = Fault::capture_typed(err, Location::caller());
if let Some(root) = Arc::get_mut(&mut fault.root) {
root.children.push((FrameKind::Wrap, self.root));
}
fault
}
}
impl<E: Send + Sync + Sized + 'static> std::process::Termination for Fault<E> {
fn report(self) -> std::process::ExitCode {
self.exit_with_report()
}
}
impl<E: Send + Sync + Sized + 'static> Fault<E> {
pub fn exit_with_report(self) -> ! {
let _ = std::io::stderr().write_fmt(format_args!("{}", crate::report_display(&self)));
crate::flush();
log::logger().flush();
std::process::exit(i32::from(self.exit_code_raw()))
}
}
pub(crate) fn frame_category(frame: &Frame) -> Option<crate::ErrorCategory> {
if let Some(tag) = core::error::request_value::<crate::errors::CategoryTag>(frame.error()) {
return Some(tag.0);
}
core::error::request_value::<crate::errors::ErrorCode>(frame.error())
.and_then(|code| crate::errors::lookup_error(code.0))
.map(|entry| entry.category)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Backoff {
None,
Fixed(std::time::Duration),
Exponential {
base: std::time::Duration,
factor: u32,
max: std::time::Duration,
},
}
impl Backoff {
const MAX_EXPONENT: usize = 20;
pub fn schedule(self) -> impl Iterator<Item = Option<std::time::Duration>> {
(1..).map(move |attempt| self.delay(attempt))
}
#[must_use]
fn delay(&self, attempt: usize) -> Option<std::time::Duration> {
match self {
Self::None => None,
Self::Fixed(d) => Some(*d),
Self::Exponential { base, factor, max } => {
let factor = u128::from((*factor).max(2));
#[allow(
clippy::cast_possible_truncation,
reason = "the exponent is capped at MAX_EXPONENT, far below u32::MAX"
)]
let exp = (attempt - 1).min(Self::MAX_EXPONENT) as u32;
let mult = factor.saturating_pow(exp);
let delay_ns = base.as_nanos().saturating_mul(mult);
#[allow(
clippy::cast_possible_truncation,
reason = "value is clamped to u64::MAX immediately before the cast"
)]
let delay =
std::time::Duration::from_nanos(delay_ns.min(u128::from(u64::MAX)) as u64);
Some(delay.min(*max))
}
}
}
}
#[track_caller]
pub fn retry_with_backoff<T, E, F, S>(
label: &'static str,
max_attempts: usize,
backoff: Backoff,
mut sleep: S,
mut f: F,
) -> Result<T, E>
where
E: Error + Send + Sync + Sized + 'static,
F: FnMut() -> core::result::Result<T, E>,
S: FnMut(std::time::Duration),
{
let max_attempts = max_attempts.max(1);
let mut collection = FaultCollection::new();
let mut schedule = backoff.schedule();
let mut attempts = 0usize;
loop {
match f() {
Ok(v) => return Ok(v),
Err(e) => {
let mut fault = Fault::new(e);
attempts += 1;
if fault.policy() == Some(crate::Policy::Retry) && attempts < max_attempts {
collection.push(fault);
let delay = schedule.next().unwrap_or(None);
if let Some(delay) = delay {
sleep(delay);
}
continue;
}
if !collection.is_empty()
&& let Some(root) = Arc::get_mut(&mut fault.root)
{
root.context = Context::Custom(
format!("{label}: failed after {attempts} attempts").into(),
);
root.children.extend(
collection
.frames
.drain(..)
.map(|frame| (FrameKind::Attempt, frame)),
);
}
return Err(fault);
}
}
}
}
#[track_caller]
pub fn retry_with_policy<T, E, F>(label: &'static str, max_attempts: usize, f: F) -> Result<T, E>
where
E: Error + Send + Sync + Sized + 'static,
F: FnMut() -> core::result::Result<T, E>,
{
retry_with_backoff(label, max_attempts, Backoff::None, |_| {}, f)
}
impl<E: Send + Sync + Sized + 'static> IntoIterator for &Fault<E> {
type Item = Arc<Frame>;
type IntoIter = FrameIter;
fn into_iter(self) -> FrameIter {
self.iter()
}
}
impl<E: Send + Sync + Sized + 'static> Deref for Fault<E> {
type Target = E;
fn deref(&self) -> &E {
&self.error
}
}
impl<E: Error + Send + Sync + Sized + 'static> Error for Fault<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.root.source()
}
}
impl<E: Send + Sync + Sized + 'static> fmt::Display for Fault<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.root.error)?;
if !matches!(self.root.context, Context::None) {
write!(f, " ({})", self.root.context)?;
}
Ok(())
}
}
pub type Result<T, E = BoxError> = core::result::Result<T, Fault<E>>;
#[derive(Default)]
pub struct FaultCollection {
frames: Vec<Arc<Frame>>,
}
impl FaultCollection {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push<E: Send + Sync + Sized + 'static>(&mut self, fault: Fault<E>) {
self.frames.push(fault.into_frame());
}
#[must_use]
pub fn len(&self) -> usize {
self.frames.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
#[track_caller]
pub fn into_fault<T: Error + Send + Sync + Sized + 'static>(self, err: T) -> Fault<T> {
let mut fault = Fault::new(err);
if let Some(root) = Arc::get_mut(&mut fault.root) {
root.children
.extend(self.frames.into_iter().map(|f| (FrameKind::Batch, f)));
}
fault
}
#[track_caller]
pub fn into_fault_msg(self, msg: impl Into<Cow<'static, str>>) -> Fault {
let mut fault = Fault::from_boxed(internal_err(msg));
if let Some(root) = Arc::get_mut(&mut fault.root) {
root.children
.extend(self.frames.into_iter().map(|f| (FrameKind::Batch, f)));
}
fault
}
}
impl<E: Send + Sync + Sized + 'static> FromIterator<Fault<E>> for FaultCollection {
fn from_iter<I: IntoIterator<Item = Fault<E>>>(iter: I) -> Self {
let mut collection = Self::new();
collection
.frames
.extend(iter.into_iter().map(Fault::into_frame));
collection
}
}
#[diagnostic::on_unimplemented(
message = "implement `ErrorExt` via `error!` or on your own error type",
note = "ErrorExt is sealed; it is implemented for faults and error!-generated types"
)]
#[sealed]
pub trait ErrorExt: Error + Send + Sync + Sized + 'static {
#[track_caller]
fn raise(self) -> Fault<Self> {
Fault::new(self)
}
}
impl<T: Error + Send + Sync + Sized + 'static> __seal_error_ext::Sealed for T {}
impl<T: Error + Send + Sync + Sized + 'static> ErrorExt for T {}
#[diagnostic::on_unimplemented(
message = "`ResultExt` methods are available on `fast_observe::Result` and `Result<T, E>` where E implements the fault contract",
note = "if you are calling `.report()`/`.wrap_msg()` on a plain std Result, convert with `Fault::new`/`error!` first"
)]
#[sealed]
pub trait ResultExt {
type Success;
type Error: Error + Send + Sync + Sized + 'static;
#[track_caller]
fn change_context<A>(self, new_err: A) -> Result<Self::Success, A>
where
A: Error + Send + Sync + Sized + 'static;
#[track_caller]
fn context(self, msg: impl Into<Cow<'static, str>>) -> Result<Self::Success>;
#[track_caller]
fn with_context(self, f: impl FnOnce() -> Cow<'static, str>) -> Result<Self::Success>;
#[track_caller]
fn wrap_msg(self, msg: impl Into<Cow<'static, str>>) -> Result<Self::Success>;
#[track_caller]
fn observed(self, msg: impl Into<Cow<'static, str>>) -> Result<Self::Success, Self::Error>;
#[track_caller]
fn attach<A: fmt::Display + Send + Sync + 'static>(
self,
value: A,
) -> Result<Self::Success, Self::Error>;
#[track_caller]
fn attach_with<A: fmt::Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> A,
) -> Result<Self::Success, Self::Error>;
#[track_caller]
fn report(self, msg: impl Into<Cow<'static, str>>) -> Option<Self::Success>;
}
impl<T, E: Error + Send + Sync + Sized + 'static> __seal_result_ext::Sealed
for core::result::Result<T, E>
{
}
impl<T, E: Error + Send + Sync + Sized + 'static> ResultExt for core::result::Result<T, E> {
type Success = T;
type Error = E;
#[track_caller]
#[cold]
fn change_context<A>(self, new_err: A) -> Result<T, A>
where
A: Error + Send + Sync + Sized + 'static,
{
match self {
Ok(v) => Ok(v),
Err(e) => Err(Fault::new(e).wrap(new_err)),
}
}
#[track_caller]
#[cold]
fn context(self, msg: impl Into<Cow<'static, str>>) -> Result<T> {
match self {
Ok(v) => Ok(v),
Err(e) => {
let location = Location::caller();
let child = frame_from_error(Box::new(e), std::any::type_name::<E>(), location);
let mut fault = Fault::capture_boxed(internal_err(msg), location);
if let Some(root) = Arc::get_mut(&mut fault.root) {
root.children.push((FrameKind::Wrap, child));
}
Err(fault)
}
}
}
#[track_caller]
#[cold]
fn with_context(self, f: impl FnOnce() -> Cow<'static, str>) -> Result<T> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(e).context(f()),
}
}
#[track_caller]
#[cold]
fn wrap_msg(self, msg: impl Into<Cow<'static, str>>) -> Result<T> {
self.context(msg)
}
#[track_caller]
#[cold]
fn observed(self, msg: impl Into<Cow<'static, str>>) -> Result<T, E> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(Fault::new(e).set_context(Context::Custom(msg.into()))),
}
}
#[track_caller]
#[cold]
fn attach<A: fmt::Display + Send + Sync + 'static>(self, value: A) -> Result<T, E> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(Fault::new(e).attach(value)),
}
}
#[track_caller]
#[cold]
fn attach_with<A: fmt::Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> A,
) -> Result<T, E> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(Fault::new(e).attach(f())),
}
}
#[track_caller]
fn report(self, msg: impl Into<Cow<'static, str>>) -> Option<T> {
match self {
Ok(v) => Some(v),
Err(e) => {
record_error(REPORTED_KEY);
let location = Location::caller();
log::warn!(
target: crate::log_targets::ERROR,
"{}: {} (reported at {}:{})",
msg.into(),
e,
location.file(),
location.line(),
);
None
}
}
}
}
#[diagnostic::on_unimplemented(
message = "`OptionExt` is sealed and implemented for `Option<T>`",
note = "use `Option::ok_or`/`ok_or_else` for a plain Option"
)]
#[sealed]
pub trait OptionExt {
type Some;
#[track_caller]
fn ok_or_msg(self, msg: impl Into<Cow<'static, str>>) -> Result<Self::Some>;
}
impl<T> __seal_option_ext::Sealed for Option<T> {}
impl<T> OptionExt for Option<T> {
type Some = T;
#[track_caller]
fn ok_or_msg(self, msg: impl Into<Cow<'static, str>>) -> Result<T> {
match self {
Some(v) => Ok(v),
None => Err(Fault::from_boxed(internal_err(msg))),
}
}
}
pub(crate) fn payload_str<'a>(payload: &'a (dyn Any + Send + 'static)) -> Option<&'a str> {
payload
.downcast_ref::<&'static str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
}
#[derive(Debug)]
pub struct InternalError(Cow<'static, str>);
fn internal_err(msg: impl Into<Cow<'static, str>>) -> BoxError {
Box::new(InternalError(msg.into()))
}
impl fmt::Display for InternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Error for InternalError {}
#[macro_export]
macro_rules! bail {
($type:ident, $fmt:literal $(, $arg:expr)* $(,)?) => {{
return ::core::result::Result::Err($crate::exn::Fault::from(
$type { detail: format!($fmt $(, $arg)*) }
));
}};
($fmt:literal $(, $arg:expr)* $(,)?) => {{
return ::core::result::Result::Err($crate::exn::Fault::from(
::std::format!($fmt $(, $arg)*)
));
}};
($err:expr) => {{ return ::core::result::Result::Err($crate::exn::Fault::from($err)); }};
}
#[macro_export]
macro_rules! ensure {
($cond:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {{
if !($cond) {
$crate::bail!($fmt $(, $arg)*)
}
}};
($cond:expr, $err:expr $(,)?) => {{
if !($cond) {
$crate::bail!($err)
}
}};
}
#[cold]
fn walk_sources(
error: &(dyn Error + 'static),
location: &'static Location<'static>,
) -> Vec<(FrameKind, Arc<Frame>)> {
let mut chain = Vec::new();
for src in error.sources().skip(1) {
chain.push((std::any::type_name_of_val(src), src.to_string()));
}
let mut children = Vec::new();
for (type_name, msg) in chain.into_iter().rev() {
children = vec![(
FrameKind::Source,
Arc::new(Frame::new(
Box::new(InternalError(msg.into())),
type_name,
location,
Context::None,
children,
)),
)];
}
children
}
impl<E: Send + Sync + Sized + 'static> fmt::Debug for Fault<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_fault(f, &self.root, "")
}
}
fn write_fault(f: &mut fmt::Formatter<'_>, root: &Frame, prefix: &str) -> fmt::Result {
fn write_frame_line(f: &mut fmt::Formatter<'_>, frame: &Frame) -> fmt::Result {
if let Some(code) = core::error::request_value::<crate::errors::ErrorCode>(frame.error()) {
write!(f, "[{}] ", code.0)?;
}
write!(f, "{}", frame.error)?;
let loc = frame.location;
write!(f, ", at {}:{}:{}", loc.file(), loc.line(), loc.column())?;
if !matches!(frame.context, Context::None) {
write!(f, " [{}]", frame.context)?;
}
let inline_count = frame
.attachments
.iter()
.filter(|a| a.placement == Placement::Inline)
.count();
let deferred = frame.attachments.len() - inline_count;
if deferred > 0 {
write!(f, " (+{deferred} more attachments)")?;
}
Ok(())
}
struct Work<'a> {
frame: &'a Frame,
prefix: String,
last: bool,
is_root: bool,
}
let mut stack = vec![Work {
frame: root,
prefix: prefix.to_string(),
last: true,
is_root: true,
}];
while let Some(work) = stack.pop() {
let Work {
frame,
prefix,
last,
is_root,
} = work;
let own_prefix = if is_root {
prefix.clone()
} else {
write!(f, "\n{prefix}{}", if last { "`-- " } else { "|-- " })?;
format!("{prefix}{}", if last { " " } else { "| " })
};
write_frame_line(f, frame)?;
let inline: Vec<&Attachment> = frame
.attachments
.iter()
.filter(|a| a.placement == Placement::Inline)
.collect();
let total = inline.len() + frame.children.len();
for (i, attachment) in inline.iter().enumerate() {
let connector = if i + 1 == total { "`-- " } else { "|-- " };
write!(f, "\n{own_prefix}{connector}* {attachment}")?;
}
for (i, (_, child)) in frame.children.iter().enumerate().rev() {
let last = inline.len() + i + 1 == total;
stack.push(Work {
frame: child,
prefix: own_prefix.clone(),
last,
is_root: false,
});
}
}
Ok(())
}