use thiserror::Error;
use super::types::VerificationPointSpec;
#[derive(Debug, Clone, Error)]
pub enum VerificationError {
#[error("Module not found: {0}")]
ModuleNotFound(String),
#[error("Type not found: {0}")]
TypeNotFound(String),
#[error("Missing derive on {ty}: {derive}")]
MissingDerive { ty: String, derive: String },
#[error("Missing field on {ty}: {field}")]
MissingField { ty: String, field: String },
#[error("Method not found: {ty}::{method}")]
MethodNotFound { ty: String, method: String },
#[error("Compilation failed: {0}")]
CompilationFailed(String),
#[error("Warning detected: {0}")]
WarningDetected(String),
}
#[derive(Debug, Clone)]
pub enum VerificationPoint {
ModuleExists(Vec<String>),
TypeExists(Vec<String>),
HasDerive { ty: String, derives: Vec<String> },
HasField {
ty: String,
field: String,
field_type: String,
},
MethodExists { ty: String, method: String },
Compiles,
NoWarnings { allowed: Vec<String> },
}
impl From<VerificationPointSpec> for VerificationPoint {
fn from(spec: VerificationPointSpec) -> Self {
match spec {
VerificationPointSpec::ModuleExists { paths } => VerificationPoint::ModuleExists(paths),
VerificationPointSpec::TypeExists { types } => VerificationPoint::TypeExists(types),
VerificationPointSpec::HasDerive { ty, derives } => {
VerificationPoint::HasDerive { ty, derives }
}
VerificationPointSpec::HasField {
ty,
field,
field_type,
} => VerificationPoint::HasField {
ty,
field,
field_type,
},
VerificationPointSpec::MethodExists { ty, method } => {
VerificationPoint::MethodExists { ty, method }
}
VerificationPointSpec::Compiles => VerificationPoint::Compiles,
VerificationPointSpec::NoWarnings { allowed } => {
VerificationPoint::NoWarnings { allowed }
}
}
}
}
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub phase: String,
pub total: usize,
pub passed: usize,
pub failures: Vec<(VerificationPoint, VerificationError)>,
}
impl VerificationResult {
pub fn all_passed(&self) -> bool {
self.failures.is_empty()
}
}
pub struct VerificationRunner {
project_root: std::path::PathBuf,
}
impl VerificationRunner {
pub fn new(project_root: impl Into<std::path::PathBuf>) -> Self {
Self {
project_root: project_root.into(),
}
}
pub fn run(&self, phase: &str, points: &[VerificationPoint]) -> VerificationResult {
let mut passed = 0;
let mut failures = Vec::new();
for point in points {
match self.verify_point(point) {
Ok(()) => passed += 1,
Err(e) => failures.push((point.clone(), e)),
}
}
VerificationResult {
phase: phase.to_string(),
total: points.len(),
passed,
failures,
}
}
fn verify_point(&self, point: &VerificationPoint) -> Result<(), VerificationError> {
match point {
VerificationPoint::ModuleExists(paths) => {
for path in paths {
self.check_module_exists(path)?;
}
Ok(())
}
VerificationPoint::TypeExists(types) => {
for ty in types {
self.check_type_exists(ty)?;
}
Ok(())
}
VerificationPoint::HasDerive { ty, derives } => {
for derive in derives {
self.check_has_derive(ty, derive)?;
}
Ok(())
}
VerificationPoint::HasField {
ty,
field,
field_type,
} => self.check_has_field(ty, field, field_type),
VerificationPoint::MethodExists { ty, method } => self.check_method_exists(ty, method),
VerificationPoint::Compiles => self.check_compiles(),
VerificationPoint::NoWarnings { allowed } => self.check_no_warnings(allowed),
}
}
fn check_module_exists(&self, path: &str) -> Result<(), VerificationError> {
let file_path = if path.contains("::") {
let parts: Vec<&str> = path.split("::").collect();
format!("src/{}.rs", parts.join("/"))
} else {
format!("src/{}.rs", path)
};
let full_path = self.project_root.join(&file_path);
let mod_path = if path.contains("::") {
let parts: Vec<&str> = path.split("::").collect();
format!("src/{}/mod.rs", parts.join("/"))
} else {
format!("src/{}/mod.rs", path)
};
let full_mod_path = self.project_root.join(&mod_path);
if full_path.exists() || full_mod_path.exists() {
Ok(())
} else {
Err(VerificationError::ModuleNotFound(path.to_string()))
}
}
fn check_type_exists(&self, ty: &str) -> Result<(), VerificationError> {
let src_path = self.project_root.join("src");
if !src_path.exists() {
return Err(VerificationError::TypeNotFound(ty.to_string()));
}
for entry in walkdir::WalkDir::new(&src_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
{
if let Ok(content) = std::fs::read_to_string(entry.path()) {
let patterns = [
format!("struct {} ", ty),
format!("struct {}(", ty),
format!("struct {} {{", ty),
format!("enum {} ", ty),
format!("enum {} {{", ty),
];
for pattern in &patterns {
if content.contains(pattern) {
return Ok(());
}
}
}
}
Err(VerificationError::TypeNotFound(ty.to_string()))
}
fn check_has_derive(&self, ty: &str, derive: &str) -> Result<(), VerificationError> {
let src_path = self.project_root.join("src");
for entry in walkdir::WalkDir::new(&src_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
{
if let Ok(content) = std::fs::read_to_string(entry.path()) {
if let Some(pos) = content.find(&format!("struct {}", ty)) {
let before = &content[..pos];
if let Some(derive_pos) = before.rfind("#[derive(") {
let derive_block = &before[derive_pos..];
if derive_block.contains(derive) {
return Ok(());
}
}
}
if let Some(pos) = content.find(&format!("enum {}", ty)) {
let before = &content[..pos];
if let Some(derive_pos) = before.rfind("#[derive(") {
let derive_block = &before[derive_pos..];
if derive_block.contains(derive) {
return Ok(());
}
}
}
}
}
Err(VerificationError::MissingDerive {
ty: ty.to_string(),
derive: derive.to_string(),
})
}
fn check_has_field(
&self,
ty: &str,
field: &str,
_field_type: &str,
) -> Result<(), VerificationError> {
let src_path = self.project_root.join("src");
for entry in walkdir::WalkDir::new(&src_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
{
if let Ok(content) = std::fs::read_to_string(entry.path()) {
if let Some(pos) = content.find(&format!("struct {} {{", ty)) {
if let Some(end) = content[pos..].find('}') {
let struct_body = &content[pos..pos + end];
if struct_body.contains(&field.to_string()) {
return Ok(());
}
}
}
}
}
Err(VerificationError::MissingField {
ty: ty.to_string(),
field: field.to_string(),
})
}
fn check_method_exists(&self, ty: &str, method: &str) -> Result<(), VerificationError> {
let src_path = self.project_root.join("src");
for entry in walkdir::WalkDir::new(&src_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
{
if let Ok(content) = std::fs::read_to_string(entry.path()) {
if content.contains(&format!("impl {}", ty))
&& content.contains(&format!("fn {}(", method))
{
return Ok(());
}
}
}
Err(VerificationError::MethodNotFound {
ty: ty.to_string(),
method: method.to_string(),
})
}
fn check_compiles(&self) -> Result<(), VerificationError> {
let output = std::process::Command::new("cargo")
.arg("check")
.current_dir(&self.project_root)
.output();
match output {
Ok(out) if out.status.success() => Ok(()),
Ok(out) => {
let stderr = String::from_utf8_lossy(&out.stderr);
Err(VerificationError::CompilationFailed(stderr.to_string()))
}
Err(e) => Err(VerificationError::CompilationFailed(e.to_string())),
}
}
fn check_no_warnings(&self, allowed: &[String]) -> Result<(), VerificationError> {
let output = std::process::Command::new("cargo")
.args(["check", "--message-format=short"])
.current_dir(&self.project_root)
.output();
match output {
Ok(out) => {
let stderr = String::from_utf8_lossy(&out.stderr);
for line in stderr.lines() {
if line.contains("warning:") {
let is_allowed = allowed.iter().any(|a| line.contains(a));
if !is_allowed {
return Err(VerificationError::WarningDetected(line.to_string()));
}
}
}
Ok(())
}
Err(e) => Err(VerificationError::CompilationFailed(e.to_string())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verification_result() {
let result = VerificationResult {
phase: "test".to_string(),
total: 3,
passed: 3,
failures: vec![],
};
assert!(result.all_passed());
}
#[test]
fn test_verification_point_from_spec() {
let spec = VerificationPointSpec::ModuleExists {
paths: vec!["user".to_string(), "product".to_string()],
};
let point: VerificationPoint = spec.into();
if let VerificationPoint::ModuleExists(paths) = point {
assert_eq!(paths.len(), 2);
} else {
panic!("Expected ModuleExists");
}
}
}