Skip to main content

gix_error/concrete/
classify.rs

1use std::fmt::{Debug, Display, Formatter};
2
3use crate::Class;
4
5/// A transparent classification marker with an optional owned source and no diagnostic of its own.
6///
7/// Unlike [`Message`](crate::Message), markers only supply classification metadata, never a visible
8/// diagnostic. Use [`Message`](crate::Message) to combine a message, class, and scalar values in one causal error;
9/// use a marker to classify an existing concrete error without changing its diagnostic or recovery payload.
10/// [`Self::with_source()`] adds a classification to an existing error while preserving its concrete type and diagnostic.
11/// The associated constants, such as [`Self::NOT_FOUND`], are owned class-only markers. A custom error can return
12/// `Some(const { &ClassificationMarker::NOT_FOUND })` from its [`source()`](std::error::Error::source)
13/// implementation without defining a static.
14/// [`Self::with_class()`] creates an owned class-only marker, including for [`Class::Io`] with a specific I/O kind.
15///
16/// Diagnostic iterators, downcasts, cause selection, and exception/test reports skip all markers,
17/// retaining their real descendants. [`crate::classify()`] still inspects markers. Raw standard-error sources can still
18/// expose markers. A report with only class-only markers falls back to displaying the root classification.
19/// If cause selection cannot choose a unique real descendant, it can likewise fall back to the stored marker root.
20/// Preserve real [`std::io::Error`] sources: the marker itself has no I/O origin for [`crate::types::Classification::io_kind()`].
21pub struct ClassificationMarker {
22    class: Class,
23    source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
24}
25
26impl ClassificationMarker {
27    /// A hidden marker for invalid input.
28    pub const VALIDATION: Self = Self::with_class(Class::Validation);
29    /// A hidden marker for malformed or internally inconsistent data.
30    pub const CORRUPTION: Self = Self::with_class(Class::Corruption);
31    /// A hidden marker for a requested resource that does not exist.
32    pub const NOT_FOUND: Self = Self::with_class(Class::NotFound);
33    /// A hidden marker for an operation which may succeed when retried.
34    pub const RETRYABLE: Self = Self::with_class(Class::Retryable);
35    /// A hidden marker for an application-configured allocation limit being exceeded.
36    pub const ALLOCATION_LIMIT: Self =
37        Self::with_class(Class::ResourceExhaustion(ResourceExhaustionKind::AllocationLimit));
38    /// A hidden marker for an unrepresentable allocation size or memory that could not be reserved.
39    pub const ALLOCATION_FAILURE: Self =
40        Self::with_class(Class::ResourceExhaustion(ResourceExhaustionKind::AllocationFailure));
41
42    /// Add `class` to `source`, preserving its concrete type and diagnostic without a visible wrapper.
43    pub fn with_source(class: Class, source: impl std::error::Error + Send + Sync + 'static) -> Self {
44        ClassificationMarker {
45            class,
46            source: Some(Box::new(source)),
47        }
48    }
49
50    /// Create a hidden metadata leaf, suitable for a custom error's static source.
51    /// Prefer the associated constants, such as [`Self::NOT_FOUND`], for fixed classifications.
52    pub const fn with_class(class: Class) -> Self {
53        ClassificationMarker { class, source: None }
54    }
55
56    /// Return the classification supplied by this marker.
57    pub fn class(&self) -> Class {
58        self.class
59    }
60}
61
62impl Display for ClassificationMarker {
63    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
64        match &self.source {
65            Some(source) => Display::fmt(source, f),
66            None => Debug::fmt(&self.class, f),
67        }
68    }
69}
70
71impl Debug for ClassificationMarker {
72    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73        match &self.source {
74            Some(source) => Debug::fmt(source, f),
75            None => Display::fmt(self, f),
76        }
77    }
78}
79
80impl std::error::Error for ClassificationMarker {
81    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
82        self.source.as_deref().map(|source| source as _)
83    }
84}
85
86/// The kind of resource exhaustion which prevented an operation from completing.
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88#[non_exhaustive]
89pub enum ResourceExhaustionKind {
90    /// An application-configured allocation limit was exceeded.
91    AllocationLimit,
92    /// An allocation size could not be represented or memory could not be reserved.
93    AllocationFailure,
94}