use thiserror::Error;
#[derive(Error, Debug)]
pub enum GgenError {
#[error("Validation error: {0}")]
ValidationError(String),
#[error("SPARQL error: {0}")]
SparqlError(String),
#[error("Template error: {0}")]
TemplateError(String),
#[error("Output validation error: {0}")]
OutputInvalid(String),
#[error("Operation timeout: {0}")]
Timeout(String),
#[error("File error: {0}")]
FileError(String),
#[error("File error in {path}: {message}")]
FileErrorWithPath { path: String, message: String },
#[error("Network error: {0}")]
NetworkError(String),
#[error("JSON error: {0}")]
JsonError(String),
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Command execution error: {0}")]
CommandError(String),
#[error("External service error: {0}")]
ExternalServiceError(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("PaaS error: {0}")]
PaasError(String),
#[error("Pack receipt error: {0}")]
PackReceiptError(String),
#[error("Validation error: {0}")]
InvalidInput(String),
}
impl From<std::io::Error> for GgenError {
fn from(err: std::io::Error) -> Self {
GgenError::FileError(err.to_string())
}
}
impl From<serde_json::error::Error> for GgenError {
fn from(err: serde_json::error::Error) -> Self {
GgenError::JsonError(err.to_string())
}
}
impl From<GgenError> for ggen_core::utils::Error {
fn from(err: GgenError) -> Self {
ggen_core::utils::Error::new(&err.to_string())
}
}
impl From<GgenError> for clap_noun_verb::NounVerbError {
fn from(err: GgenError) -> Self {
clap_noun_verb::NounVerbError::execution_error(&err.to_string())
}
}
impl GgenError {
pub fn from_clap_error(err: clap_noun_verb::NounVerbError) -> Self {
GgenError::CommandError(err.to_string())
}
pub fn from_paas_error(err: impl std::fmt::Display) -> Self {
GgenError::PaasError(err.to_string())
}
pub fn from_pack_receipt_error(err: impl std::fmt::Display) -> Self {
GgenError::PackReceiptError(err.to_string())
}
pub fn from_validation_error(err: impl std::fmt::Display) -> Self {
GgenError::InvalidInput(err.to_string())
}
pub fn file_error(path: &str, message: &str) -> Self {
GgenError::FileErrorWithPath {
path: path.to_string(),
message: message.to_string(),
}
}
pub fn network_error(message: &str) -> Self {
GgenError::NetworkError(message.to_string())
}
pub fn external_service_error(message: &str) -> Self {
GgenError::ExternalServiceError(message.to_string())
}
}
impl GgenError {
pub fn exit_code(&self) -> i32 {
match self {
GgenError::ValidationError(_) => 1,
GgenError::SparqlError(_) => 2,
GgenError::TemplateError(_) => 3,
GgenError::OutputInvalid(_) => 4,
GgenError::Timeout(_) => 5,
GgenError::FileError(_) => 127,
GgenError::JsonError(_) => 127,
GgenError::Internal(_) => 127,
_ => 1,
}
}
pub fn category(&self) -> &'static str {
match self {
GgenError::ValidationError(_) => "validation",
GgenError::SparqlError(_) => "sparql",
GgenError::TemplateError(_) => "template",
GgenError::OutputInvalid(_) => "output",
GgenError::Timeout(_) => "timeout",
GgenError::FileError(_) => "file",
GgenError::JsonError(_) => "json",
GgenError::Internal(_) => "internal",
_ => "unknown",
}
}
}
pub type Result<T> = std::result::Result<T, GgenError>;
pub trait GgenResultExt<T> {
fn to_ggen_result(self) -> Result<T>;
}
impl<T, E> GgenResultExt<T> for std::result::Result<T, E>
where
E: ToString,
{
fn to_ggen_result(self) -> Result<T> {
self.map_err(|e| GgenError::Internal(e.to_string()))
}
}
#[macro_export]
macro_rules! map_err {
($expr:expr, $error_type:ident) => {
$expr.map_err(|e| $crate::error::GgenError::$error_type(e.to_string()))?
};
($expr:expr, $error_type:ident, $message:expr) => {
$expr.map_err(|e| $crate::error::GgenError::$error_type(format!("{}: {}", $message, e)))?
};
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AuditTrail {
pub input_ontology_hash: String,
pub sparql_query: String,
pub template_name: String,
pub output_code: String,
pub validation_passed: bool,
pub exit_code: i32,
pub duration_ms: u64,
pub validation_errors: Vec<String>,
}
impl AuditTrail {
pub fn new(
input_ontology_hash: String, sparql_query: String, template_name: String,
output_code: String,
) -> Self {
Self {
input_ontology_hash,
sparql_query,
template_name,
output_code,
validation_passed: false,
exit_code: 0,
duration_ms: 0,
validation_errors: Vec::new(),
}
}
pub fn mark_valid(mut self) -> Self {
self.validation_passed = true;
self.exit_code = 0;
self
}
pub fn add_error(mut self, error: String) -> Self {
self.validation_errors.push(error);
self.validation_passed = false;
self.exit_code = 4; self
}
pub fn with_duration(mut self, duration_ms: u64) -> Self {
self.duration_ms = duration_ms;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exit_codes() {
assert_eq!(
GgenError::ValidationError("test".to_string()).exit_code(),
1
);
assert_eq!(GgenError::SparqlError("test".to_string()).exit_code(), 2);
assert_eq!(GgenError::TemplateError("test".to_string()).exit_code(), 3);
assert_eq!(GgenError::OutputInvalid("test".to_string()).exit_code(), 4);
assert_eq!(GgenError::Timeout("test".to_string()).exit_code(), 5);
}
#[test]
fn test_audit_trail() {
let audit = AuditTrail::new(
"abc123".to_string(),
"SELECT ?x WHERE { ?x a rdfs:Class }".to_string(),
"rust-service".to_string(),
"struct User { id: Uuid }".to_string(),
)
.mark_valid()
.with_duration(42);
assert!(audit.validation_passed);
assert_eq!(audit.exit_code, 0);
assert_eq!(audit.duration_ms, 42);
}
}