1use thiserror::Error;
19
20#[derive(Error, Debug)]
22pub enum GgenError {
23 #[error("Validation error: {0}")]
25 ValidationError(String),
26
27 #[error("SPARQL error: {0}")]
29 SparqlError(String),
30
31 #[error("Template error: {0}")]
33 TemplateError(String),
34
35 #[error("Output validation error: {0}")]
37 OutputInvalid(String),
38
39 #[error("Operation timeout: {0}")]
41 Timeout(String),
42
43 #[error("File error: {0}")]
45 FileError(String),
46
47 #[error("File error in {path}: {message}")]
49 FileErrorWithPath { path: String, message: String },
50
51 #[error("Network error: {0}")]
53 NetworkError(String),
54
55 #[error("JSON error: {0}")]
57 JsonError(String),
58
59 #[error("Configuration error: {0}")]
61 ConfigError(String),
62
63 #[error("Command execution error: {0}")]
65 CommandError(String),
66
67 #[error("External service error: {0}")]
69 ExternalServiceError(String),
70
71 #[error("Internal error: {0}")]
73 Internal(String),
74
75 #[error("PaaS error: {0}")]
77 PaasError(String),
78
79 #[error("Pack receipt error: {0}")]
81 PackReceiptError(String),
82
83 #[error("Validation error: {0}")]
85 InvalidInput(String),
86}
87
88impl From<std::io::Error> for GgenError {
89 fn from(err: std::io::Error) -> Self {
90 GgenError::FileError(err.to_string())
91 }
92}
93
94impl From<serde_json::error::Error> for GgenError {
95 fn from(err: serde_json::error::Error) -> Self {
96 GgenError::JsonError(err.to_string())
97 }
98}
99
100impl From<GgenError> for ggen_core::utils::Error {
101 fn from(err: GgenError) -> Self {
102 ggen_core::utils::Error::new(&err.to_string())
103 }
104}
105
106impl From<GgenError> for clap_noun_verb::NounVerbError {
107 fn from(err: GgenError) -> Self {
108 clap_noun_verb::NounVerbError::execution_error(&err.to_string())
109 }
110}
111
112impl GgenError {
113 pub fn from_clap_error(err: clap_noun_verb::NounVerbError) -> Self {
115 GgenError::CommandError(err.to_string())
116 }
117
118 pub fn from_paas_error(err: impl std::fmt::Display) -> Self {
120 GgenError::PaasError(err.to_string())
121 }
122
123 pub fn from_pack_receipt_error(err: impl std::fmt::Display) -> Self {
125 GgenError::PackReceiptError(err.to_string())
126 }
127
128 pub fn from_validation_error(err: impl std::fmt::Display) -> Self {
130 GgenError::InvalidInput(err.to_string())
131 }
132
133 pub fn file_error(path: &str, message: &str) -> Self {
135 GgenError::FileErrorWithPath {
136 path: path.to_string(),
137 message: message.to_string(),
138 }
139 }
140
141 pub fn network_error(message: &str) -> Self {
143 GgenError::NetworkError(message.to_string())
144 }
145
146 pub fn external_service_error(message: &str) -> Self {
148 GgenError::ExternalServiceError(message.to_string())
149 }
150}
151
152impl GgenError {
153 pub fn exit_code(&self) -> i32 {
155 match self {
156 GgenError::ValidationError(_) => 1,
157 GgenError::SparqlError(_) => 2,
158 GgenError::TemplateError(_) => 3,
159 GgenError::OutputInvalid(_) => 4,
160 GgenError::Timeout(_) => 5,
161 GgenError::FileError(_) => 127,
162 GgenError::JsonError(_) => 127,
163 GgenError::Internal(_) => 127,
164 _ => 1,
165 }
166 }
167
168 pub fn category(&self) -> &'static str {
170 match self {
171 GgenError::ValidationError(_) => "validation",
172 GgenError::SparqlError(_) => "sparql",
173 GgenError::TemplateError(_) => "template",
174 GgenError::OutputInvalid(_) => "output",
175 GgenError::Timeout(_) => "timeout",
176 GgenError::FileError(_) => "file",
177 GgenError::JsonError(_) => "json",
178 GgenError::Internal(_) => "internal",
179 _ => "unknown",
180 }
181 }
182}
183
184pub type Result<T> = std::result::Result<T, GgenError>;
186
187pub trait GgenResultExt<T> {
189 fn to_ggen_result(self) -> Result<T>;
191}
192
193impl<T, E> GgenResultExt<T> for std::result::Result<T, E>
194where
195 E: ToString,
196{
197 fn to_ggen_result(self) -> Result<T> {
198 self.map_err(|e| GgenError::Internal(e.to_string()))
199 }
200}
201
202#[macro_export]
204macro_rules! map_err {
205 ($expr:expr, $error_type:ident) => {
206 $expr.map_err(|e| $crate::error::GgenError::$error_type(e.to_string()))?
207 };
208 ($expr:expr, $error_type:ident, $message:expr) => {
209 $expr.map_err(|e| $crate::error::GgenError::$error_type(format!("{}: {}", $message, e)))?
210 };
211}
212
213#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
215pub struct AuditTrail {
216 pub input_ontology_hash: String,
218
219 pub sparql_query: String,
221
222 pub template_name: String,
224
225 pub output_code: String,
227
228 pub validation_passed: bool,
230
231 pub exit_code: i32,
233
234 pub duration_ms: u64,
236
237 pub validation_errors: Vec<String>,
239}
240
241impl AuditTrail {
242 pub fn new(
244 input_ontology_hash: String, sparql_query: String, template_name: String,
245 output_code: String,
246 ) -> Self {
247 Self {
248 input_ontology_hash,
249 sparql_query,
250 template_name,
251 output_code,
252 validation_passed: false,
253 exit_code: 0,
254 duration_ms: 0,
255 validation_errors: Vec::new(),
256 }
257 }
258
259 pub fn mark_valid(mut self) -> Self {
261 self.validation_passed = true;
262 self.exit_code = 0;
263 self
264 }
265
266 pub fn add_error(mut self, error: String) -> Self {
268 self.validation_errors.push(error);
269 self.validation_passed = false;
270 self.exit_code = 4; self
272 }
273
274 pub fn with_duration(mut self, duration_ms: u64) -> Self {
276 self.duration_ms = duration_ms;
277 self
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_exit_codes() {
287 assert_eq!(
288 GgenError::ValidationError("test".to_string()).exit_code(),
289 1
290 );
291 assert_eq!(GgenError::SparqlError("test".to_string()).exit_code(), 2);
292 assert_eq!(GgenError::TemplateError("test".to_string()).exit_code(), 3);
293 assert_eq!(GgenError::OutputInvalid("test".to_string()).exit_code(), 4);
294 assert_eq!(GgenError::Timeout("test".to_string()).exit_code(), 5);
295 }
296
297 #[test]
298 fn test_audit_trail() {
299 let audit = AuditTrail::new(
300 "abc123".to_string(),
301 "SELECT ?x WHERE { ?x a rdfs:Class }".to_string(),
302 "rust-service".to_string(),
303 "struct User { id: Uuid }".to_string(),
304 )
305 .mark_valid()
306 .with_duration(42);
307
308 assert!(audit.validation_passed);
309 assert_eq!(audit.exit_code, 0);
310 assert_eq!(audit.duration_ms, 42);
311 }
312}