klauthed_data/error/
data_error.rs1use klauthed_core::config::{CacheBackend, DbSystem, MessagingBackend};
2use klauthed_core::error::ConfigError;
3use klauthed_error::{DomainError, ErrorCategory, ErrorCode};
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum DataError {
10 #[error("configuration error: {0}")]
13 Config(#[from] ConfigError),
14
15 #[error("database system '{0:?}' is not supported by this connector")]
17 UnsupportedSystem(DbSystem),
18
19 #[error("cache backend '{0:?}' is not supported by this connector")]
21 UnsupportedCacheBackend(CacheBackend),
22
23 #[error("messaging backend '{0:?}' is not supported by this connector")]
25 UnsupportedMessagingBackend(MessagingBackend),
26
27 #[error("no connection URL could be derived for {0}")]
29 MissingUrl(&'static str),
30
31 #[error("messaging setup error: {0}")]
33 Messaging(String),
34
35 #[error("transactional outbox error: {0}")]
37 Outbox(String),
38
39 #[error("idempotency store error: {0}")]
41 Idempotency(String),
42
43 #[error("migration error: {0}")]
45 Migration(String),
46
47 #[error("lock '{0}' is already held")]
49 LockHeld(String),
50
51 #[error("this backend requires the '{0}' cargo feature to be enabled")]
53 FeatureDisabled(&'static str),
54
55 #[error("I/O error: {0}")]
57 Io(#[from] std::io::Error),
58
59 #[cfg(feature = "sql")]
61 #[error("database error: {0}")]
62 Sqlx(#[from] sqlx::Error),
63
64 #[cfg(feature = "redis")]
66 #[error("redis error: {0}")]
67 Redis(#[from] ::redis::RedisError),
68
69 #[cfg(feature = "nats")]
71 #[error("nats connection error: {0}")]
72 Nats(#[from] async_nats::ConnectError),
73
74 #[cfg(feature = "rabbitmq")]
76 #[error("rabbitmq connection error: {0}")]
77 RabbitMq(#[from] lapin::Error),
78
79 #[cfg(feature = "kafka")]
81 #[error("kafka connection error: {0}")]
82 Kafka(#[from] rskafka::client::error::Error),
83
84 #[cfg(feature = "storage")]
86 #[error("storage error: {0}")]
87 Storage(#[from] object_store::Error),
88
89 #[error("invalid pagination request: {0}")]
91 InvalidPage(String),
92
93 #[error("invalid or corrupted cursor: {0}")]
95 InvalidCursor(String),
96}
97
98impl DataError {
99 fn classify(&self) -> (ErrorCategory, ErrorCode) {
103 use ErrorCategory::Internal;
104 match self {
105 DataError::Config(e) => (e.category(), e.code()),
106 DataError::UnsupportedSystem(_) => {
107 (Internal, ErrorCode::new("data.unsupported_system"))
108 }
109 DataError::UnsupportedCacheBackend(_) => {
110 (Internal, ErrorCode::new("data.unsupported_cache_backend"))
111 }
112 DataError::UnsupportedMessagingBackend(_) => {
113 (Internal, ErrorCode::new("data.unsupported_messaging_backend"))
114 }
115 DataError::MissingUrl(_) => (Internal, ErrorCode::new("data.missing_url")),
116 DataError::Messaging(_) => (Internal, ErrorCode::new("data.messaging")),
117 DataError::Outbox(_) => (Internal, ErrorCode::new("data.outbox")),
118 DataError::Idempotency(_) => (Internal, ErrorCode::new("data.idempotency")),
119 DataError::Migration(_) => (Internal, ErrorCode::new("data.migration")),
120 DataError::LockHeld(_) => (ErrorCategory::Conflict, ErrorCode::new("data.lock_held")),
122 DataError::FeatureDisabled(_) => (Internal, ErrorCode::new("data.feature_disabled")),
123 DataError::Io(_) => (Internal, ErrorCode::new("data.io")),
124 #[cfg(feature = "sql")]
126 DataError::Sqlx(_) => (ErrorCategory::Unavailable, ErrorCode::new("data.database")),
127 #[cfg(feature = "redis")]
128 DataError::Redis(_) => (ErrorCategory::Unavailable, ErrorCode::new("data.redis")),
129 #[cfg(feature = "nats")]
130 DataError::Nats(_) => (ErrorCategory::Unavailable, ErrorCode::new("data.nats")),
131 #[cfg(feature = "rabbitmq")]
132 DataError::RabbitMq(_) => (ErrorCategory::Unavailable, ErrorCode::new("data.rabbitmq")),
133 #[cfg(feature = "kafka")]
134 DataError::Kafka(_) => (ErrorCategory::Unavailable, ErrorCode::new("data.kafka")),
135 #[cfg(feature = "storage")]
136 DataError::Storage(_) => (Internal, ErrorCode::new("data.storage")),
137 DataError::InvalidPage(_) => {
138 (ErrorCategory::BadRequest, ErrorCode::new("data.invalid_page"))
139 }
140 DataError::InvalidCursor(_) => {
141 (ErrorCategory::BadRequest, ErrorCode::new("data.invalid_cursor"))
142 }
143 }
144 }
145}
146
147impl DomainError for DataError {
148 fn category(&self) -> ErrorCategory {
149 self.classify().0
150 }
151
152 fn code(&self) -> ErrorCode {
153 self.classify().1
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn unsupported_system_is_internal() {
163 let err = DataError::UnsupportedSystem(DbSystem::MongoDb);
164 assert_eq!(err.category(), ErrorCategory::Internal);
165 assert_eq!(err.code().as_str(), "data.unsupported_system");
166 }
167
168 #[test]
169 fn wrapped_config_error_delegates_classification() {
170 let err: DataError = ConfigError::MissingRequired("database".into()).into();
171 assert_eq!(err.category(), ErrorCategory::Internal);
173 assert_eq!(err.code().as_str(), "config.missing_required");
174 }
175}