use thiserror::Error;
#[derive(Error, Debug)]
pub enum QueryError {
#[error("Connection error: {message}")]
ConnectionError { message: String },
#[error("Query execution failed: {message}")]
ExecutionError { message: String },
#[error("Query parsing error: {message}")]
ParseError { message: String },
#[error("Type mismatch: expected {expected}, got {actual}")]
TypeMismatch { expected: String, actual: String },
#[error("Missing required field: {field}")]
MissingField { field: String },
#[error("Transaction error: {message}")]
TransactionError { message: String },
#[error("Unsupported query language: {language}")]
UnsupportedLanguage { language: String },
#[error("Invalid parameter binding: {message}")]
ParameterError { message: String },
#[error("Row not found")]
RowNotFound,
#[error("Expected one row, found {count}")]
MultipleRowsFound { count: usize },
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization error: {message}")]
SerializationError { message: String },
#[error("{0}")]
Other(String),
}
impl QueryError {
pub fn connection<S: Into<String>>(message: S) -> Self {
QueryError::ConnectionError {
message: message.into(),
}
}
pub fn execution<S: Into<String>>(message: S) -> Self {
QueryError::ExecutionError {
message: message.into(),
}
}
pub fn parse<S: Into<String>>(message: S) -> Self {
QueryError::ParseError {
message: message.into(),
}
}
pub fn type_mismatch<S: Into<String>>(expected: S, actual: S) -> Self {
QueryError::TypeMismatch {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn missing_field<S: Into<String>>(field: S) -> Self {
QueryError::MissingField {
field: field.into(),
}
}
pub fn transaction<S: Into<String>>(message: S) -> Self {
QueryError::TransactionError {
message: message.into(),
}
}
pub fn parameter<S: Into<String>>(message: S) -> Self {
QueryError::ParameterError {
message: message.into(),
}
}
pub fn serialization<S: Into<String>>(message: S) -> Self {
QueryError::SerializationError {
message: message.into(),
}
}
pub fn other<S: Into<String>>(message: S) -> Self {
QueryError::Other(message.into())
}
}
pub type Result<T> = std::result::Result<T, QueryError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connection_error() {
let err = QueryError::connection("Failed to connect");
assert!(matches!(err, QueryError::ConnectionError { .. }));
assert_eq!(err.to_string(), "Connection error: Failed to connect");
}
#[test]
fn test_execution_error() {
let err = QueryError::execution("Query failed");
assert!(matches!(err, QueryError::ExecutionError { .. }));
}
#[test]
fn test_type_mismatch() {
let err = QueryError::type_mismatch("String", "Integer");
assert!(matches!(err, QueryError::TypeMismatch { .. }));
assert!(err.to_string().contains("String"));
assert!(err.to_string().contains("Integer"));
}
#[test]
fn test_missing_field() {
let err = QueryError::missing_field("id");
assert!(matches!(err, QueryError::MissingField { .. }));
assert!(err.to_string().contains("id"));
}
#[test]
fn test_row_not_found() {
let err = QueryError::RowNotFound;
assert_eq!(err.to_string(), "Row not found");
}
#[test]
fn test_multiple_rows_found() {
let err = QueryError::MultipleRowsFound { count: 5 };
assert!(err.to_string().contains("5"));
}
#[test]
fn test_unsupported_language() {
let err = QueryError::UnsupportedLanguage {
language: "gremlin".to_string(),
};
assert!(err.to_string().contains("gremlin"));
}
#[test]
fn test_io_error_conversion() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: QueryError = io_err.into();
assert!(matches!(err, QueryError::IoError(_)));
}
#[test]
fn test_result_alias() {
let ok_result: Result<i32> = Ok(42);
assert!(ok_result.is_ok());
let err_result: Result<i32> = Err(QueryError::RowNotFound);
assert!(err_result.is_err());
}
}