agtrace_engine/diagnostics/
validator.rs

1use std::collections::HashMap;
2
3#[derive(Debug)]
4pub struct DiagnoseResult {
5    pub provider_name: String,
6    pub total_files: usize,
7    pub successful: usize,
8    pub failures: HashMap<FailureType, Vec<FailureExample>>,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum FailureType {
13    MissingField(String),
14    TypeMismatch(String),
15    ParseError,
16}
17
18impl std::fmt::Display for FailureType {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            FailureType::MissingField(field) => write!(f, "missing_field ({})", field),
22            FailureType::TypeMismatch(field) => write!(f, "type_mismatch ({})", field),
23            FailureType::ParseError => write!(f, "parse_error"),
24        }
25    }
26}
27
28#[derive(Debug, Clone)]
29pub struct FailureExample {
30    pub path: String,
31    pub reason: String,
32}
33
34pub fn categorize_parse_error(error_msg: &str) -> (FailureType, String) {
35    if error_msg.contains("missing field") {
36        if let Some(field) = extract_field_name(error_msg) {
37            (
38                FailureType::MissingField(field.clone()),
39                format!("Missing required field: {}", field),
40            )
41        } else {
42            (FailureType::ParseError, error_msg.to_string())
43        }
44    } else if error_msg.contains("expected") || error_msg.contains("invalid type") {
45        if let Some(field) = extract_field_name(error_msg) {
46            (
47                FailureType::TypeMismatch(field.clone()),
48                format!("Type mismatch for field: {}", field),
49            )
50        } else {
51            (FailureType::ParseError, error_msg.to_string())
52        }
53    } else {
54        (FailureType::ParseError, error_msg.to_string())
55    }
56}
57
58fn extract_field_name(error_msg: &str) -> Option<String> {
59    if let Some(start) = error_msg.find("field `") {
60        let rest = &error_msg[start + 7..];
61        if let Some(end) = rest.find('`') {
62            return Some(rest[..end].to_string());
63        }
64    }
65    None
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_categorize_missing_field() {
74        let error = "missing field `source` at line 1 column 2";
75        let (failure_type, reason) = categorize_parse_error(error);
76        assert_eq!(
77            failure_type,
78            FailureType::MissingField("source".to_string())
79        );
80        assert_eq!(reason, "Missing required field: source");
81    }
82
83    #[test]
84    fn test_categorize_type_mismatch() {
85        let error = "invalid type for field `timestamp`: expected string, got number";
86        let (failure_type, reason) = categorize_parse_error(error);
87        assert_eq!(
88            failure_type,
89            FailureType::TypeMismatch("timestamp".to_string())
90        );
91        assert_eq!(reason, "Type mismatch for field: timestamp");
92    }
93
94    #[test]
95    fn test_categorize_generic_parse_error() {
96        let error = "unexpected character at line 1";
97        let (failure_type, _reason) = categorize_parse_error(error);
98        assert_eq!(failure_type, FailureType::ParseError);
99    }
100
101    #[test]
102    fn test_extract_field_name() {
103        assert_eq!(
104            extract_field_name("missing field `source`"),
105            Some("source".to_string())
106        );
107        assert_eq!(
108            extract_field_name("field `timestamp` has wrong type"),
109            Some("timestamp".to_string())
110        );
111        assert_eq!(extract_field_name("no field here"), None);
112    }
113}