sqlx_cloudflare_core/
error.rs1use std::borrow::Cow;
2use std::error::Error as StdError;
3use std::fmt::{self, Display, Formatter};
4
5use sqlx_core::error::ErrorKind;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct DatabaseError {
17 message: String,
18}
19
20impl DatabaseError {
21 #[must_use]
23 pub fn new(message: String) -> Self {
24 Self { message }
25 }
26}
27
28impl Display for DatabaseError {
29 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
30 f.write_str(&self.message)
31 }
32}
33
34impl StdError for DatabaseError {}
35
36impl sqlx_core::error::DatabaseError for DatabaseError {
37 fn message(&self) -> &str {
38 &self.message
39 }
40
41 fn code(&self) -> Option<Cow<'_, str>> {
44 code(&self.message).map(Cow::Borrowed)
45 }
46
47 fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) {
48 self
49 }
50
51 fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
52 self
53 }
54
55 fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static> {
56 self
57 }
58
59 fn kind(&self) -> ErrorKind {
60 kind(&self.message)
61 }
62}
63
64fn code(message: &str) -> Option<&str> {
67 let is_code_char = |c: char| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_';
68
69 if let Some((_, rest)) = message.rsplit_once("(extended: ") {
70 let end = rest.find(|c| !is_code_char(c)).unwrap_or(rest.len());
71 if end > 0 {
72 return Some(&rest[..end]);
73 }
74 }
75
76 let start = message.rfind("SQLITE_")?;
77 let rest = &message[start..];
78 Some(&rest[..rest.find(|c| !is_code_char(c)).unwrap_or(rest.len())])
79}
80
81fn kind(message: &str) -> ErrorKind {
89 match code(message) {
90 Some("SQLITE_CONSTRAINT_UNIQUE" | "SQLITE_CONSTRAINT_PRIMARYKEY") => {
91 return ErrorKind::UniqueViolation
92 }
93 Some("SQLITE_CONSTRAINT_FOREIGNKEY") => return ErrorKind::ForeignKeyViolation,
94 Some("SQLITE_CONSTRAINT_NOTNULL") => return ErrorKind::NotNullViolation,
95 Some("SQLITE_CONSTRAINT_CHECK") => return ErrorKind::CheckViolation,
96 _ => {}
97 }
98
99 if message.contains("UNIQUE constraint failed") {
100 ErrorKind::UniqueViolation
101 } else if message.contains("FOREIGN KEY constraint failed") {
102 ErrorKind::ForeignKeyViolation
103 } else if message.contains("NOT NULL constraint failed") {
104 ErrorKind::NotNullViolation
105 } else if message.contains("CHECK constraint failed") {
106 ErrorKind::CheckViolation
107 } else {
108 ErrorKind::Other
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use sqlx_core::error::{DatabaseError as _, ErrorKind};
115
116 use super::DatabaseError;
117
118 fn kind(message: &str) -> ErrorKind {
119 DatabaseError::new(message.to_owned()).kind()
120 }
121
122 const NOT_NULL: &str = "NOT NULL constraint failed: users.email: SQLITE_CONSTRAINT \
124 (extended: SQLITE_CONSTRAINT_NOTNULL)";
125
126 #[test]
127 fn reads_the_extended_code() {
128 let error = DatabaseError::new(NOT_NULL.to_owned());
129
130 assert_eq!(error.code().as_deref(), Some("SQLITE_CONSTRAINT_NOTNULL"));
131 assert!(matches!(error.kind(), ErrorKind::NotNullViolation));
132 }
133
134 #[test]
135 fn falls_back_to_the_primary_code() {
136 let error = DatabaseError::new(
137 "D1_ERROR: near \"SELEC\": syntax error at offset 0: SQLITE_ERROR".to_owned(),
138 );
139
140 assert_eq!(error.code().as_deref(), Some("SQLITE_ERROR"));
141 assert!(matches!(error.kind(), ErrorKind::Other));
142 }
143
144 #[test]
145 fn maps_each_extended_constraint_code() {
146 for (code, expected) in [
147 ("SQLITE_CONSTRAINT_UNIQUE", "unique"),
148 ("SQLITE_CONSTRAINT_PRIMARYKEY", "unique"),
149 ("SQLITE_CONSTRAINT_FOREIGNKEY", "foreign key"),
150 ("SQLITE_CONSTRAINT_NOTNULL", "not null"),
151 ("SQLITE_CONSTRAINT_CHECK", "check"),
152 ] {
153 let kind = kind(&format!("x: SQLITE_CONSTRAINT (extended: {code})"));
154 let actual = match kind {
155 ErrorKind::UniqueViolation => "unique",
156 ErrorKind::ForeignKeyViolation => "foreign key",
157 ErrorKind::NotNullViolation => "not null",
158 ErrorKind::CheckViolation => "check",
159 _ => "other",
160 };
161 assert_eq!(actual, expected, "{code}");
162 }
163 }
164
165 #[test]
167 fn maps_constraint_messages_to_kinds() {
168 assert!(matches!(
169 kind("D1_ERROR: UNIQUE constraint failed: users.email: SQLITE_CONSTRAINT"),
170 ErrorKind::UniqueViolation
171 ));
172 assert!(matches!(
173 kind("D1_ERROR: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT"),
174 ErrorKind::ForeignKeyViolation
175 ));
176 assert!(matches!(
177 kind("D1_ERROR: NOT NULL constraint failed: users.name: SQLITE_CONSTRAINT"),
178 ErrorKind::NotNullViolation
179 ));
180 assert!(matches!(
181 kind("D1_ERROR: CHECK constraint failed: age >= 0: SQLITE_CONSTRAINT"),
182 ErrorKind::CheckViolation
183 ));
184 }
185
186 #[test]
187 fn anything_else_is_other() {
188 assert!(matches!(
189 kind("D1_ERROR: no such table: users: SQLITE_ERROR"),
190 ErrorKind::Other
191 ));
192 }
193}