Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 0 additions & 7 deletions clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,3 @@ accept-comment-above-statement = true
accept-comment-above-attributes = true
allow-panic-in-tests = true
allow-unwrap-in-tests = true
# Raised from the default of 128 to accommodate the `&'static core::panic::Location<'static>`
# field that `ironrdp_error::Error<Kind>` captures via `#[track_caller]` for diagnostic
# reporting. The location field adds one usize (8 bytes) to every error type, which pushes
# `Error<ConnectorErrorKind>` (whose largest variant nests another `Error<Kind>`) just over
# the 128-byte threshold. The trade-off favours diagnostic quality over the small
# Result-Ok-path cost.
large-error-threshold = 152
115 changes: 76 additions & 39 deletions crates/ironrdp-error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,41 @@ extern crate alloc;
use alloc::boxed::Box;
use core::fmt;

#[cfg(feature = "std")]
pub trait Source: core::error::Error + Sync + Send + 'static {}

#[cfg(feature = "std")]
impl<T> Source for T where T: core::error::Error + Sync + Send + 'static {}

#[cfg(not(feature = "std"))]
pub trait Source: fmt::Display + fmt::Debug + Send + Sync + 'static {}
pub trait Source: core::error::Error + Send + Sync + 'static {}

#[cfg(not(feature = "std"))]
impl<T> Source for T where T: fmt::Display + fmt::Debug + Send + Sync + 'static {}
impl<T> Source for T where T: core::error::Error + Send + Sync + 'static {}
Comment thread
CBenoit marked this conversation as resolved.

pub struct Error<Kind> {
/// Diagnostic metadata stored behind a [`Box`] so that `Error<Kind>` stays small.
///
/// All fields here are purely for display and error-chain traversal; none are
/// needed for matching on the error kind. The allocation only occurs when an
/// error is *constructed* — a cold path — so the per-error heap cost is
/// acceptable.
#[cfg(feature = "alloc")]
struct ErrorMeta {
context: &'static str,
location: &'static core::panic::Location<'static>,
kind: Kind,
#[cfg(feature = "std")]
source: Option<Box<dyn core::error::Error + Sync + Send>>,
#[cfg(all(not(feature = "std"), feature = "alloc"))]
source: Option<Box<dyn Source>>,
}

/// A typed error wrapper carrying a `Kind` discriminant plus diagnostic metadata.
///
/// # `no_alloc` platforms
///
/// When compiled without the `alloc` feature, `Error<Kind>` retains only
/// `kind` and `context`; the call-site location and error source chain are
/// unavailable. This is intentional: `no_alloc` targets are supported on a
/// best-effort basis and are not a primary target of this crate.
pub struct Error<Kind> {
kind: Kind,
/// Diagnostic metadata. Present only when `alloc` is available.
#[cfg(feature = "alloc")]
meta: Box<ErrorMeta>,
/// Minimal context kept for `no_alloc` targets (no location, no source).
#[cfg(not(feature = "alloc"))]
context: &'static str,
}

// Manual `Debug` impl that excludes the `location` field. The location is
// captured via `core::panic::Location::caller()` and rendered in `Display`,
// but its `file()` returns platform-native paths (`/` on Unix, `\` on
Expand All @@ -39,9 +52,12 @@ pub struct Error<Kind> {
impl<Kind: fmt::Debug> fmt::Debug for Error<Kind> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dbg = f.debug_struct("Error");
#[cfg(feature = "alloc")]
dbg.field("context", &self.meta.context)
.field("kind", &self.kind)
.field("source", &self.meta.source);
#[cfg(not(feature = "alloc"))]
dbg.field("context", &self.context).field("kind", &self.kind);
#[cfg(any(feature = "std", feature = "alloc"))]
dbg.field("source", &self.source);
dbg.finish()
}
}
Expand All @@ -52,11 +68,15 @@ impl<Kind> Error<Kind> {
#[track_caller]
pub fn new(context: &'static str, kind: Kind) -> Self {
Self {
context,
location: core::panic::Location::caller(),
kind,
#[cfg(feature = "alloc")]
source: None,
meta: Box::new(ErrorMeta {
context,
location: core::panic::Location::caller(),
source: None,
}),
#[cfg(not(feature = "alloc"))]
context,
}
}

Expand All @@ -69,11 +89,11 @@ impl<Kind> Error<Kind> {
#[cfg(feature = "alloc")]
{
let mut this = self;
this.source = Some(Box::new(source));
this.meta.source = Some(Box::new(source));
this
}

// No source when no std and no alloc crates
// No source when no alloc
#[cfg(not(feature = "alloc"))]
{
let _ = source;
Expand All @@ -86,11 +106,11 @@ impl<Kind> Error<Kind> {
Kind: Into<OtherKind>,
{
Error {
context: self.context,
location: self.location,
kind: self.kind.into(),
#[cfg(any(feature = "std", feature = "alloc"))]
source: self.source,
#[cfg(feature = "alloc")]
meta: self.meta,
#[cfg(not(feature = "alloc"))]
context: self.context,
}
}

Expand All @@ -103,12 +123,22 @@ impl<Kind> Error<Kind> {
/// Captured automatically by [`Error::new`] via [`core::panic::Location::caller`]
/// and `#[track_caller]`. Useful for diagnostic logging and error reporting
/// when the variant alone does not narrow down the call site enough.
///
/// Not available on `no_alloc` platforms (see crate-level documentation).
#[cfg(feature = "alloc")]
pub fn location(&self) -> &'static core::panic::Location<'static> {
self.location
self.meta.location
Comment thread
CBenoit marked this conversation as resolved.
Outdated
}

pub fn set_context(&mut self, context: &'static str) {
self.context = context;
#[cfg(feature = "alloc")]
{
self.meta.context = context;
}
#[cfg(not(feature = "alloc"))]
{
self.context = context;
}
}

pub fn report(&self) -> ErrorReport<'_, Kind> {
Expand All @@ -121,14 +151,21 @@ where
Kind: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{} @ {}:{}] {}",
self.context,
self.location.file(),
self.location.line(),
self.kind
)
#[cfg(feature = "alloc")]
{
write!(
f,
"[{} @ {}:{}] {}",
self.meta.context,
self.meta.location.file(),
self.meta.location.line(),
self.kind
)
}
#[cfg(not(feature = "alloc"))]
{
write!(f, "[{}] {}", self.context, self.kind)
}
}
}

Expand All @@ -141,8 +178,8 @@ where
if let Some(source) = self.kind.source() {
Some(source)
} else {
// NOTE: we cant use Option::as_ref here because of type inference
if let Some(e) = &self.source {
// NOTE: we can't use Option::as_ref here because of type inference
if let Some(e) = &self.meta.source {
Some(e.as_ref())
} else {
None
Expand Down Expand Up @@ -193,7 +230,7 @@ where
write!(f, "{}", self.0)?;

#[cfg(feature = "alloc")]
if let Some(source) = &self.0.source {
if let Some(source) = &self.0.meta.source {
write!(f, ", caused by: {source}")?;
}

Expand Down
Loading