turso_orm_driver/
error.rs1use std::fmt;
19
20pub type Result<T, E = Error> = std::result::Result<T, E>;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum ConstraintKind {
27 Unique,
29 ForeignKey,
31 NotNull,
33 Check,
35 Other,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum ErrorKind {
43 Busy,
46 Constraint(ConstraintKind),
48 Connection,
51 PoolTimeout,
53 Decode,
55 Encode,
57 NotFound,
59 Misuse,
62 Other,
64}
65
66#[derive(Debug, thiserror::Error)]
68#[non_exhaustive]
69pub enum Error {
70 #[error("{kind:?}: {source}")]
72 Turso {
73 kind: ErrorKind,
75 #[source]
77 source: turso::Error,
78 },
79 #[cfg(feature = "serverless")]
81 #[cfg_attr(docsrs, doc(cfg(feature = "serverless")))]
82 #[error("{kind:?}: {source}")]
83 Remote {
84 kind: ErrorKind,
86 #[source]
88 source: turso_serverless::Error,
89 },
90 #[error("timed out waiting for a pooled connection")]
92 PoolTimeout,
93 #[error("cannot decode column {column} as {ty}: {reason}")]
95 Decode {
96 column: String,
98 ty: &'static str,
100 reason: String,
102 },
103 #[error("cannot encode value: {0}")]
105 Encode(String),
106 #[error("no row returned")]
108 NotFound,
109 #[error("invalid connect options: {0}")]
111 InvalidOptions(String),
112 #[error("misuse: {0}")]
114 Misuse(String),
115 #[error("{0}")]
117 Custom(String),
118}
119
120impl Error {
121 pub fn kind(&self) -> ErrorKind {
123 match self {
124 Error::Turso { kind, .. } => *kind,
125 #[cfg(feature = "serverless")]
126 Error::Remote { kind, .. } => *kind,
127 Error::PoolTimeout => ErrorKind::PoolTimeout,
128 Error::Decode { .. } => ErrorKind::Decode,
129 Error::Encode(_) => ErrorKind::Encode,
130 Error::NotFound => ErrorKind::NotFound,
131 Error::InvalidOptions(_) | Error::Misuse(_) => ErrorKind::Misuse,
132 Error::Custom(_) => ErrorKind::Other,
133 }
134 }
135
136 pub fn is_busy(&self) -> bool {
138 self.kind() == ErrorKind::Busy
139 }
140
141 pub fn constraint(&self) -> Option<ConstraintKind> {
143 match self.kind() {
144 ErrorKind::Constraint(k) => Some(k),
145 _ => None,
146 }
147 }
148
149 pub fn decode(column: impl fmt::Display, ty: &'static str, reason: impl fmt::Display) -> Self {
154 Error::Decode {
155 column: column.to_string(),
156 ty,
157 reason: reason.to_string(),
158 }
159 }
160}
161
162impl From<turso::Error> for Error {
163 fn from(source: turso::Error) -> Self {
164 let kind = classify(&source);
165 Error::Turso { kind, source }
166 }
167}
168
169fn classify(err: &turso::Error) -> ErrorKind {
175 match err {
176 turso::Error::Busy(_) | turso::Error::BusySnapshot(_) => ErrorKind::Busy,
177 turso::Error::Constraint(msg) => ErrorKind::Constraint(classify_constraint(msg)),
178 turso::Error::Misuse(_) => ErrorKind::Misuse,
179 turso::Error::Error(msg) if msg.to_ascii_lowercase().contains("conflict") => {
180 ErrorKind::Busy
181 }
182 turso::Error::IoError(..) | turso::Error::NotAdb(_) | turso::Error::Corrupt(_) => {
183 ErrorKind::Connection
184 }
185 _ => ErrorKind::Other,
186 }
187}
188
189#[cfg(feature = "serverless")]
193impl From<turso_serverless::Error> for Error {
194 fn from(source: turso_serverless::Error) -> Self {
195 use turso_serverless::Error as E;
196 let kind = match &source {
197 E::Busy(_) | E::BusySnapshot(_) => ErrorKind::Busy,
198 E::Constraint(msg) => ErrorKind::Constraint(classify_constraint(msg)),
199 E::Misuse(_) => ErrorKind::Misuse,
200 E::Error(msg) if msg.to_ascii_lowercase().contains("conflict") => ErrorKind::Busy,
201 E::Http(_) | E::NotAdb(_) | E::Corrupt(_) => ErrorKind::Connection,
202 _ => ErrorKind::Other,
203 };
204 Error::Remote { kind, source }
205 }
206}
207
208fn classify_constraint(msg: &str) -> ConstraintKind {
211 let lower = msg.to_ascii_lowercase();
212 if lower.contains("unique") || lower.contains("primary key") {
213 ConstraintKind::Unique
214 } else if lower.contains("foreign key") {
215 ConstraintKind::ForeignKey
216 } else if lower.contains("not null") {
217 ConstraintKind::NotNull
218 } else if lower.contains("check") {
219 ConstraintKind::Check
220 } else {
221 ConstraintKind::Other
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
232 fn classifies() {
233 let e: Error = turso::Error::Constraint("UNIQUE constraint failed: t.a".into()).into();
234 assert_eq!(e.constraint(), Some(ConstraintKind::Unique));
235 let e: Error = turso::Error::Busy("database is locked".into()).into();
236 assert!(e.is_busy());
237 let e: Error = turso::Error::Error("write-write conflict".into()).into();
238 assert!(e.is_busy());
239 let e: Error = turso::Error::Error("syntax error".into()).into();
240 assert_eq!(e.kind(), ErrorKind::Other);
241 }
242}