klauthed_error/lib.rs
1#![deny(unsafe_code)]
2#![deny(missing_docs)]
3#![cfg_attr(
4 not(test),
5 deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::indexing_slicing)
6)]
7
8//! The klauthed error **kernel**.
9//!
10//! This crate deliberately holds no error *types* — those live with the domains
11//! that raise them (`ConfigError` in `klauthed-core`, `DataError` in
12//! `klauthed-data`, …). Instead it defines the shared *contract* every klauthed
13//! error implements, so the whole system classifies, codes, and surfaces errors
14//! the same way:
15//!
16//! * [`ErrorCategory`] — coarse classification driving HTTP status / retryability.
17//! * [`ErrorCode`] — a stable `domain.reason` code for logs and API responses.
18//! * [`DomainError`] — the trait each concrete error type implements.
19//!
20//! It has zero required dependencies (enable the `serde` feature to serialize
21//! codes/categories), so everything can depend on it without pulling weight and
22//! without creating cycles: types stay home, only the contract is shared.
23//!
24//! ```
25//! use klauthed_error::{DomainError, ErrorCategory, ErrorCode};
26//!
27//! #[derive(Debug)]
28//! struct WidgetMissing(String);
29//! impl std::fmt::Display for WidgetMissing {
30//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31//! write!(f, "widget {} not found", self.0)
32//! }
33//! }
34//! impl std::error::Error for WidgetMissing {}
35//!
36//! impl DomainError for WidgetMissing {
37//! fn category(&self) -> ErrorCategory { ErrorCategory::NotFound }
38//! fn code(&self) -> ErrorCode { ErrorCode::new("widget.not_found") }
39//! }
40//!
41//! let err = WidgetMissing("w-1".into());
42//! assert_eq!(err.http_status(), 404);
43//! assert!(!err.is_retryable());
44//! ```
45
46mod category;
47mod code;
48
49pub use category::ErrorCategory;
50pub use code::ErrorCode;
51
52/// The contract every klauthed error type implements.
53///
54/// Concrete error enums stay in their own crates and implement this to plug into
55/// shared handling (HTTP mapping, retry decisions, structured logging). The
56/// `category()` answer supplies sensible defaults for [`is_retryable`] and
57/// [`http_status`], so most impls only define `category` and `code`.
58///
59/// [`is_retryable`]: DomainError::is_retryable
60/// [`http_status`]: DomainError::http_status
61pub trait DomainError: std::error::Error {
62 /// The coarse classification of this error.
63 fn category(&self) -> ErrorCategory;
64
65 /// The stable `domain.reason` code for this error.
66 fn code(&self) -> ErrorCode;
67
68 /// Whether retrying might help. Defaults to the category's policy.
69 fn is_retryable(&self) -> bool {
70 self.category().is_retryable()
71 }
72
73 /// The HTTP status to surface. Defaults to the category's status.
74 fn http_status(&self) -> u16 {
75 self.category().http_status()
76 }
77}