shimexe_core/
traits.rs

1use async_trait::async_trait;
2use std::path::{Path, PathBuf};
3
4use crate::config::ShimConfig;
5use crate::error::Result;
6
7/// Type alias for pre-execute hook functions
8pub type PreExecuteHook = Box<dyn Fn(&[String]) -> Result<()> + Send + Sync>;
9
10/// Type alias for post-execute hook functions
11pub type PostExecuteHook = Box<dyn Fn(i32) -> Result<()> + Send + Sync>;
12
13/// Trait for custom shim configuration loaders
14pub trait ShimConfigLoader: Send + Sync {
15    /// Load shim configuration from a file
16    fn load_config(&self, path: &Path) -> Result<ShimConfig>;
17
18    /// Save shim configuration to a file
19    fn save_config(&self, config: &ShimConfig, path: &Path) -> Result<()>;
20
21    /// Get the default file extension for this loader
22    fn file_extension(&self) -> &str;
23
24    /// Validate the configuration
25    fn validate_config(&self, config: &ShimConfig) -> Result<()> {
26        config.validate()
27    }
28}
29
30/// Trait for custom shim runners
31#[async_trait]
32pub trait ShimRunnerTrait {
33    /// Execute the shim with additional arguments
34    async fn execute(&self, additional_args: &[String]) -> Result<i32>;
35
36    /// Get the shim configuration
37    fn config(&self) -> &ShimConfig;
38
39    /// Validate that the target executable exists and is executable
40    fn validate(&self) -> Result<()>;
41
42    /// Pre-execution hook (called before running the target executable)
43    async fn pre_execute(&self, _args: &[String]) -> Result<()> {
44        Ok(())
45    }
46
47    /// Post-execution hook (called after running the target executable)
48    async fn post_execute(&self, _exit_code: i32) -> Result<()> {
49        Ok(())
50    }
51}
52
53/// Trait for custom update providers
54#[async_trait]
55pub trait UpdateProvider: Send + Sync {
56    /// Check if an update is available
57    async fn check_update_available(&self, current_version: &str) -> Result<Option<String>>;
58
59    /// Download and install the update
60    async fn install_update(&self, version: &str, target_path: &Path) -> Result<()>;
61
62    /// Get the download URL for a specific version
63    fn get_download_url(&self, version: &str) -> Result<String>;
64
65    /// Verify the downloaded file (checksum, signature, etc.)
66    async fn verify_download(&self, _file_path: &Path, _version: &str) -> Result<bool> {
67        // Default implementation: always return true
68        Ok(true)
69    }
70}
71
72/// Trait for custom version checkers
73#[async_trait]
74pub trait VersionChecker: Send + Sync {
75    /// Get the latest available version
76    async fn get_latest_version(&self) -> Result<String>;
77
78    /// Compare two versions (returns true if first is newer than second)
79    fn is_newer_version(&self, version1: &str, version2: &str) -> Result<bool>;
80
81    /// Parse version from string
82    fn parse_version(&self, version_str: &str) -> Result<String>;
83}
84
85/// Builder for creating customizable shim runners
86pub struct ShimRunnerBuilder {
87    config_loader: Option<Box<dyn ShimConfigLoader>>,
88    update_provider: Option<Box<dyn UpdateProvider>>,
89    version_checker: Option<Box<dyn VersionChecker>>,
90    config_file_pattern: Option<String>,
91    pre_execute_hooks: Vec<PreExecuteHook>,
92    post_execute_hooks: Vec<PostExecuteHook>,
93}
94
95impl ShimRunnerBuilder {
96    /// Create a new builder
97    pub fn new() -> Self {
98        Self {
99            config_loader: None,
100            update_provider: None,
101            version_checker: None,
102            config_file_pattern: None,
103            pre_execute_hooks: Vec::new(),
104            post_execute_hooks: Vec::new(),
105        }
106    }
107
108    /// Set a custom configuration loader
109    pub fn with_config_loader(mut self, loader: Box<dyn ShimConfigLoader>) -> Self {
110        self.config_loader = Some(loader);
111        self
112    }
113
114    /// Set a custom update provider
115    pub fn with_update_provider(mut self, provider: Box<dyn UpdateProvider>) -> Self {
116        self.update_provider = Some(provider);
117        self
118    }
119
120    /// Set a custom version checker
121    pub fn with_version_checker(mut self, checker: Box<dyn VersionChecker>) -> Self {
122        self.version_checker = Some(checker);
123        self
124    }
125
126    /// Set a custom config file pattern (e.g., "{name}.custom.toml")
127    pub fn with_config_file_pattern<S: Into<String>>(mut self, pattern: S) -> Self {
128        self.config_file_pattern = Some(pattern.into());
129        self
130    }
131
132    /// Add a pre-execute hook
133    pub fn with_pre_execute_hook<F>(mut self, hook: F) -> Self
134    where
135        F: Fn(&[String]) -> Result<()> + Send + Sync + 'static,
136    {
137        self.pre_execute_hooks.push(Box::new(hook));
138        self
139    }
140
141    /// Add a post-execute hook
142    pub fn with_post_execute_hook<F>(mut self, hook: F) -> Self
143    where
144        F: Fn(i32) -> Result<()> + Send + Sync + 'static,
145    {
146        self.post_execute_hooks.push(Box::new(hook));
147        self
148    }
149
150    /// Build a customizable shim runner
151    pub fn build(self, shim_name: &str, shim_dir: &Path) -> Result<CustomizableShimRunner> {
152        let config_file_pattern = self
153            .config_file_pattern
154            .unwrap_or_else(|| "{name}.shim.toml".to_string());
155
156        let config_file = shim_dir.join(config_file_pattern.replace("{name}", shim_name));
157
158        let config_loader = self
159            .config_loader
160            .unwrap_or_else(|| Box::new(DefaultConfigLoader));
161
162        let config = config_loader.load_config(&config_file)?;
163
164        Ok(CustomizableShimRunner {
165            config,
166            config_file_path: config_file,
167            config_loader,
168            update_provider: self.update_provider,
169            version_checker: self.version_checker,
170            pre_execute_hooks: self.pre_execute_hooks,
171            post_execute_hooks: self.post_execute_hooks,
172        })
173    }
174}
175
176impl Default for ShimRunnerBuilder {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182/// Customizable shim runner that supports custom loaders and providers
183pub struct CustomizableShimRunner {
184    config: ShimConfig,
185    #[allow(dead_code)]
186    config_file_path: PathBuf,
187    config_loader: Box<dyn ShimConfigLoader>,
188    update_provider: Option<Box<dyn UpdateProvider>>,
189    version_checker: Option<Box<dyn VersionChecker>>,
190    pre_execute_hooks: Vec<PreExecuteHook>,
191    post_execute_hooks: Vec<PostExecuteHook>,
192}
193
194#[async_trait]
195impl ShimRunnerTrait for CustomizableShimRunner {
196    async fn execute(&self, additional_args: &[String]) -> Result<i32> {
197        // Run pre-execute hooks
198        for hook in &self.pre_execute_hooks {
199            hook(additional_args)?;
200        }
201
202        // Check for updates if configured
203        if let (Some(ref update_provider), Some(ref version_checker)) =
204            (&self.update_provider, &self.version_checker)
205        {
206            self.check_and_update(update_provider.as_ref(), version_checker.as_ref())
207                .await?;
208        }
209
210        // Execute the actual command (simplified version)
211        let executable_path = self.config.get_executable_path()?;
212        let exit_code = self.run_executable(&executable_path, additional_args)?;
213
214        // Run post-execute hooks
215        for hook in &self.post_execute_hooks {
216            hook(exit_code)?;
217        }
218
219        Ok(exit_code)
220    }
221
222    fn config(&self) -> &ShimConfig {
223        &self.config
224    }
225
226    fn validate(&self) -> Result<()> {
227        self.config_loader.validate_config(&self.config)
228    }
229}
230
231impl CustomizableShimRunner {
232    /// Check for updates and install if available
233    async fn check_and_update(
234        &self,
235        update_provider: &dyn UpdateProvider,
236        version_checker: &dyn VersionChecker,
237    ) -> Result<()> {
238        let current_version = "1.0.0"; // TODO: Get from config or executable
239
240        if let Some(latest_version) = update_provider
241            .check_update_available(current_version)
242            .await?
243        {
244            if version_checker.is_newer_version(&latest_version, current_version)? {
245                let executable_path = self.config.get_executable_path()?;
246                update_provider
247                    .install_update(&latest_version, &executable_path)
248                    .await?;
249            }
250        }
251
252        Ok(())
253    }
254
255    /// Run the actual executable
256    fn run_executable(&self, executable_path: &Path, args: &[String]) -> Result<i32> {
257        // Simplified implementation - in reality this would be more complex
258        use std::process::Command;
259
260        let status = Command::new(executable_path)
261            .args(args)
262            .status()
263            .map_err(|e| crate::error::ShimError::ProcessExecution(e.to_string()))?;
264
265        Ok(status.code().unwrap_or(-1))
266    }
267}
268
269/// Default TOML configuration loader
270pub struct DefaultConfigLoader;
271
272impl ShimConfigLoader for DefaultConfigLoader {
273    fn load_config(&self, path: &Path) -> Result<ShimConfig> {
274        ShimConfig::from_file(path)
275    }
276
277    fn save_config(&self, config: &ShimConfig, path: &Path) -> Result<()> {
278        config.to_file(path)
279    }
280
281    fn file_extension(&self) -> &str {
282        "shim.toml"
283    }
284}