ggen_core/codegen/
execution_lifecycle.rs

1use crate::codegen::{SyncOptions, SyncResult};
2use ggen_utils::error::Result;
3use std::time::Instant;
4
5pub struct ExecutionLifecycle {
6    pre_sync_hooks: Vec<String>,
7    post_sync_hooks: Vec<String>,
8    error_handlers: Vec<String>,
9}
10
11impl ExecutionLifecycle {
12    pub fn new() -> Self {
13        Self {
14            pre_sync_hooks: vec![],
15            post_sync_hooks: vec![],
16            error_handlers: vec![],
17        }
18    }
19
20    pub fn register_pre_sync(&mut self, hook: String) {
21        self.pre_sync_hooks.push(hook);
22    }
23
24    pub fn register_post_sync(&mut self, hook: String) {
25        self.post_sync_hooks.push(hook);
26    }
27
28    pub fn register_error_handler(&mut self, handler: String) {
29        self.error_handlers.push(handler);
30    }
31
32    pub async fn run_pre_sync_hooks(&self, options: &SyncOptions) -> Result<PreSyncContext> {
33        let start = Instant::now();
34        let mut context = PreSyncContext {
35            manifest_path: options.manifest_path.clone(),
36            output_dir: options.output_dir.clone(),
37            dry_run: options.dry_run,
38            verbose: options.verbose,
39            hooks_executed: 0,
40            duration_ms: 0,
41            checks_passed: true,
42        };
43
44        for hook in &self.pre_sync_hooks {
45            if options.verbose {
46                eprintln!("Executing pre-sync hook: {}", hook);
47            }
48            context.hooks_executed += 1;
49        }
50
51        context.duration_ms = start.elapsed().as_millis() as u64;
52        Ok(context)
53    }
54
55    pub async fn run_post_sync_hooks(
56        &self, result: &SyncResult, options: &SyncOptions,
57    ) -> Result<PostSyncContext> {
58        let start = Instant::now();
59        let mut context = PostSyncContext {
60            status: result.status.clone(),
61            files_synced: result.files_synced,
62            duration_ms: result.duration_ms,
63            hooks_executed: 0,
64            artifact_count: result.files.len(),
65        };
66
67        for hook in &self.post_sync_hooks {
68            if options.verbose {
69                eprintln!("Executing post-sync hook: {}", hook);
70            }
71            context.hooks_executed += 1;
72        }
73
74        context.duration_ms = start.elapsed().as_millis() as u64;
75        Ok(context)
76    }
77
78    pub async fn handle_execution_error(
79        &self, _error: &ggen_utils::error::Error, options: &SyncOptions,
80    ) -> Result<()> {
81        for handler in &self.error_handlers {
82            if options.verbose {
83                eprintln!("Executing error handler: {}", handler);
84            }
85        }
86        Ok(())
87    }
88}
89
90pub struct PreSyncContext {
91    pub manifest_path: std::path::PathBuf,
92    pub output_dir: Option<std::path::PathBuf>,
93    pub dry_run: bool,
94    pub verbose: bool,
95    pub hooks_executed: usize,
96    pub duration_ms: u64,
97    pub checks_passed: bool,
98}
99
100pub struct PostSyncContext {
101    pub status: String,
102    pub files_synced: usize,
103    pub duration_ms: u64,
104    pub hooks_executed: usize,
105    pub artifact_count: usize,
106}
107
108impl Default for ExecutionLifecycle {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_lifecycle_creation() {
120        let lifecycle = ExecutionLifecycle::new();
121        assert_eq!(lifecycle.pre_sync_hooks.len(), 0);
122        assert_eq!(lifecycle.post_sync_hooks.len(), 0);
123    }
124
125    #[test]
126    fn test_hook_registration() {
127        let mut lifecycle = ExecutionLifecycle::new();
128        lifecycle.register_pre_sync("hook1".to_string());
129        lifecycle.register_post_sync("hook2".to_string());
130
131        assert_eq!(lifecycle.pre_sync_hooks.len(), 1);
132        assert_eq!(lifecycle.post_sync_hooks.len(), 1);
133    }
134
135    #[tokio::test]
136    async fn test_pre_sync_context() {
137        let lifecycle = ExecutionLifecycle::new();
138        let options = SyncOptions::default();
139
140        let context = lifecycle.run_pre_sync_hooks(&options).await.unwrap();
141        assert!(context.checks_passed);
142        assert_eq!(context.hooks_executed, 0);
143    }
144
145    #[tokio::test]
146    async fn test_post_sync_context() {
147        let lifecycle = ExecutionLifecycle::new();
148        let result = SyncResult {
149            status: "success".to_string(),
150            files_synced: 5,
151            duration_ms: 100,
152            files: vec![],
153            inference_rules_executed: 2,
154            generation_rules_executed: 3,
155            audit_trail: None,
156            error: None,
157        };
158        let options = SyncOptions::default();
159
160        let context = lifecycle
161            .run_post_sync_hooks(&result, &options)
162            .await
163            .unwrap();
164        assert_eq!(context.status, "success");
165        assert_eq!(context.files_synced, 5);
166    }
167
168    #[tokio::test]
169    async fn test_error_handling() {
170        let mut lifecycle = ExecutionLifecycle::new();
171        lifecycle.register_error_handler("error_handler".to_string());
172
173        let error = ggen_utils::error::Error::new("test error");
174        let options = SyncOptions::default();
175
176        let result = lifecycle.handle_execution_error(&error, &options).await;
177        assert!(result.is_ok());
178    }
179}