raspberry_web/
validation.rs

1use crate::errors::RpWebError;
2use crate::settings::GpioConfig;
3//use std::collections::HashMap;
4
5/// Return a copy of the vec in Option(vec), or an empty vector for None
6pub fn vec_option_to_vec(option: &Option<Vec<i32>>) -> Vec<i32> {
7    match option {
8        Some(vec) => vec.to_vec(),
9        None => vec![],
10    }
11}
12
13/// Return Some(vec) of elements in both u and v, else None
14fn elements_in_both_vecs(u: &Vec<i32>, v: &Vec<i32>) -> Option<Vec<i32>> {
15    let mut res = vec![];
16
17    // Iterate and push duplicate values
18    for &idx in u.iter() {
19        if v.contains(&idx) {
20            res.push(idx);
21        }
22    }
23
24    // Return None if res is empty
25    if !res.is_empty() {
26        return Some(res);
27    } else {
28        return None;
29    }
30}
31
32pub fn validate_setup(gpioconfig: &GpioConfig) -> Result<(), RpWebError> {
33    // All GPIOs set to either 'high' or 'low' for 'gpio_level' must have 'in_use' = 1
34    // All GPIOs set to either 'high' or 'low' for 'gpio_level' must have 'gpio_mode' = 'output'
35    // GPIO's set to 'gpio_level' = 'low' must not be set to 'high' and vice versa
36    // GPIO's set to 'gpio_mode' = 'output' must not be set to 'input'
37
38    // It will be clearer to work with vecs than options
39    let gpios_in_use = vec_option_to_vec(&gpioconfig.gpios_in_use);
40    let gpios_mode_output = vec_option_to_vec(&gpioconfig.gpios_mode_output);
41    let gpios_mode_input = vec_option_to_vec(&gpioconfig.gpios_mode_input);
42    let gpios_level_low = vec_option_to_vec(&gpioconfig.gpios_level_low);
43    let gpios_level_high = vec_option_to_vec(&gpioconfig.gpios_level_high);
44
45    // Build vector of all gpios_level_{high / low}
46    let mut gpio_all_levels: Vec<i32> = vec![];
47    gpio_all_levels.append(&mut gpios_level_low.to_vec());
48    gpio_all_levels.append(&mut gpios_level_high.to_vec());
49
50    // 'gpios_in_use' must be present if levels are set
51    if gpios_in_use.is_empty() {
52        if !gpio_all_levels.is_empty() {
53            return Err(RpWebError::new(
54                "Invalid configuration: gpio_levels_* is set, but gpios_in_use is empty",
55            ));
56        }
57    }
58    // 'gpios_mode_output' be present if levels are set
59    if gpios_mode_output.is_empty() {
60        if !gpio_all_levels.is_empty() {
61            return Err(RpWebError::new(
62                "Invalid configuration: gpios_level_* is set, but gpios_mode_output is empty",
63            ));
64        }
65    }
66
67    // Find misconfigured gpios
68    for idx in gpio_all_levels.iter() {
69        // for all gpios set to level high or low, they must be in use
70        if !gpios_in_use.contains(idx) {
71            let errs = format!(
72                "Invalid configuration: GPIO #{} is not in_use, but a level is set for it",
73                idx
74            );
75            return Err(RpWebError::new(&errs));
76        }
77
78        // for all gpios set to level high or low, they must be set to mode output
79        if !gpios_mode_output.contains(idx) {
80            let errs = format!(
81                "Invalid configuration: GPIO #{} is not configured to OUTPUT, but a level is set for it", idx);
82            return Err(RpWebError::new(&errs));
83        }
84    }
85
86    // Find gpios in both level high and low, if any
87    let both_high_and_low_option = elements_in_both_vecs(&gpios_level_low, &gpios_level_high);
88    if let Some(high_and_low) = both_high_and_low_option {
89        let errs = format!(
90            "Invalid configuration: GPIO(s) {:?} in both gpios_level_low and gpios_level_high",
91            high_and_low
92        );
93        return Err(RpWebError::new(&errs));
94    }
95
96    // Find gpios in mode_input and mode_output, if any
97    let both_input_and_output_option = elements_in_both_vecs(&gpios_mode_input, &gpios_mode_output);
98    if let Some(input_and_output) = both_input_and_output_option {
99        let errs = format!(
100            "Invalid configuration: GPIO(s) {:?} in both gpios_mode_input and gpios_mode_output",
101            input_and_output
102        );
103        return Err(RpWebError::new(&errs));
104    }
105
106    Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn validation_in_use_none_but_level_set_must_fail() {
115        let gpioconfig = GpioConfig {
116            gpios_in_use: None,
117            gpios_mode_output: Some(vec![1]),
118            gpios_mode_input: None,
119            gpios_level_low: Some(vec![1]),
120            gpios_level_high: None,
121        };
122
123        let res = validate_setup(&gpioconfig);
124        assert!(res.is_err());
125    }
126
127    #[test]
128    fn validation_in_use_not_set_but_level_set_must_fail() {
129        let gpioconfig = GpioConfig {
130            gpios_in_use: Some(vec![2]),
131            gpios_mode_output: Some(vec![2]),
132            gpios_mode_input: None,
133            gpios_level_low: Some(vec![1]),
134            gpios_level_high: None,
135        };
136
137        let res = validate_setup(&gpioconfig);
138        assert!(res.is_err());
139    }
140
141    #[test]
142    fn validation_mode_output_none_but_level_set_must_fail() {
143        let gpioconfig = GpioConfig {
144            gpios_in_use: Some(vec![1]),
145            gpios_mode_output: None,
146            gpios_mode_input: None,
147            gpios_level_low: Some(vec![1]),
148            gpios_level_high: None,
149        };
150
151        let res = validate_setup(&gpioconfig);
152        assert!(res.is_err());
153    }
154
155    #[test]
156    fn validation_mode_output_not_set_but_level_set_must_fail() {
157        let gpioconfig = GpioConfig {
158            gpios_in_use: Some(vec![1]),
159            gpios_mode_output: Some(vec![2]),
160            gpios_mode_input: None,
161            gpios_level_low: Some(vec![1]),
162            gpios_level_high: None,
163        };
164
165        let res = validate_setup(&gpioconfig);
166        assert!(res.is_err());
167    }
168
169    #[test]
170    fn validation_in_use_not_set_for_pin_level_set_must_fail() {
171        let gpioconfig = GpioConfig {
172            gpios_in_use: Some(vec![1, 2]),
173            gpios_mode_output: Some(vec![1, 2, 3]),
174            gpios_mode_input: None,
175            gpios_level_low: Some(vec![3]),
176            gpios_level_high: None,
177        };
178
179        let res = validate_setup(&gpioconfig);
180        assert!(res.is_err());
181    }
182
183    #[test]
184    fn validation_mode_output_not_set_for_pin_level_high_set_must_fail() {
185        let gpioconfig = GpioConfig {
186            gpios_in_use: Some(vec![1, 2, 3]),
187            gpios_mode_output: Some(vec![1, 2]),
188            gpios_mode_input: None,
189            gpios_level_low: Some(vec![3]),
190            gpios_level_high: None,
191        };
192
193        let res = validate_setup(&gpioconfig);
194        assert!(res.is_err());
195    }
196
197    #[test]
198    fn validation_same_pin_high_and_low_must_fail() {
199        let gpioconfig = GpioConfig {
200            gpios_in_use: Some(vec![1, 2]),
201            gpios_mode_output: Some(vec![1, 2]),
202            gpios_mode_input: None,
203            gpios_level_low: Some(vec![1]),
204            gpios_level_high: Some(vec![1]),
205        };
206
207        let res = validate_setup(&gpioconfig);
208        assert!(res.is_err());
209    }
210
211    #[test]
212    fn validation_same_pin_input_and_output_must_fail() {
213        let gpioconfig = GpioConfig {
214            gpios_in_use: Some(vec![1]),
215            gpios_mode_output: Some(vec![1]),
216            gpios_mode_input: Some(vec![1]),
217            gpios_level_low: None,
218            gpios_level_high: None,
219        };
220
221        let res = validate_setup(&gpioconfig);
222        assert!(res.is_err());
223    }
224
225    #[test]
226    fn validation_valid_setup_must_succeed() {
227        let gpioconfig = GpioConfig {
228            gpios_in_use: Some(vec![1, 2, 3]),
229            gpios_mode_output: Some(vec![1, 2, 3]),
230            gpios_mode_input: None,
231            gpios_level_low: Some(vec![1]),
232            gpios_level_high: Some(vec![2, 3]),
233        };
234
235        let res = validate_setup(&gpioconfig);
236
237        assert!(res.is_ok());
238    }
239}