dampen_cli/commands/check/
cross_widget.rs

1// Cross-widget validation for radio button groups
2use crate::commands::check::errors::CheckError;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub struct RadioButton {
8    pub value: String,
9    pub file: PathBuf,
10    pub line: u32,
11    pub col: u32,
12    pub handler: Option<String>,
13}
14
15#[derive(Debug)]
16pub struct RadioGroup {
17    pub id: String,
18    pub buttons: Vec<RadioButton>,
19}
20
21impl RadioGroup {
22    pub fn new(id: String) -> Self {
23        Self {
24            id,
25            buttons: Vec::new(),
26        }
27    }
28
29    pub fn add_button(&mut self, button: RadioButton) {
30        self.buttons.push(button);
31    }
32
33    /// Validate the radio group for duplicate values and inconsistent handlers
34    pub fn validate(&self) -> Vec<CheckError> {
35        let mut errors = Vec::new();
36
37        // Check for duplicate values
38        let mut seen_values: HashMap<String, &RadioButton> = HashMap::new();
39        for button in &self.buttons {
40            if let Some(first) = seen_values.get(&button.value) {
41                // Found duplicate
42                errors.push(CheckError::DuplicateRadioValue {
43                    value: button.value.clone(),
44                    group: self.id.clone(),
45                    file: button.file.clone(),
46                    line: button.line,
47                    col: button.col,
48                    first_file: first.file.clone(),
49                    first_line: first.line,
50                    first_col: first.col,
51                });
52            } else {
53                seen_values.insert(button.value.clone(), button);
54            }
55        }
56
57        // Check for inconsistent handlers
58        if self.buttons.len() > 1 {
59            let first_handler = &self.buttons[0].handler;
60            let mut handler_list = Vec::new();
61            let mut has_inconsistency = false;
62
63            for button in &self.buttons {
64                if let Some(ref h) = button.handler {
65                    if !handler_list.contains(h) {
66                        handler_list.push(h.clone());
67                    }
68                }
69                if &button.handler != first_handler {
70                    has_inconsistency = true;
71                }
72            }
73
74            if has_inconsistency {
75                // Report from the second button's location
76                let button = &self.buttons[1];
77                errors.push(CheckError::InconsistentRadioHandlers {
78                    group: self.id.clone(),
79                    file: button.file.clone(),
80                    line: button.line,
81                    col: button.col,
82                    handlers: handler_list.join(", "),
83                });
84            }
85        }
86
87        errors
88    }
89}
90
91#[derive(Debug, Default)]
92pub struct RadioGroupValidator {
93    groups: HashMap<String, RadioGroup>,
94}
95
96impl RadioGroupValidator {
97    pub fn new() -> Self {
98        Self::default()
99    }
100
101    /// Add a radio button to the validator
102    pub fn add_radio(
103        &mut self,
104        group_id: &str,
105        value: &str,
106        file: &str,
107        line: u32,
108        col: u32,
109        handler: Option<String>,
110    ) {
111        let button = RadioButton {
112            value: value.to_string(),
113            file: PathBuf::from(file),
114            line,
115            col,
116            handler,
117        };
118
119        self.groups
120            .entry(group_id.to_string())
121            .or_insert_with(|| RadioGroup::new(group_id.to_string()))
122            .add_button(button);
123    }
124
125    /// Validate all radio groups
126    pub fn validate(&self) -> Vec<CheckError> {
127        let mut errors = Vec::new();
128        for group in self.groups.values() {
129            errors.extend(group.validate());
130        }
131        errors
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_radio_group_with_unique_values() {
141        let mut group = RadioGroup::new("test".to_string());
142        group.add_button(RadioButton {
143            value: "opt1".to_string(),
144            file: PathBuf::from("test.dampen"),
145            line: 10,
146            col: 5,
147            handler: Some("handler".to_string()),
148        });
149        group.add_button(RadioButton {
150            value: "opt2".to_string(),
151            file: PathBuf::from("test.dampen"),
152            line: 15,
153            col: 5,
154            handler: Some("handler".to_string()),
155        });
156
157        let errors = group.validate();
158        assert_eq!(errors.len(), 0);
159    }
160
161    #[test]
162    fn test_radio_group_with_duplicate_values() {
163        let mut group = RadioGroup::new("test".to_string());
164        group.add_button(RadioButton {
165            value: "opt1".to_string(),
166            file: PathBuf::from("test.dampen"),
167            line: 10,
168            col: 5,
169            handler: Some("handler".to_string()),
170        });
171        group.add_button(RadioButton {
172            value: "opt1".to_string(),
173            file: PathBuf::from("test.dampen"),
174            line: 15,
175            col: 5,
176            handler: Some("handler".to_string()),
177        });
178
179        let errors = group.validate();
180        assert_eq!(errors.len(), 1);
181        match &errors[0] {
182            CheckError::DuplicateRadioValue { value, .. } => {
183                assert_eq!(value, "opt1");
184            }
185            _ => panic!("Expected DuplicateRadioValue error"),
186        }
187    }
188}