use super::{Adapter, AdapterError, AdapterOutput};
use crate::core::types::ExecutorKind;
use std::time::Instant;
pub type RustAdapterFn =
Box<dyn Fn(&str, &str) -> Result<(String, String), (i32, String, String)> + Send + Sync>;
pub struct RustAdapter {
executor: RustAdapterFn,
}
impl RustAdapter {
pub fn new(executor: RustAdapterFn) -> Self {
Self { executor }
}
pub fn simple_success(message_prefix: &str) -> Self {
let prefix = message_prefix.to_string();
Self::new(Box::new(move |id, version| {
Ok((format!("{prefix}: {id} {version} installed"), String::new()))
}))
}
pub fn always_fails(exit_code: i32, message: &str) -> Self {
let msg = message.to_string();
Self::new(Box::new(move |_id, _version| {
Err((exit_code, String::new(), msg.clone()))
}))
}
}
impl Adapter for RustAdapter {
fn execute(&self, component_id: &str, version: &str) -> Result<AdapterOutput, AdapterError> {
let start = Instant::now();
match (self.executor)(component_id, version) {
Ok((stdout, stderr)) => Ok(AdapterOutput {
component_id: component_id.to_string(),
executor_kind: ExecutorKind::Rust,
stdout,
stderr,
exit_code: 0,
duration_ms: start.elapsed().as_millis() as u64,
}),
Err((exit_code, stdout, stderr)) => Ok(AdapterOutput {
component_id: component_id.to_string(),
executor_kind: ExecutorKind::Rust,
stdout,
stderr: stderr.clone(),
exit_code,
duration_ms: start.elapsed().as_millis() as u64,
}),
}
}
fn executor_kind(&self) -> ExecutorKind {
ExecutorKind::Rust
}
}
pub struct ShellOutputFormat;
impl ShellOutputFormat {
pub fn success(component_id: &str, version: &str) -> String {
let display = crate::platform::registry::display_name(component_id);
format!("{display} {version} installed successfully")
}
pub fn failure(component_id: &str, reason: &str) -> String {
let display = crate::platform::registry::display_name(component_id);
format!("Error: {display} installation failed: {reason}")
}
pub fn partial(component_id: &str, details: &str) -> String {
let display = crate::platform::registry::display_name(component_id);
format!("Warning: {display} partially installed: {details}")
}
pub fn status(component_id: &str, status: &str) -> String {
let display = crate::platform::registry::display_name(component_id);
format!("{display} {status}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rust_adapter_success() {
let adapter = RustAdapter::new(Box::new(|id, version| {
Ok((format!("{id}:{version}:ok"), String::new()))
}));
let result = adapter.execute("pytorch", "2.5.0").unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout, "pytorch:2.5.0:ok");
assert!(result.stderr.is_empty());
assert_eq!(result.executor_kind, ExecutorKind::Rust);
}
#[test]
fn test_rust_adapter_failure() {
let adapter = RustAdapter::new(Box::new(|_id, _version| {
Err((1, String::new(), "installation error".to_string()))
}));
let result = adapter.execute("pytorch", "2.5.0").unwrap();
assert_eq!(result.exit_code, 1);
assert_eq!(result.stderr, "installation error");
assert!(!result.is_success());
}
#[test]
fn test_rust_adapter_partial_failure() {
let adapter = RustAdapter::new(Box::new(|_id, _version| {
Err((2, "partial output".to_string(), "some warnings".to_string()))
}));
let result = adapter.execute("triton", "3.1.0").unwrap();
assert_eq!(result.exit_code, 2);
assert_eq!(result.stdout, "partial output");
assert_eq!(result.stderr, "some warnings");
}
#[test]
fn test_rust_adapter_no_shell_subprocess() {
let adapter = RustAdapter::new(Box::new(|id, version| {
let result = format!("computed: {id}@{version}");
Ok((result, String::new()))
}));
let result = adapter.execute("test", "1.0.0").unwrap();
assert_eq!(result.stdout, "computed: test@1.0.0");
assert_eq!(result.executor_kind, ExecutorKind::Rust);
}
#[test]
fn test_rust_adapter_simple_success() {
let adapter = RustAdapter::simple_success("mock");
let result = adapter.execute("pytorch", "2.5.0").unwrap();
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("pytorch"));
assert!(result.stdout.contains("2.5.0"));
}
#[test]
fn test_rust_adapter_always_fails() {
let adapter = RustAdapter::always_fails(42, "always fails");
let result = adapter.execute("test", "1.0.0").unwrap();
assert_eq!(result.exit_code, 42);
assert_eq!(result.stderr, "always fails");
}
#[test]
fn test_shell_output_format_success() {
let output = ShellOutputFormat::success("pytorch", "2.5.0");
assert!(output.contains("PyTorch"));
assert!(output.contains("2.5.0"));
assert!(output.contains("installed successfully"));
}
#[test]
fn test_shell_output_format_failure() {
let output = ShellOutputFormat::failure("pytorch", "build error");
assert!(output.contains("PyTorch"));
assert!(output.contains("Error:"));
assert!(output.contains("build error"));
}
#[test]
fn test_shell_output_format_partial() {
let output = ShellOutputFormat::partial("triton", "missing optional deps");
assert!(output.contains("Triton"));
assert!(output.contains("Warning:"));
assert!(output.contains("missing optional deps"));
}
#[test]
fn test_shell_output_format_status() {
let output = ShellOutputFormat::status("rocm", "detected");
assert!(output.contains("ROCm"));
assert!(output.contains("detected"));
}
#[test]
fn test_rust_adapter_tracks_duration() {
let adapter = RustAdapter::new(Box::new(|_id, _version| {
std::thread::sleep(std::time::Duration::from_millis(1));
Ok(("done".to_string(), String::new()))
}));
let result = adapter.execute("test", "1.0.0").unwrap();
assert!(result.duration_ms > 0, "Duration should be non-zero");
}
#[test]
fn test_rust_adapter_executor_kind() {
let adapter = RustAdapter::simple_success("test");
assert_eq!(adapter.executor_kind(), ExecutorKind::Rust);
}
}