rez_lsp_server/validation/
mod.rs1pub mod python_validator;
4pub mod rez_validator;
5pub mod validation_engine;
6
7pub use python_validator::PythonValidator;
8pub use rez_validator::RezValidator;
9pub use validation_engine::ValidationEngine;
10
11use crate::core::Result;
12use serde::{Deserialize, Serialize};
13use std::fmt;
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct ValidationIssue {
18 pub severity: Severity,
20 pub line: u32,
22 pub column: u32,
24 pub length: u32,
26 pub message: String,
28 pub code: String,
30 pub suggestion: Option<String>,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
36pub enum Severity {
37 Info,
39 Warning,
41 Error,
43 Critical,
45}
46
47impl fmt::Display for Severity {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 Severity::Info => write!(f, "info"),
51 Severity::Warning => write!(f, "warning"),
52 Severity::Error => write!(f, "error"),
53 Severity::Critical => write!(f, "critical"),
54 }
55 }
56}
57
58impl ValidationIssue {
59 pub fn new(
61 severity: Severity,
62 line: u32,
63 column: u32,
64 length: u32,
65 message: impl Into<String>,
66 code: impl Into<String>,
67 ) -> Self {
68 Self {
69 severity,
70 line,
71 column,
72 length,
73 message: message.into(),
74 code: code.into(),
75 suggestion: None,
76 }
77 }
78
79 pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
81 self.suggestion = Some(suggestion.into());
82 self
83 }
84}
85
86pub trait Validator {
88 fn validate(&self, content: &str, file_path: &str) -> Result<Vec<ValidationIssue>>;
90
91 fn name(&self) -> &str;
93
94 fn version(&self) -> &str {
96 "1.0.0"
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct ValidationResult {
103 pub file_path: String,
105 pub issues: Vec<ValidationIssue>,
107 pub stats: ValidationStats,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ValidationStats {
114 pub total_issues: usize,
116 pub critical_count: usize,
118 pub error_count: usize,
120 pub warning_count: usize,
122 pub info_count: usize,
124 pub validation_time_ms: u64,
126}
127
128impl ValidationResult {
129 pub fn new(
131 file_path: impl Into<String>,
132 issues: Vec<ValidationIssue>,
133 validation_time_ms: u64,
134 ) -> Self {
135 let stats = ValidationStats::from_issues(&issues, validation_time_ms);
136 Self {
137 file_path: file_path.into(),
138 issues,
139 stats,
140 }
141 }
142
143 pub fn has_errors(&self) -> bool {
145 self.stats.error_count > 0 || self.stats.critical_count > 0
146 }
147
148 pub fn has_issues(&self) -> bool {
150 self.stats.total_issues > 0
151 }
152}
153
154impl ValidationStats {
155 pub fn from_issues(issues: &[ValidationIssue], validation_time_ms: u64) -> Self {
157 let mut critical_count = 0;
158 let mut error_count = 0;
159 let mut warning_count = 0;
160 let mut info_count = 0;
161
162 for issue in issues {
163 match issue.severity {
164 Severity::Critical => critical_count += 1,
165 Severity::Error => error_count += 1,
166 Severity::Warning => warning_count += 1,
167 Severity::Info => info_count += 1,
168 }
169 }
170
171 Self {
172 total_issues: issues.len(),
173 critical_count,
174 error_count,
175 warning_count,
176 info_count,
177 validation_time_ms,
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_validation_issue_creation() {
188 let issue = ValidationIssue::new(Severity::Error, 10, 5, 8, "Invalid syntax", "E001");
189
190 assert_eq!(issue.severity, Severity::Error);
191 assert_eq!(issue.line, 10);
192 assert_eq!(issue.column, 5);
193 assert_eq!(issue.length, 8);
194 assert_eq!(issue.message, "Invalid syntax");
195 assert_eq!(issue.code, "E001");
196 assert!(issue.suggestion.is_none());
197 }
198
199 #[test]
200 fn test_validation_issue_with_suggestion() {
201 let issue = ValidationIssue::new(Severity::Warning, 5, 10, 3, "Deprecated field", "W001")
202 .with_suggestion("Use 'new_field' instead");
203
204 assert_eq!(
205 issue.suggestion,
206 Some("Use 'new_field' instead".to_string())
207 );
208 }
209
210 #[test]
211 fn test_validation_stats() {
212 let issues = vec![
213 ValidationIssue::new(Severity::Critical, 1, 1, 1, "Critical", "C001"),
214 ValidationIssue::new(Severity::Error, 2, 1, 1, "Error", "E001"),
215 ValidationIssue::new(Severity::Warning, 3, 1, 1, "Warning", "W001"),
216 ValidationIssue::new(Severity::Info, 4, 1, 1, "Info", "I001"),
217 ];
218
219 let stats = ValidationStats::from_issues(&issues, 100);
220
221 assert_eq!(stats.total_issues, 4);
222 assert_eq!(stats.critical_count, 1);
223 assert_eq!(stats.error_count, 1);
224 assert_eq!(stats.warning_count, 1);
225 assert_eq!(stats.info_count, 1);
226 assert_eq!(stats.validation_time_ms, 100);
227 }
228
229 #[test]
230 fn test_validation_result() {
231 let issues = vec![
232 ValidationIssue::new(Severity::Error, 1, 1, 1, "Error", "E001"),
233 ValidationIssue::new(Severity::Warning, 2, 1, 1, "Warning", "W001"),
234 ];
235
236 let result = ValidationResult::new("test.py", issues, 50);
237
238 assert_eq!(result.file_path, "test.py");
239 assert!(result.has_errors());
240 assert!(result.has_issues());
241 assert_eq!(result.stats.total_issues, 2);
242 assert_eq!(result.stats.validation_time_ms, 50);
243 }
244}