Skip to main content

mongreldb_kit/
error.rs

1//! Error model for `mongreldb-kit`.
2//!
3//! Storage errors from MongrelDB core and validation errors from the core model
4//! are folded into a small, stable set of categories so consumers can handle
5//! failures without depending on internal crate details.
6
7use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, KitError>;
10
11/// A storage/transaction error in kit terminology.
12#[derive(Debug, Error, Clone, PartialEq)]
13pub enum KitError {
14    #[error("validation error: {0}")]
15    Validation(String),
16    #[error("duplicate key: {0}")]
17    Duplicate(String),
18    #[error("foreign key violation: {0}")]
19    ForeignKey(String),
20    #[error("restrict violation: {0}")]
21    Restrict(String),
22    #[error("migration error: {0}")]
23    Migration(String),
24    #[error("conflict: {0}")]
25    Conflict(String),
26    #[error("trigger validation error: {0}")]
27    TriggerValidation(String),
28    #[error("storage error: {0}")]
29    Storage(String),
30    #[error("integrity error: {0}")]
31    Integrity(String),
32    #[error("authentication required: {0}")]
33    AuthRequired(String),
34    #[error("authentication not required: {0}")]
35    AuthNotRequired(String),
36    #[error("invalid credentials: {0}")]
37    InvalidCredentials(String),
38    #[error("permission denied: {0}")]
39    PermissionDenied(String),
40    #[error("query {query_id} cancelled: {reason}")]
41    Cancelled { query_id: String, reason: String },
42    #[error("query {query_id} deadline exceeded")]
43    DeadlineExceeded {
44        query_id: String,
45        timeout_ms: Option<u64>,
46    },
47    #[error("query id conflict: {0}")]
48    QueryConflict(String),
49    #[error("transaction aborted: {0}")]
50    TransactionAborted(String),
51    #[error("unsupported feature: {0}")]
52    Unsupported(String),
53    #[error("transport error for query {query_id}: {message}")]
54    Transport { query_id: String, message: String },
55}
56
57impl From<std::io::Error> for KitError {
58    fn from(e: std::io::Error) -> Self {
59        KitError::Storage(e.to_string())
60    }
61}
62
63impl From<mongreldb_core::MongrelError> for KitError {
64    fn from(e: mongreldb_core::MongrelError) -> Self {
65        use mongreldb_core::MongrelError;
66        match e {
67            MongrelError::Conflict(msg) if is_trigger_error(&msg) => {
68                KitError::TriggerValidation(msg)
69            }
70            MongrelError::Conflict(msg) => KitError::Conflict(msg),
71            MongrelError::InvalidArgument(msg) if is_trigger_error(&msg) => {
72                KitError::TriggerValidation(msg)
73            }
74            MongrelError::InvalidArgument(msg) => KitError::Validation(msg),
75            MongrelError::Schema(msg) => KitError::Validation(msg),
76            MongrelError::ColumnNotFound(msg) => KitError::Integrity(msg),
77            MongrelError::NotFound(msg) => KitError::Integrity(msg),
78            MongrelError::Io(e) => KitError::Storage(e.to_string()),
79            MongrelError::Serialization(e) => KitError::Storage(e.to_string()),
80            MongrelError::ChecksumMismatch { .. }
81            | MongrelError::MagicMismatch { .. }
82            | MongrelError::CorruptWal { .. }
83            | MongrelError::TornWrite { .. } => KitError::Integrity(e.to_string()),
84            MongrelError::EncryptionDisabled
85            | MongrelError::Encryption(_)
86            | MongrelError::Decryption(_) => KitError::Integrity(e.to_string()),
87            MongrelError::Full(msg) => KitError::Storage(msg),
88            MongrelError::Other(msg) => KitError::Storage(msg),
89            MongrelError::AuthRequired => KitError::AuthRequired(e.to_string()),
90            MongrelError::AuthNotRequired => KitError::AuthNotRequired(e.to_string()),
91            MongrelError::InvalidCredentials { username } => KitError::InvalidCredentials(username),
92            MongrelError::PermissionDenied {
93                required,
94                principal,
95            } => KitError::PermissionDenied(format!("{principal} lacks {required}")),
96            _ => KitError::Storage(e.to_string()),
97        }
98    }
99}
100
101fn is_trigger_error(message: &str) -> bool {
102    message.contains("trigger ")
103        || message.contains("Trigger ")
104        || message.contains("external trigger bridge")
105}
106
107impl From<mongreldb_kit_core::schema::SchemaError> for KitError {
108    fn from(e: mongreldb_kit_core::schema::SchemaError) -> Self {
109        KitError::Validation(e.to_string())
110    }
111}
112
113impl From<mongreldb_kit_core::validation::ValidationError> for KitError {
114    fn from(e: mongreldb_kit_core::validation::ValidationError) -> Self {
115        KitError::Validation(e.to_string())
116    }
117}
118
119impl From<mongreldb_kit_core::planner::PlannerError> for KitError {
120    fn from(e: mongreldb_kit_core::planner::PlannerError) -> Self {
121        match e {
122            mongreldb_kit_core::planner::PlannerError::TableNotFound(msg) => {
123                KitError::Integrity(msg)
124            }
125            mongreldb_kit_core::planner::PlannerError::CircularDelete(msg) => {
126                KitError::Restrict(msg)
127            }
128        }
129    }
130}
131
132impl From<serde_json::Error> for KitError {
133    fn from(e: serde_json::Error) -> Self {
134        KitError::Storage(e.to_string())
135    }
136}
137
138impl From<mongreldb_query::MongrelQueryError> for KitError {
139    fn from(e: mongreldb_query::MongrelQueryError) -> Self {
140        use mongreldb_query::MongrelQueryError;
141        match e {
142            // Core errors carry the engine's declarative constraint failures
143            // (unique / FK / check / conflict) — route them through the same
144            // mapping as direct core errors so callers see the right category.
145            MongrelQueryError::Core(core) => KitError::from(core),
146            MongrelQueryError::Schema(msg) => KitError::Validation(msg),
147            MongrelQueryError::Arrow(msg) | MongrelQueryError::DataFusion(msg) => {
148                KitError::Storage(msg)
149            }
150            MongrelQueryError::QueryCancelled {
151                query_id, reason, ..
152            } => KitError::Cancelled {
153                query_id: query_id.to_string(),
154                reason: format!("{reason:?}"),
155            },
156            MongrelQueryError::DeadlineExceeded {
157                query_id,
158                timeout_ms,
159                ..
160            } => KitError::DeadlineExceeded {
161                query_id: query_id.to_string(),
162                timeout_ms,
163            },
164            MongrelQueryError::QueryIdConflict { query_id } => {
165                KitError::QueryConflict(query_id.to_string())
166            }
167            MongrelQueryError::TransactionAborted => {
168                KitError::TransactionAborted("only ROLLBACK is allowed".into())
169            }
170            _ => KitError::Storage(e.to_string()),
171        }
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::KitError;
178
179    #[test]
180    fn maps_trigger_core_errors_to_trigger_validation() {
181        let conflict = KitError::from(mongreldb_core::MongrelError::Conflict(
182            "trigger raised".into(),
183        ));
184        assert_eq!(
185            conflict,
186            KitError::TriggerValidation("trigger raised".into())
187        );
188
189        let invalid = KitError::from(mongreldb_core::MongrelError::InvalidArgument(
190            "external trigger bridge rejected".into(),
191        ));
192        assert_eq!(
193            invalid,
194            KitError::TriggerValidation("external trigger bridge rejected".into())
195        );
196    }
197}