use std::error::Error as StdError;
use std::fmt;
#[derive(Debug)]
pub struct Error {
message: String,
context: Option<String>,
source: Option<Box<dyn StdError + Send + Sync>>,
}
impl Error {
#[must_use]
pub fn new(message: &str) -> Self {
Self {
message: message.to_string(),
context: None,
source: None,
}
}
#[must_use]
pub fn new_fmt(args: std::fmt::Arguments) -> Self {
Self {
message: args.to_string(),
context: None,
source: None,
}
}
#[must_use]
pub fn with_context(message: &str, context: &str) -> Self {
Self {
message: message.to_string(),
context: Some(context.to_string()),
source: None,
}
}
#[must_use]
pub fn with_source(message: &str, source: Box<dyn StdError + Send + Sync>) -> Self {
Self {
message: message.to_string(),
context: None,
source: Some(source),
}
}
#[must_use]
pub fn context<C>(self, context: C) -> Self
where
C: fmt::Display + Send + Sync + 'static,
{
Self {
message: context.to_string(),
context: None,
source: Some(Box::new(self)),
}
}
#[must_use]
pub fn with_context_fn<C, F>(self, f: F) -> Self
where
C: fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
Self {
message: f().to_string(),
context: None,
source: Some(Box::new(self)),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)?;
if let Some(context) = &self.context {
write!(f, " (context: {context})")?;
}
if let Some(source) = &self.source {
write!(f, " (caused by: {source})")?;
}
Ok(())
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source
.as_ref()
.map(|s| s.as_ref() as &(dyn StdError + 'static))
}
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::new(&err.to_string())
}
}
impl From<serde_yaml::Error> for Error {
fn from(err: serde_yaml::Error) -> Self {
Self::new(&err.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Self::new(&err.to_string())
}
}
impl From<tera::Error> for Error {
fn from(err: tera::Error) -> Self {
Self::new(&err.to_string())
}
}
impl From<config::ConfigError> for Error {
fn from(err: config::ConfigError) -> Self {
Self::new(&err.to_string())
}
}
impl From<log::SetLoggerError> for Error {
fn from(err: log::SetLoggerError) -> Self {
Self::new(&err.to_string())
}
}
impl<T> From<std::sync::PoisonError<T>> for Error {
fn from(err: std::sync::PoisonError<T>) -> Self {
Self::new(&err.to_string())
}
}
impl From<anyhow::Error> for Error {
fn from(err: anyhow::Error) -> Self {
Self::new(&err.to_string())
}
}
impl From<toml::de::Error> for Error {
fn from(err: toml::de::Error) -> Self {
Self::new(&err.to_string())
}
}
impl From<String> for Error {
fn from(err: String) -> Self {
Self::new(&err)
}
}
impl From<&str> for Error {
fn from(err: &str) -> Self {
Self::new(err)
}
}
impl From<oxigraph::store::StorageError> for Error {
fn from(err: oxigraph::store::StorageError) -> Self {
Self::new(&err.to_string())
}
}
impl From<oxigraph::sparql::QueryEvaluationError> for Error {
fn from(err: oxigraph::sparql::QueryEvaluationError) -> Self {
Self::new(&err.to_string())
}
}
impl From<oxigraph::store::SerializerError> for Error {
fn from(err: oxigraph::store::SerializerError) -> Self {
Self::new(&err.to_string())
}
}
impl From<toml::ser::Error> for Error {
fn from(err: toml::ser::Error) -> Self {
Self::new(&err.to_string())
}
}
pub type GgenError = Error;
pub trait Context<T> {
fn context<C>(self, context: C) -> Result<T>
where
C: fmt::Display + Send + Sync + 'static;
fn with_context<C, F>(self, f: F) -> Result<T>
where
C: fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C;
}
impl<T> Context<T> for Result<T> {
fn context<C>(self, context: C) -> Result<T>
where
C: fmt::Display + Send + Sync + 'static,
{
self.map_err(|e| e.context(context))
}
fn with_context<C, F>(self, f: F) -> Result<T>
where
C: fmt::Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
self.map_err(|e| e.with_context_fn(f))
}
}
#[macro_export]
macro_rules! bail {
($msg:literal $(,)?) => {
return Err($crate::utils::error::Error::new($msg))
};
($fmt:expr, $($arg:tt)*) => {
return Err($crate::utils::error::Error::new(&format!($fmt, $($arg)*)))
};
}
#[macro_export]
macro_rules! ensure {
($condition:expr, $msg:literal $(,)?) => {
if !$condition {
$crate::bail!($msg);
}
};
($condition:expr, $fmt:expr, $($arg:tt)*) => {
if !$condition {
$crate::bail!($fmt, $($arg)*);
}
};
}
#[macro_export]
macro_rules! ggen_error {
($msg:literal $(,)?) => {
$crate::utils::error::Error::new($msg)
};
($fmt:expr, $($arg:tt)*) => {
$crate::utils::error::Error::new(&format!($fmt, $($arg)*))
};
}
pub use crate::ggen_error;
impl Error {
#[must_use]
pub fn invalid_input(message: impl Into<String>) -> Self {
let msg = message.into();
Self::new(&format!("Invalid input: {}", msg))
}
#[must_use]
pub fn network_error(message: impl Into<String>) -> Self {
let msg = message.into();
Self::new(&format!("Network error: {}", msg))
}
#[must_use]
pub fn feature_not_enabled(feature: &str, help: &str) -> Self {
Self::new(&format!("Feature '{}' not enabled. {}", feature, help))
}
#[must_use]
pub fn file_not_found(path: std::path::PathBuf) -> Self {
Self::new(&format!("File not found: {}", path.display()))
}
#[must_use]
pub fn io_error(message: impl Into<String>) -> Self {
let msg = message.into();
Self::new(&format!("IO error: {}", msg))
}
#[must_use]
pub fn internal_error(message: impl Into<String>) -> Self {
let msg = message.into();
Self::new(&format!("Internal error: {}", msg))
}
#[must_use]
pub fn invalid_state(message: impl Into<String>) -> Self {
let msg = message.into();
Self::new(&format!("Invalid state: {}", msg))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let error = Error::new("Test error message");
assert_eq!(error.message, "Test error message");
}
#[test]
fn test_error_display() {
let error = Error::new("Test error message");
let display = format!("{error}");
assert_eq!(display, "Test error message");
}
#[test]
fn test_error_debug() {
let error = Error::new("Test error message");
let debug = format!("{error:?}");
assert!(debug.contains("Test error message"));
}
#[test]
fn test_error_from_io_error_old() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
let error: Error = io_error.into();
assert!(error.to_string().contains("File not found"));
}
#[test]
fn test_error_from_yaml_error() {
let yaml_content = "invalid: yaml: content: [";
let yaml_error = serde_yaml::from_str::<serde_yaml::Value>(yaml_content).unwrap_err();
let error: Error = yaml_error.into();
assert!(!error.to_string().is_empty());
}
#[test]
fn test_error_from_json_error() {
let json_content = "invalid json content";
let json_error = serde_json::from_str::<serde_json::Value>(json_content).unwrap_err();
let error: Error = json_error.into();
assert!(!error.to_string().is_empty());
}
#[test]
#[ignore]
fn test_error_from_tera_error() {
let template_content = "{{ invalid template syntax";
let tera_error = tera::Tera::new("templates/**/*")
.unwrap()
.render_str(template_content, &tera::Context::new())
.unwrap_err();
let error: Error = tera_error.into();
assert!(!error.to_string().is_empty());
}
#[test]
fn test_result_type() {
fn success_function() -> String {
"success".to_string()
}
fn error_function() -> Result<String> {
Err(Error::new("error"))
}
assert_eq!(success_function(), "success");
assert!(error_function().is_err());
assert_eq!(error_function().unwrap_err().to_string(), "error");
}
#[test]
fn test_error_chain() {
let io_error =
std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Permission denied");
let error: Error = io_error.into();
let error_ref: &dyn std::error::Error = &error;
assert!(!error_ref.to_string().is_empty());
}
#[test]
fn test_context_trait() {
let result: Result<()> = Err(Error::new("Original error"));
let result_with_context = result.context("Failed to process");
assert!(result_with_context.is_err());
let err = result_with_context.unwrap_err();
assert!(err.to_string().contains("Failed to process"));
}
#[test]
fn test_with_context_trait() {
let result: Result<()> = Err(Error::new("Original error"));
let result_with_context = result.with_context(|| format!("Failed at step {}", 1));
assert!(result_with_context.is_err());
let err = result_with_context.unwrap_err();
assert!(err.to_string().contains("Failed at step 1"));
}
#[test]
fn test_error_context_method() {
let error = Error::new("Original error");
let error_with_context = error.context("Additional context");
assert!(error_with_context
.to_string()
.contains("Additional context"));
}
}