rusty_stack/adapter/
rust_adapter.rs1use super::{Adapter, AdapterError, AdapterOutput};
20use crate::core::types::ExecutorKind;
21use std::time::Instant;
22
23pub type RustAdapterFn =
33 Box<dyn Fn(&str, &str) -> Result<(String, String), (i32, String, String)> + Send + Sync>;
34
35pub struct RustAdapter {
59 executor: RustAdapterFn,
61}
62
63impl RustAdapter {
64 pub fn new(executor: RustAdapterFn) -> Self {
66 Self { executor }
67 }
68
69 pub fn simple_success(message_prefix: &str) -> Self {
71 let prefix = message_prefix.to_string();
72 Self::new(Box::new(move |id, version| {
73 Ok((format!("{prefix}: {id} {version} installed"), String::new()))
74 }))
75 }
76
77 pub fn always_fails(exit_code: i32, message: &str) -> Self {
79 let msg = message.to_string();
80 Self::new(Box::new(move |_id, _version| {
81 Err((exit_code, String::new(), msg.clone()))
82 }))
83 }
84}
85
86impl Adapter for RustAdapter {
87 fn execute(&self, component_id: &str, version: &str) -> Result<AdapterOutput, AdapterError> {
88 let start = Instant::now();
89
90 match (self.executor)(component_id, version) {
91 Ok((stdout, stderr)) => Ok(AdapterOutput {
92 component_id: component_id.to_string(),
93 executor_kind: ExecutorKind::Rust,
94 stdout,
95 stderr,
96 exit_code: 0,
97 duration_ms: start.elapsed().as_millis() as u64,
98 }),
99 Err((exit_code, stdout, stderr)) => Ok(AdapterOutput {
100 component_id: component_id.to_string(),
101 executor_kind: ExecutorKind::Rust,
102 stdout,
103 stderr: stderr.clone(),
104 exit_code,
105 duration_ms: start.elapsed().as_millis() as u64,
106 }),
107 }
108 }
109
110 fn executor_kind(&self) -> ExecutorKind {
111 ExecutorKind::Rust
112 }
113}
114
115pub struct ShellOutputFormat;
124
125impl ShellOutputFormat {
126 pub fn success(component_id: &str, version: &str) -> String {
130 let display = crate::platform::registry::display_name(component_id);
131 format!("{display} {version} installed successfully")
132 }
133
134 pub fn failure(component_id: &str, reason: &str) -> String {
138 let display = crate::platform::registry::display_name(component_id);
139 format!("Error: {display} installation failed: {reason}")
140 }
141
142 pub fn partial(component_id: &str, details: &str) -> String {
146 let display = crate::platform::registry::display_name(component_id);
147 format!("Warning: {display} partially installed: {details}")
148 }
149
150 pub fn status(component_id: &str, status: &str) -> String {
154 let display = crate::platform::registry::display_name(component_id);
155 format!("{display} {status}")
156 }
157}
158
159#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
172 fn test_rust_adapter_success() {
173 let adapter = RustAdapter::new(Box::new(|id, version| {
174 Ok((format!("{id}:{version}:ok"), String::new()))
175 }));
176
177 let result = adapter.execute("pytorch", "2.5.0").unwrap();
178 assert_eq!(result.exit_code, 0);
179 assert_eq!(result.stdout, "pytorch:2.5.0:ok");
180 assert!(result.stderr.is_empty());
181 assert_eq!(result.executor_kind, ExecutorKind::Rust);
182 }
183
184 #[test]
185 fn test_rust_adapter_failure() {
186 let adapter = RustAdapter::new(Box::new(|_id, _version| {
187 Err((1, String::new(), "installation error".to_string()))
188 }));
189
190 let result = adapter.execute("pytorch", "2.5.0").unwrap();
191 assert_eq!(result.exit_code, 1);
192 assert_eq!(result.stderr, "installation error");
193 assert!(!result.is_success());
194 }
195
196 #[test]
197 fn test_rust_adapter_partial_failure() {
198 let adapter = RustAdapter::new(Box::new(|_id, _version| {
199 Err((2, "partial output".to_string(), "some warnings".to_string()))
200 }));
201
202 let result = adapter.execute("triton", "3.1.0").unwrap();
203 assert_eq!(result.exit_code, 2);
204 assert_eq!(result.stdout, "partial output");
205 assert_eq!(result.stderr, "some warnings");
206 }
207
208 #[test]
209 fn test_rust_adapter_no_shell_subprocess() {
210 let adapter = RustAdapter::new(Box::new(|id, version| {
213 let result = format!("computed: {id}@{version}");
215 Ok((result, String::new()))
216 }));
217
218 let result = adapter.execute("test", "1.0.0").unwrap();
219 assert_eq!(result.stdout, "computed: test@1.0.0");
220 assert_eq!(result.executor_kind, ExecutorKind::Rust);
221 }
222
223 #[test]
228 fn test_rust_adapter_simple_success() {
229 let adapter = RustAdapter::simple_success("mock");
230 let result = adapter.execute("pytorch", "2.5.0").unwrap();
231 assert_eq!(result.exit_code, 0);
232 assert!(result.stdout.contains("pytorch"));
233 assert!(result.stdout.contains("2.5.0"));
234 }
235
236 #[test]
237 fn test_rust_adapter_always_fails() {
238 let adapter = RustAdapter::always_fails(42, "always fails");
239 let result = adapter.execute("test", "1.0.0").unwrap();
240 assert_eq!(result.exit_code, 42);
241 assert_eq!(result.stderr, "always fails");
242 }
243
244 #[test]
249 fn test_shell_output_format_success() {
250 let output = ShellOutputFormat::success("pytorch", "2.5.0");
251 assert!(output.contains("PyTorch"));
252 assert!(output.contains("2.5.0"));
253 assert!(output.contains("installed successfully"));
254 }
255
256 #[test]
257 fn test_shell_output_format_failure() {
258 let output = ShellOutputFormat::failure("pytorch", "build error");
259 assert!(output.contains("PyTorch"));
260 assert!(output.contains("Error:"));
261 assert!(output.contains("build error"));
262 }
263
264 #[test]
265 fn test_shell_output_format_partial() {
266 let output = ShellOutputFormat::partial("triton", "missing optional deps");
267 assert!(output.contains("Triton"));
268 assert!(output.contains("Warning:"));
269 assert!(output.contains("missing optional deps"));
270 }
271
272 #[test]
273 fn test_shell_output_format_status() {
274 let output = ShellOutputFormat::status("rocm", "detected");
275 assert!(output.contains("ROCm"));
276 assert!(output.contains("detected"));
277 }
278
279 #[test]
284 fn test_rust_adapter_tracks_duration() {
285 let adapter = RustAdapter::new(Box::new(|_id, _version| {
286 std::thread::sleep(std::time::Duration::from_millis(1));
288 Ok(("done".to_string(), String::new()))
289 }));
290
291 let result = adapter.execute("test", "1.0.0").unwrap();
292 assert!(result.duration_ms > 0, "Duration should be non-zero");
293 }
294
295 #[test]
300 fn test_rust_adapter_executor_kind() {
301 let adapter = RustAdapter::simple_success("test");
302 assert_eq!(adapter.executor_kind(), ExecutorKind::Rust);
303 }
304}