1use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, KitError>;
10
11#[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 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}