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}