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}