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///
11/// For a leaf error or variant you define, prefer encoding its intrinsic classification in its
12/// [`source()`](std::error::Error::source) implementation. The associated constants, such as [`Self::NOT_FOUND`],
13/// are owned class-only markers: return `Some(const { &ClassificationMarker::NOT_FOUND })` without defining a static.
14/// This classifies every construction site without repeated tagging.
15/// [`Self::with_class()`] creates an owned class-only marker.
16/// Use [`crate::tag()`] for classifications that depend on the calling context or for error types you cannot modify,
17/// preserving the concrete type and diagnostic.
18///
19/// Diagnostic iterators, downcasts, cause selection, and exception/test reports skip all markers,
20/// retaining their real descendants. [`crate::classify()`] still inspects markers. Raw standard-error sources can still
21/// expose markers. A report with only class-only markers falls back to displaying the root classification.
22/// Conversion from [`crate::Exn`] to `anyhow::Error` skips markers, retaining that fallback for marker-only errors.
23/// If cause selection cannot choose a unique real descendant, it can likewise fall back to the stored marker root.
24/// Preserve real [`std::io::Error`] sources: the marker itself has no I/O origin for [`crate::types::Classification::io_kind()`].
25pub struct ClassificationMarker {
26    class: Class,
27    source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
28}
29
30/// Add `class` to `err`, preserving its concrete type and diagnostic without a visible wrapper.
31///
32/// Use this when the classification depends on the calling context, or when you cannot modify the error type.
33/// For intrinsic classifications on leaf errors or variants you define, prefer returning a constant marker such as
34/// [`ClassificationMarker::NOT_FOUND`] from [`std::error::Error::source()`] instead of tagging every construction site.
35/// Preserve genuine callee errors as sources rather than replacing them with class-only markers.
36///
37/// The returned marker identifies `err` through [`crate::types::Classification::error()`].
38/// Use a concrete error's variants for specific recovery decisions, and [`Class`] for broad categorization.
39pub fn tag(err: impl std::error::Error + Send + Sync + 'static, class: Class) -> ClassificationMarker {
40    ClassificationMarker::with_source(class, err)
41}
42
43impl ClassificationMarker {
44    /// A hidden marker for invalid input.
45    pub const VALIDATION: Self = Self::with_class(Class::Validation);
46    /// A hidden marker for malformed or internally inconsistent data.
47    pub const CORRUPTION: Self = Self::with_class(Class::Corruption);
48    /// A hidden marker for a requested resource that does not exist.
49    pub const NOT_FOUND: Self = Self::with_class(Class::NotFound);
50    /// A hidden marker for an operation which may succeed when retried.
51    pub const RETRYABLE: Self = Self::with_class(Class::Retryable);
52    /// The caller requested cancellation; stop rather than retry.
53    pub const CANCELLED: Self = Self::with_class(Class::Cancelled);
54    /// Authorization or permissions are insufficient; obtain authorization or change permissions.
55    pub const PERMISSION_DENIED: Self = Self::with_class(Class::PermissionDenied);
56    /// Credentials are missing or rejected; obtain or refresh credentials.
57    pub const UNAUTHENTICATED: Self = Self::with_class(Class::Unauthenticated);
58    /// Current state conflicts with the operation; refresh or reconcile state before retrying.
59    pub const CONFLICT: Self = Self::with_class(Class::Conflict);
60    /// A required capability is unsupported; switch implementation, format, protocol, or strategy.
61    pub const UNSUPPORTED: Self = Self::with_class(Class::Unsupported);
62    /// A hidden marker for an application-configured allocation limit being exceeded.
63    pub const ALLOCATION_LIMIT: Self =
64        Self::with_class(Class::ResourceExhaustion(ResourceExhaustionKind::AllocationLimit));
65    /// A hidden marker for an unrepresentable allocation size or memory that could not be reserved.
66    pub const ALLOCATION_FAILURE: Self =
67        Self::with_class(Class::ResourceExhaustion(ResourceExhaustionKind::AllocationFailure));
68
69    /// Add `class` to `source`, preserving its concrete type and diagnostic without a visible wrapper.
70    pub fn with_source(class: Class, source: impl std::error::Error + Send + Sync + 'static) -> Self {
71        ClassificationMarker {
72            class,
73            source: Some(Box::new(source)),
74        }
75    }
76
77    /// Create a hidden metadata leaf, suitable for a custom error's static source.
78    /// Prefer the associated constants, such as [`Self::NOT_FOUND`], for fixed classifications.
79    pub const fn with_class(class: Class) -> Self {
80        ClassificationMarker { class, source: None }
81    }
82
83    /// Return the classification supplied by this marker.
84    pub fn class(&self) -> Class {
85        self.class
86    }
87}
88
89impl Display for ClassificationMarker {
90    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91        match &self.source {
92            Some(source) => Display::fmt(source, f),
93            None => Debug::fmt(&self.class, f),
94        }
95    }
96}
97
98impl Debug for ClassificationMarker {
99    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100        match &self.source {
101            Some(source) => Debug::fmt(source, f),
102            None => Display::fmt(self, f),
103        }
104    }
105}
106
107impl std::error::Error for ClassificationMarker {
108    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
109        self.source.as_deref().map(|source| source as _)
110    }
111}
112
113/// The kind of resource exhaustion which prevented an operation from completing.
114#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
115#[non_exhaustive]
116pub enum ResourceExhaustionKind {
117    /// An application-configured allocation limit was exceeded.
118    AllocationLimit,
119    /// An allocation size could not be represented or memory could not be reserved.
120    AllocationFailure,
121}