1use std::fmt;
27
28pub type Result<T> = std::result::Result<T, SurqlError>;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum SurqlError {
37 Database {
39 reason: String,
41 },
42 Connection {
44 reason: String,
46 },
47 Query {
49 reason: String,
51 },
52 Transaction {
54 reason: String,
56 },
57 Context {
59 reason: String,
61 },
62 Registry {
64 reason: String,
66 },
67 Streaming {
69 reason: String,
71 },
72 Validation {
74 reason: String,
76 },
77 SchemaParse {
79 reason: String,
81 },
82 MigrationDiscovery {
84 reason: String,
86 },
87 MigrationLoad {
89 reason: String,
91 },
92 MigrationGeneration {
94 reason: String,
96 },
97 MigrationExecution {
99 reason: String,
101 },
102 MigrationHistory {
104 reason: String,
106 },
107 MigrationSquash {
109 reason: String,
111 },
112 MigrationWatcher {
114 reason: String,
116 },
117 Orchestration {
119 reason: String,
121 },
122 Serialization {
124 reason: String,
126 },
127 Io {
129 reason: String,
131 },
132 WithContext {
134 source: Box<SurqlError>,
136 context: String,
138 },
139}
140
141impl fmt::Display for SurqlError {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 Self::Database { reason } => write!(f, "database error: {reason}"),
145 Self::Connection { reason } => write!(f, "connection error: {reason}"),
146 Self::Query { reason } => write!(f, "query error: {reason}"),
147 Self::Transaction { reason } => write!(f, "transaction error: {reason}"),
148 Self::Context { reason } => write!(f, "context error: {reason}"),
149 Self::Registry { reason } => write!(f, "registry error: {reason}"),
150 Self::Streaming { reason } => write!(f, "streaming error: {reason}"),
151 Self::Validation { reason } => write!(f, "validation error: {reason}"),
152 Self::SchemaParse { reason } => write!(f, "schema parse error: {reason}"),
153 Self::MigrationDiscovery { reason } => {
154 write!(f, "migration discovery error: {reason}")
155 }
156 Self::MigrationLoad { reason } => write!(f, "migration load error: {reason}"),
157 Self::MigrationGeneration { reason } => {
158 write!(f, "migration generation error: {reason}")
159 }
160 Self::MigrationExecution { reason } => {
161 write!(f, "migration execution error: {reason}")
162 }
163 Self::MigrationHistory { reason } => write!(f, "migration history error: {reason}"),
164 Self::MigrationSquash { reason } => write!(f, "migration squash error: {reason}"),
165 Self::MigrationWatcher { reason } => write!(f, "migration watcher error: {reason}"),
166 Self::Orchestration { reason } => write!(f, "orchestration error: {reason}"),
167 Self::Serialization { reason } => write!(f, "serialization error: {reason}"),
168 Self::Io { reason } => write!(f, "io error: {reason}"),
169 Self::WithContext { source, context } => write!(f, "{context}: {source}"),
170 }
171 }
172}
173
174impl std::error::Error for SurqlError {
175 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
176 match self {
177 Self::WithContext { source, .. } => Some(source.as_ref()),
178 _ => None,
179 }
180 }
181}
182
183impl From<std::io::Error> for SurqlError {
184 fn from(err: std::io::Error) -> Self {
185 Self::Io {
186 reason: err.to_string(),
187 }
188 }
189}
190
191impl From<serde_json::Error> for SurqlError {
192 fn from(err: serde_json::Error) -> Self {
193 Self::Serialization {
194 reason: err.to_string(),
195 }
196 }
197}
198
199pub trait Context<T> {
216 fn context(self, context: impl Into<String>) -> Result<T>;
218}
219
220impl<T, E> Context<T> for std::result::Result<T, E>
221where
222 E: Into<SurqlError>,
223{
224 fn context(self, context: impl Into<String>) -> Result<T> {
225 self.map_err(|e| SurqlError::WithContext {
226 source: Box::new(e.into()),
227 context: context.into(),
228 })
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn display_includes_reason() {
238 let err = SurqlError::Query {
239 reason: "missing table".into(),
240 };
241 assert_eq!(err.to_string(), "query error: missing table");
242 }
243
244 #[test]
245 fn context_wraps_error() {
246 let base: Result<()> = Err(SurqlError::Connection {
247 reason: "refused".into(),
248 });
249 let wrapped = base.context("dialing surrealdb").unwrap_err();
250 assert_eq!(
251 wrapped.to_string(),
252 "dialing surrealdb: connection error: refused"
253 );
254 }
255
256 #[test]
257 fn context_is_noop_on_ok() {
258 let ok: Result<u32> = Ok(1);
259 assert_eq!(ok.context("should not fire").unwrap(), 1);
260 }
261
262 #[test]
263 fn from_serde_json_error() {
264 let json_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
265 let err: SurqlError = json_err.into();
266 assert!(matches!(err, SurqlError::Serialization { .. }));
267 }
268
269 #[test]
270 fn from_io_error() {
271 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
272 let err: SurqlError = io_err.into();
273 assert!(matches!(err, SurqlError::Io { .. }));
274 }
275
276 #[test]
277 fn source_chain_is_reported() {
278 let err = SurqlError::WithContext {
279 source: Box::new(SurqlError::Validation {
280 reason: "bad".into(),
281 }),
282 context: "outer".into(),
283 };
284 let source = std::error::Error::source(&err);
285 assert!(source.is_some());
286 }
287}