nautilus_connector/
error.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum SqlxErrorKind {
13 None,
15 Database,
17 UniqueConstraint,
19 ForeignKeyConstraint,
21 CheckConstraint,
23 NullConstraint,
25 Deadlock,
27 SerializationFailure,
29 Io,
31 Tls,
33 Protocol,
35 RowNotFound,
37 TypeNotFound,
39 ColumnIndexOutOfBounds,
41 ColumnNotFound,
43 ColumnDecode,
45 Decode,
47 PoolTimedOut,
49 PoolClosed,
51 WorkerCrashed,
53 Configuration,
55}
56
57impl SqlxErrorKind {
58 pub fn from_sqlx(e: &sqlx::Error) -> Self {
60 match e {
61 sqlx::Error::Database(db_err) => {
62 if db_err.is_unique_violation() {
63 SqlxErrorKind::UniqueConstraint
64 } else if db_err.is_foreign_key_violation() {
65 SqlxErrorKind::ForeignKeyConstraint
66 } else if db_err.is_check_violation() {
67 SqlxErrorKind::CheckConstraint
68 } else {
69 let state = db_err.code();
72 let state = state.as_deref().unwrap_or("");
73 let msg = db_err.message();
74 if state == "23502"
75 || msg.contains("NOT NULL constraint")
76 || msg.contains("not-null constraint")
77 || msg.contains("cannot be null")
78 {
79 SqlxErrorKind::NullConstraint
80 } else if state == "40P01" || msg.to_ascii_lowercase().contains("deadlock") {
81 SqlxErrorKind::Deadlock
82 } else if state == "40001"
83 || msg.to_ascii_lowercase().contains("serialization failure")
84 || msg.to_ascii_lowercase().contains("could not serialize")
85 {
86 SqlxErrorKind::SerializationFailure
87 } else {
88 SqlxErrorKind::Database
89 }
90 }
91 }
92 sqlx::Error::Io(_) => SqlxErrorKind::Io,
93 sqlx::Error::Tls(_) => SqlxErrorKind::Tls,
94 sqlx::Error::Protocol(_) => SqlxErrorKind::Protocol,
95 sqlx::Error::RowNotFound => SqlxErrorKind::RowNotFound,
96 sqlx::Error::TypeNotFound { .. } => SqlxErrorKind::TypeNotFound,
97 sqlx::Error::ColumnIndexOutOfBounds { .. } => SqlxErrorKind::ColumnIndexOutOfBounds,
98 sqlx::Error::ColumnNotFound(_) => SqlxErrorKind::ColumnNotFound,
99 sqlx::Error::ColumnDecode { .. } => SqlxErrorKind::ColumnDecode,
100 sqlx::Error::Decode(_) => SqlxErrorKind::Decode,
101 sqlx::Error::PoolTimedOut => SqlxErrorKind::PoolTimedOut,
102 sqlx::Error::PoolClosed => SqlxErrorKind::PoolClosed,
103 sqlx::Error::WorkerCrashed => SqlxErrorKind::WorkerCrashed,
104 sqlx::Error::Configuration(_) => SqlxErrorKind::Configuration,
105 #[allow(unreachable_patterns)]
107 _ => SqlxErrorKind::None,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, thiserror::Error)]
122pub enum ConnectorError {
123 #[error("Database error: {1}")]
125 Database(SqlxErrorKind, String),
126 #[error("Connection error: {1}")]
128 Connection(SqlxErrorKind, String),
129 #[error("Row decode error: {1}")]
131 RowDecode(SqlxErrorKind, String),
132 #[error("Core error: {0}")]
134 Core(#[from] nautilus_core::Error),
135}
136
137impl ConnectorError {
138 pub fn database(e: sqlx::Error, context: &str) -> Self {
140 ConnectorError::Database(SqlxErrorKind::from_sqlx(&e), format!("{}: {}", context, e))
141 }
142
143 pub fn connection(e: sqlx::Error, context: &str) -> Self {
145 ConnectorError::Connection(SqlxErrorKind::from_sqlx(&e), format!("{}: {}", context, e))
146 }
147
148 pub fn row_decode(e: sqlx::Error, context: &str) -> Self {
150 ConnectorError::RowDecode(SqlxErrorKind::from_sqlx(&e), format!("{}: {}", context, e))
151 }
152
153 pub fn database_msg(msg: impl Into<String>) -> Self {
155 ConnectorError::Database(SqlxErrorKind::None, msg.into())
156 }
157
158 pub fn connection_msg(msg: impl Into<String>) -> Self {
160 ConnectorError::Connection(SqlxErrorKind::None, msg.into())
161 }
162
163 pub fn row_decode_msg(msg: impl Into<String>) -> Self {
165 ConnectorError::RowDecode(SqlxErrorKind::None, msg.into())
166 }
167
168 pub fn sqlx_kind(&self) -> SqlxErrorKind {
170 match self {
171 ConnectorError::Database(k, _)
172 | ConnectorError::Connection(k, _)
173 | ConnectorError::RowDecode(k, _) => *k,
174 ConnectorError::Core(_) => SqlxErrorKind::None,
175 }
176 }
177}
178
179pub type Result<T> = std::result::Result<T, ConnectorError>;
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_display() {
188 assert_eq!(
189 ConnectorError::database_msg("query failed").to_string(),
190 "Database error: query failed"
191 );
192 assert_eq!(
193 ConnectorError::connection_msg("refused").to_string(),
194 "Connection error: refused"
195 );
196 assert_eq!(
197 ConnectorError::row_decode_msg("invalid bool").to_string(),
198 "Row decode error: invalid bool"
199 );
200 }
201
202 #[test]
203 fn test_sqlx_kind() {
204 let err = ConnectorError::database_msg("test");
205 assert_eq!(err.sqlx_kind(), SqlxErrorKind::None);
206
207 let err = ConnectorError::Database(SqlxErrorKind::PoolTimedOut, "timeout".to_string());
208 assert_eq!(err.sqlx_kind(), SqlxErrorKind::PoolTimedOut);
209 }
210
211 #[test]
212 fn test_from_core_error() {
213 let core_err = nautilus_core::Error::InvalidQuery("bad query".to_string());
214 let conn_err = ConnectorError::from(core_err.clone());
215 assert_eq!(conn_err, ConnectorError::Core(core_err));
216 assert!(conn_err.to_string().contains("bad query"));
217 }
218}