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