aether_core/
validation.rs1use crate::{Result, SlotKind};
2use std::process::Command;
3use std::io::Write;
4use tempfile::NamedTempFile;
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum ValidationResult {
9 Valid,
11 Invalid(String),
13}
14
15pub trait Validator: Send + Sync {
17 fn validate(&self, kind: &SlotKind, code: &str) -> Result<ValidationResult>;
19
20 fn format(&self, kind: &SlotKind, code: &str) -> Result<String>;
22}
23
24pub struct RustValidator;
26
27impl Validator for RustValidator {
28 fn validate(&self, kind: &SlotKind, code: &str) -> Result<ValidationResult> {
29 match kind {
31 SlotKind::Function | SlotKind::Class | SlotKind::Component => {
32 let has_tests = code.contains("#[test]");
35
36 let mut tmp_file = NamedTempFile::new()
37 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
38
39 let wrapper = if has_tests {
40 code.to_string()
41 } else {
42 format!(
43 "#[allow(dead_code, unused_variables, unused_imports)]\nmod validation_module {{\n{}\n}}",
44 code
45 )
46 };
47
48 tmp_file.write_all(wrapper.as_bytes())
49 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
50
51 let output = Command::new("rustc")
53 .arg("--crate-type=lib")
54 .arg("--emit=metadata")
55 .arg("-o")
56 .arg("NUL")
57 .arg(tmp_file.path())
58 .output()
59 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
60
61 if !output.status.success() {
62 let err = String::from_utf8_lossy(&output.stderr).to_string();
63 return Ok(ValidationResult::Invalid(format!("Compilation Error:\n{}", err)));
64 }
65
66 if has_tests {
68 let test_exe = NamedTempFile::new()
70 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
71
72 let test_compile = Command::new("rustc")
73 .arg("--test")
74 .arg("-o")
75 .arg(test_exe.path())
76 .arg(tmp_file.path())
77 .output()
78 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
79
80 if !test_compile.status.success() {
81 let err = String::from_utf8_lossy(&test_compile.stderr).to_string();
82 return Ok(ValidationResult::Invalid(format!("Test Compilation Error:\n{}", err)));
83 }
84
85 let test_run = Command::new(test_exe.path())
86 .output()
87 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
88
89 if !test_run.status.success() {
90 let err = String::from_utf8_lossy(&test_run.stdout).to_string(); let stderr = String::from_utf8_lossy(&test_run.stderr).to_string();
92 return Ok(ValidationResult::Invalid(format!("Unit Test Failed:\n{}\n{}", err, stderr)));
93 }
94 }
95
96 Ok(ValidationResult::Valid)
97 }
98 _ => Ok(ValidationResult::Valid),
99 }
100 }
101
102 fn format(&self, kind: &SlotKind, code: &str) -> Result<String> {
103 match kind {
104 SlotKind::Function | SlotKind::Class | SlotKind::Component | SlotKind::JavaScript => {
105 let mut tmp_file = NamedTempFile::new()
106 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
107
108 tmp_file.write_all(code.as_bytes())
109 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
110
111 let output = Command::new("rustfmt")
113 .arg(tmp_file.path())
114 .output();
115
116 if let Ok(out) = output {
117 if out.status.success() {
118 let formatted = std::fs::read_to_string(tmp_file.path())
119 .map_err(|e| crate::AetherError::InjectionError(e.to_string()))?;
120 return Ok(formatted);
121 }
122 }
123
124 Ok(code.to_string())
125 }
126 _ => Ok(code.to_string()),
127 }
128 }
129}