turbovault_tools/
validation_tools.rs1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5use std::sync::Arc;
6use turbovault_core::{
7 CompositeValidator, ContentValidator, FrontmatterValidator, LinkValidator, Result, Severity,
8 ValidationReport, Validator,
9};
10use turbovault_vault::VaultManager;
11
12pub struct ValidationTools {
14 pub manager: Arc<VaultManager>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ValidationIssueInfo {
20 pub severity: String,
21 pub category: String,
22 pub message: String,
23 pub line: Option<usize>,
24 pub suggestion: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ValidationReportInfo {
30 pub passed: bool,
31 pub total_issues: usize,
32 pub info_count: usize,
33 pub warning_count: usize,
34 pub error_count: usize,
35 pub critical_count: usize,
36 pub issues: Vec<ValidationIssueInfo>,
37}
38
39impl ValidationTools {
40 pub fn new(manager: Arc<VaultManager>) -> Self {
42 Self { manager }
43 }
44
45 pub async fn validate_note(&self, path: &str) -> Result<ValidationReportInfo> {
47 let file_path = PathBuf::from(path);
48 let vault_file = self.manager.parse_file(&file_path).await?;
49
50 let validator = CompositeValidator::default_rules();
51 let report = validator.validate(&vault_file);
52
53 Ok(Self::convert_report(report))
54 }
55
56 pub async fn validate_note_with_rules(
58 &self,
59 path: &str,
60 require_frontmatter: bool,
61 required_fields: Vec<String>,
62 check_links: bool,
63 min_length: Option<usize>,
64 ) -> Result<ValidationReportInfo> {
65 let file_path = PathBuf::from(path);
66 let vault_file = self.manager.parse_file(&file_path).await?;
67
68 let mut validator = CompositeValidator::new();
69
70 if require_frontmatter || !required_fields.is_empty() {
72 let mut fm_validator = FrontmatterValidator::new();
73 for field in required_fields {
74 fm_validator = fm_validator.require_field(field);
75 }
76 validator = validator.add_validator(Box::new(fm_validator));
77 }
78
79 if check_links {
81 validator = validator.add_validator(Box::new(LinkValidator::new()));
82 }
83
84 if let Some(min) = min_length {
86 let content_validator = ContentValidator::new().min_length(min);
87 validator = validator.add_validator(Box::new(content_validator));
88 }
89
90 let report = validator.validate(&vault_file);
91
92 Ok(Self::convert_report(report))
93 }
94
95 pub async fn validate_vault(&self) -> Result<ValidationReportInfo> {
97 let files = self.manager.scan_vault().await?;
98
99 let validator = CompositeValidator::default_rules();
100 let mut combined_report = ValidationReport::new();
101
102 for file_path in files {
103 if let Ok(vault_file) = self.manager.parse_file(&file_path).await {
104 let report = validator.validate(&vault_file);
105 combined_report.merge(report);
106 }
107 }
108
109 Ok(Self::convert_report(combined_report))
110 }
111
112 pub async fn validate_vault_quick(&self, max_issues: usize) -> Result<ValidationReportInfo> {
114 let files = self.manager.scan_vault().await?;
115
116 let validator = CompositeValidator::default_rules();
117 let mut combined_report = ValidationReport::new();
118
119 for file_path in files {
120 if combined_report.total_issues() >= max_issues {
122 break;
123 }
124
125 if let Ok(vault_file) = self.manager.parse_file(&file_path).await {
126 let report = validator.validate(&vault_file);
127 combined_report.merge(report);
128 }
129 }
130
131 Ok(Self::convert_report(combined_report))
132 }
133
134 fn convert_report(report: ValidationReport) -> ValidationReportInfo {
136 ValidationReportInfo {
137 passed: report.passed,
138 total_issues: report.total_issues(),
139 info_count: report.summary.info_count,
140 warning_count: report.summary.warning_count,
141 error_count: report.summary.error_count,
142 critical_count: report.summary.critical_count,
143 issues: report
144 .issues
145 .into_iter()
146 .map(|issue| ValidationIssueInfo {
147 severity: Self::severity_to_string(issue.severity),
148 category: issue.category,
149 message: issue.message,
150 line: issue.line,
151 suggestion: issue.suggestion,
152 })
153 .collect(),
154 }
155 }
156
157 fn severity_to_string(severity: Severity) -> String {
159 match severity {
160 Severity::Info => "info".to_string(),
161 Severity::Warning => "warning".to_string(),
162 Severity::Error => "error".to_string(),
163 Severity::Critical => "critical".to_string(),
164 }
165 }
166}