Skip to main content

vtcode_core/skills/
validation_report.rs

1//! Comprehensive validation report for Agent Skills
2//!
3//! Provides detailed validation feedback with multiple error levels and suggestions.
4
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8/// Severity level for validation issues
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum ValidationLevel {
11    /// Critical error that prevents skill from working
12    Error,
13    /// Warning that may cause issues but doesn't prevent usage
14    Warning,
15    /// Suggestion for improvement
16    Info,
17}
18
19/// Single validation issue
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ValidationIssue {
22    pub level: ValidationLevel,
23    pub field: Option<String>,
24    pub message: String,
25    pub suggestion: Option<String>,
26}
27
28/// Comprehensive skill validation report
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SkillValidationReport {
31    pub skill_name: String,
32    pub skill_path: PathBuf,
33    pub is_valid: bool,
34    pub errors: Vec<ValidationIssue>,
35    pub warnings: Vec<ValidationIssue>,
36    pub suggestions: Vec<ValidationIssue>,
37    pub stats: ValidationStats,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ValidationStats {
42    pub total_issues: usize,
43    pub error_count: usize,
44    pub warning_count: usize,
45    pub suggestion_count: usize,
46    pub token_estimate: usize,
47}
48
49impl SkillValidationReport {
50    pub fn new(skill_name: String, skill_path: PathBuf) -> Self {
51        Self {
52            skill_name,
53            skill_path,
54            is_valid: true,
55            errors: Vec::new(),
56            warnings: Vec::new(),
57            suggestions: Vec::new(),
58            stats: ValidationStats {
59                total_issues: 0,
60                error_count: 0,
61                warning_count: 0,
62                suggestion_count: 0,
63                token_estimate: 0,
64            },
65        }
66    }
67
68    pub fn add_error(
69        &mut self,
70        field: Option<String>,
71        message: String,
72        suggestion: Option<String>,
73    ) {
74        self.errors.push(ValidationIssue {
75            level: ValidationLevel::Error,
76            field,
77            message,
78            suggestion,
79        });
80        self.is_valid = false;
81    }
82
83    pub fn add_warning(
84        &mut self,
85        field: Option<String>,
86        message: String,
87        suggestion: Option<String>,
88    ) {
89        self.warnings.push(ValidationIssue {
90            level: ValidationLevel::Warning,
91            field,
92            message,
93            suggestion,
94        });
95    }
96
97    pub fn add_suggestion(&mut self, field: Option<String>, message: String) {
98        self.suggestions.push(ValidationIssue {
99            level: ValidationLevel::Info,
100            field,
101            message,
102            suggestion: None,
103        });
104    }
105
106    pub fn finalize(&mut self) {
107        self.stats.error_count = self.errors.len();
108        self.stats.warning_count = self.warnings.len();
109        self.stats.suggestion_count = self.suggestions.len();
110        self.stats.total_issues =
111            self.stats.error_count + self.stats.warning_count + self.stats.suggestion_count;
112    }
113
114    pub fn generate_summary(&self) -> String {
115        let mut summary = String::new();
116
117        summary.push_str(&format!("Skill: {}\n", self.skill_name));
118        summary.push_str(&format!("Path: {}\n", self.skill_path.display()));
119        summary.push_str(&format!(
120            "Status: {}\n",
121            if self.is_valid {
122                "v Valid"
123            } else {
124                "x Invalid"
125            }
126        ));
127        summary.push_str("\nIssues found:\n");
128        summary.push_str(&format!("  Errors: {}\n", self.stats.error_count));
129        summary.push_str(&format!("  Warnings: {}\n", self.stats.warning_count));
130        summary.push_str(&format!("  Suggestions: {}\n", self.stats.suggestion_count));
131
132        if !self.errors.is_empty() {
133            summary.push_str("\nx Errors:\n");
134            for error in &self.errors {
135                summary.push_str(&format!("  - {}", error.message));
136                if let Some(field) = &error.field {
137                    summary.push_str(&format!(" [{}]", field));
138                }
139                summary.push('\n');
140                if let Some(suggestion) = &error.suggestion {
141                    summary.push_str(&format!("    šŸ’” Suggestion: {}\n", suggestion));
142                }
143            }
144        }
145
146        if !self.warnings.is_empty() {
147            summary.push_str("\n[!]  Warnings:\n");
148            for warning in &self.warnings {
149                summary.push_str(&format!("  - {}", warning.message));
150                if let Some(field) = &warning.field {
151                    summary.push_str(&format!(" [{}]", field));
152                }
153                summary.push('\n');
154            }
155        }
156
157        if !self.suggestions.is_empty() {
158            summary.push_str("\nšŸ’” Suggestions:\n");
159            for suggestion in &self.suggestions {
160                summary.push_str(&format!("  - {}", suggestion.message));
161                if let Some(field) = &suggestion.field {
162                    summary.push_str(&format!(" [{}]", field));
163                }
164                summary.push('\n');
165            }
166        }
167
168        summary
169    }
170
171    pub fn to_json(&self) -> Result<String, serde_json::Error> {
172        serde_json::to_string_pretty(self)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use std::path::PathBuf;
180
181    #[test]
182    fn test_validation_report() {
183        let mut report =
184            SkillValidationReport::new("test-skill".to_string(), PathBuf::from("/tmp/test-skill"));
185
186        report.add_error(
187            Some("name".to_string()),
188            "Invalid characters in name".to_string(),
189            Some("Use only lowercase letters, numbers, and hyphens".to_string()),
190        );
191
192        report.add_warning(
193            None,
194            "Description is very short".to_string(),
195            Some("Consider adding more detail about when to use this skill".to_string()),
196        );
197
198        report.finalize();
199
200        assert!(!report.is_valid);
201        assert_eq!(report.stats.error_count, 1);
202        assert_eq!(report.stats.warning_count, 1);
203        assert!(report.generate_summary().contains("Invalid characters"));
204    }
205}