1use colored::Colorize;
7use serde::{Deserialize, Serialize};
8use std::path::Path;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ValidationResult {
13 pub passed: bool,
15 pub errors: Vec<ValidationError>,
17 pub warnings: Vec<ValidationWarning>,
19 pub info: Vec<String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ValidationError {
26 pub category: ErrorCategory,
28 pub message: String,
30 pub location: Option<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ValidationWarning {
37 pub category: WarningCategory,
39 pub message: String,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45pub enum ErrorCategory {
46 Format,
48 Integrity,
50 Metadata,
52 Projection,
54 Bounds,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60pub enum WarningCategory {
61 Performance,
63 Compatibility,
65 BestPractices,
67}
68
69impl ValidationResult {
70 pub fn new() -> Self {
72 Self {
73 passed: true,
74 errors: Vec::new(),
75 warnings: Vec::new(),
76 info: Vec::new(),
77 }
78 }
79
80 pub fn add_error(&mut self, category: ErrorCategory, message: impl Into<String>) {
82 self.passed = false;
83 self.errors.push(ValidationError {
84 category,
85 message: message.into(),
86 location: None,
87 });
88 }
89
90 pub fn add_warning(&mut self, category: WarningCategory, message: impl Into<String>) {
92 self.warnings.push(ValidationWarning {
93 category,
94 message: message.into(),
95 });
96 }
97
98 pub fn add_info(&mut self, message: impl Into<String>) {
100 self.info.push(message.into());
101 }
102
103 pub fn report(&self) -> String {
105 let mut report = String::new();
106 report.push_str(&format!("\n{}\n", "Validation Report".bold()));
107 report.push_str(&format!("{}\n\n", "=".repeat(60)));
108
109 let status = if self.passed {
110 "PASSED".green().bold()
111 } else {
112 "FAILED".red().bold()
113 };
114 report.push_str(&format!("Status: {}\n\n", status));
115
116 if !self.errors.is_empty() {
117 report.push_str(&format!(
118 "{} ({}):\n",
119 "Errors".red().bold(),
120 self.errors.len()
121 ));
122 for (i, error) in self.errors.iter().enumerate() {
123 report.push_str(&format!(
124 " {}. [{:?}] {}\n",
125 i + 1,
126 error.category,
127 error.message
128 ));
129 }
130 report.push('\n');
131 }
132
133 if !self.warnings.is_empty() {
134 report.push_str(&format!(
135 "{} ({}):\n",
136 "Warnings".yellow().bold(),
137 self.warnings.len()
138 ));
139 for (i, warning) in self.warnings.iter().enumerate() {
140 report.push_str(&format!(
141 " {}. [{:?}] {}\n",
142 i + 1,
143 warning.category,
144 warning.message
145 ));
146 }
147 report.push('\n');
148 }
149
150 if !self.info.is_empty() {
151 report.push_str(&format!("{}:\n", "Info".cyan()));
152 for info in &self.info {
153 report.push_str(&format!(" - {}\n", info));
154 }
155 }
156
157 report
158 }
159}
160
161impl Default for ValidationResult {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167pub struct DataValidator;
169
170impl DataValidator {
171 pub fn validate_raster_dimensions(
173 width: usize,
174 height: usize,
175 bands: usize,
176 ) -> ValidationResult {
177 let mut result = ValidationResult::new();
178
179 if width == 0 {
180 result.add_error(ErrorCategory::Format, "Width cannot be zero");
181 }
182 if height == 0 {
183 result.add_error(ErrorCategory::Format, "Height cannot be zero");
184 }
185 if bands == 0 {
186 result.add_error(ErrorCategory::Format, "Bands cannot be zero");
187 }
188
189 if width > 100000 || height > 100000 {
190 result.add_warning(
191 WarningCategory::Performance,
192 format!("Large raster dimensions: {}x{}", width, height),
193 );
194 }
195
196 result.add_info(format!("Dimensions: {}x{}x{}", width, height, bands));
197
198 result
199 }
200
201 pub fn validate_bounds(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> ValidationResult {
203 let mut result = ValidationResult::new();
204
205 if min_x >= max_x {
206 result.add_error(
207 ErrorCategory::Bounds,
208 format!("Invalid X bounds: min ({}) >= max ({})", min_x, max_x),
209 );
210 }
211
212 if min_y >= max_y {
213 result.add_error(
214 ErrorCategory::Bounds,
215 format!("Invalid Y bounds: min ({}) >= max ({})", min_y, max_y),
216 );
217 }
218
219 if !min_x.is_finite() || !min_y.is_finite() || !max_x.is_finite() || !max_y.is_finite() {
220 result.add_error(ErrorCategory::Bounds, "Bounds contain non-finite values");
221 }
222
223 result.add_info(format!(
224 "Bounds: [{}, {}] x [{}, {}]",
225 min_x, min_y, max_x, max_y
226 ));
227
228 result
229 }
230
231 pub fn validate_data_range(
233 data: &[f64],
234 expected_min: f64,
235 expected_max: f64,
236 ) -> ValidationResult {
237 let mut result = ValidationResult::new();
238
239 if data.is_empty() {
240 result.add_error(ErrorCategory::Integrity, "Empty data array");
241 return result;
242 }
243
244 let (actual_min, actual_max) = data
245 .iter()
246 .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), &v| {
247 (min.min(v), max.max(v))
248 });
249
250 if actual_min < expected_min || actual_max > expected_max {
251 result.add_warning(
252 WarningCategory::BestPractices,
253 format!(
254 "Data range [{}, {}] outside expected range [{}, {}]",
255 actual_min, actual_max, expected_min, expected_max
256 ),
257 );
258 }
259
260 let nan_count = data.iter().filter(|v| !v.is_finite()).count();
261 if nan_count > 0 {
262 result.add_warning(
263 WarningCategory::BestPractices,
264 format!("Found {} non-finite values", nan_count),
265 );
266 }
267
268 result.add_info(format!("Data range: [{}, {}]", actual_min, actual_max));
269 result.add_info(format!("Data count: {}", data.len()));
270
271 result
272 }
273
274 pub fn validate_file_path(path: &Path) -> ValidationResult {
276 let mut result = ValidationResult::new();
277
278 if !path.exists() {
279 result.add_error(ErrorCategory::Format, "File does not exist");
280 return result;
281 }
282
283 if !path.is_file() {
284 result.add_error(ErrorCategory::Format, "Path is not a file");
285 return result;
286 }
287
288 if let Ok(metadata) = path.metadata() {
289 let size = metadata.len();
290 result.add_info(format!("File size: {} bytes", size));
291
292 if size == 0 {
293 result.add_error(ErrorCategory::Format, "File is empty");
294 }
295
296 if size > 1_000_000_000 {
297 result.add_warning(
298 WarningCategory::Performance,
299 format!("Large file size: {:.2} GB", size as f64 / 1e9),
300 );
301 }
302 }
303
304 result
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_validation_result_creation() {
314 let result = ValidationResult::new();
315 assert!(result.passed);
316 assert!(result.errors.is_empty());
317 assert!(result.warnings.is_empty());
318 }
319
320 #[test]
321 fn test_validation_error() {
322 let mut result = ValidationResult::new();
323 result.add_error(ErrorCategory::Format, "test error");
324
325 assert!(!result.passed);
326 assert_eq!(result.errors.len(), 1);
327 }
328
329 #[test]
330 fn test_validate_raster_dimensions() {
331 let result = DataValidator::validate_raster_dimensions(100, 100, 3);
332 assert!(result.passed);
333
334 let result = DataValidator::validate_raster_dimensions(0, 100, 3);
335 assert!(!result.passed);
336 }
337
338 #[test]
339 fn test_validate_bounds() {
340 let result = DataValidator::validate_bounds(0.0, 0.0, 100.0, 100.0);
341 assert!(result.passed);
342
343 let result = DataValidator::validate_bounds(100.0, 0.0, 0.0, 100.0);
344 assert!(!result.passed);
345 }
346
347 #[test]
348 fn test_validate_data_range() {
349 let data = vec![0.0, 50.0, 100.0];
350 let result = DataValidator::validate_data_range(&data, 0.0, 100.0);
351 assert!(result.passed);
352
353 let data = vec![0.0, 150.0, 100.0];
354 let result = DataValidator::validate_data_range(&data, 0.0, 100.0);
355 assert!(result.passed);
356 assert!(!result.warnings.is_empty());
357 }
358
359 #[test]
360 fn test_validate_empty_data() {
361 let data: Vec<f64> = vec![];
362 let result = DataValidator::validate_data_range(&data, 0.0, 100.0);
363 assert!(!result.passed);
364 }
365}