use std::fmt;
pub type Result<T> = std::result::Result<T, SurqlError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SurqlError {
Database {
reason: String,
},
Connection {
reason: String,
},
Query {
reason: String,
},
Transaction {
reason: String,
},
Context {
reason: String,
},
Registry {
reason: String,
},
Streaming {
reason: String,
},
Validation {
reason: String,
},
SchemaParse {
reason: String,
},
MigrationDiscovery {
reason: String,
},
MigrationLoad {
reason: String,
},
MigrationGeneration {
reason: String,
},
MigrationExecution {
reason: String,
},
MigrationHistory {
reason: String,
},
MigrationSquash {
reason: String,
},
MigrationWatcher {
reason: String,
},
Orchestration {
reason: String,
},
Serialization {
reason: String,
},
Io {
reason: String,
},
WithContext {
source: Box<SurqlError>,
context: String,
},
}
impl fmt::Display for SurqlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Database { reason } => write!(f, "database error: {reason}"),
Self::Connection { reason } => write!(f, "connection error: {reason}"),
Self::Query { reason } => write!(f, "query error: {reason}"),
Self::Transaction { reason } => write!(f, "transaction error: {reason}"),
Self::Context { reason } => write!(f, "context error: {reason}"),
Self::Registry { reason } => write!(f, "registry error: {reason}"),
Self::Streaming { reason } => write!(f, "streaming error: {reason}"),
Self::Validation { reason } => write!(f, "validation error: {reason}"),
Self::SchemaParse { reason } => write!(f, "schema parse error: {reason}"),
Self::MigrationDiscovery { reason } => {
write!(f, "migration discovery error: {reason}")
}
Self::MigrationLoad { reason } => write!(f, "migration load error: {reason}"),
Self::MigrationGeneration { reason } => {
write!(f, "migration generation error: {reason}")
}
Self::MigrationExecution { reason } => {
write!(f, "migration execution error: {reason}")
}
Self::MigrationHistory { reason } => write!(f, "migration history error: {reason}"),
Self::MigrationSquash { reason } => write!(f, "migration squash error: {reason}"),
Self::MigrationWatcher { reason } => write!(f, "migration watcher error: {reason}"),
Self::Orchestration { reason } => write!(f, "orchestration error: {reason}"),
Self::Serialization { reason } => write!(f, "serialization error: {reason}"),
Self::Io { reason } => write!(f, "io error: {reason}"),
Self::WithContext { source, context } => write!(f, "{context}: {source}"),
}
}
}
impl std::error::Error for SurqlError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::WithContext { source, .. } => Some(source.as_ref()),
_ => None,
}
}
}
impl From<std::io::Error> for SurqlError {
fn from(err: std::io::Error) -> Self {
Self::Io {
reason: err.to_string(),
}
}
}
impl From<serde_json::Error> for SurqlError {
fn from(err: serde_json::Error) -> Self {
Self::Serialization {
reason: err.to_string(),
}
}
}
pub trait Context<T> {
fn context(self, context: impl Into<String>) -> Result<T>;
}
impl<T, E> Context<T> for std::result::Result<T, E>
where
E: Into<SurqlError>,
{
fn context(self, context: impl Into<String>) -> Result<T> {
self.map_err(|e| SurqlError::WithContext {
source: Box::new(e.into()),
context: context.into(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_includes_reason() {
let err = SurqlError::Query {
reason: "missing table".into(),
};
assert_eq!(err.to_string(), "query error: missing table");
}
#[test]
fn context_wraps_error() {
let base: Result<()> = Err(SurqlError::Connection {
reason: "refused".into(),
});
let wrapped = base.context("dialing surrealdb").unwrap_err();
assert_eq!(
wrapped.to_string(),
"dialing surrealdb: connection error: refused"
);
}
#[test]
fn context_is_noop_on_ok() {
let ok: Result<u32> = Ok(1);
assert_eq!(ok.context("should not fire").unwrap(), 1);
}
#[test]
fn from_serde_json_error() {
let json_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
let err: SurqlError = json_err.into();
assert!(matches!(err, SurqlError::Serialization { .. }));
}
#[test]
fn from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
let err: SurqlError = io_err.into();
assert!(matches!(err, SurqlError::Io { .. }));
}
#[test]
fn source_chain_is_reported() {
let err = SurqlError::WithContext {
source: Box::new(SurqlError::Validation {
reason: "bad".into(),
}),
context: "outer".into(),
};
let source = std::error::Error::source(&err);
assert!(source.is_some());
}
}