summaryrefslogtreecommitdiffstats
path: root/src/force.rs
blob: 40f0ffcac0fe77e6cd1249d3f1902536e852bdcf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! `force`: overriding checks that otherwise refuse. Conflicts are errors by default;
//! each check that can be overridden has a name, and `force` lists the names to
//! override, or is `true` for all of them (`false`, or nothing, for none).
//!
//! The same format everywhere: the `?force=` query parameter of the API and the
//! `--force` option. Names are camelCase, like the API's fields (`switchDelaySecs`),
//! on the command line too. An unknown name is an error, so a typo can't silently
//! override nothing.

/// The checks a `force` value overrides.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct Force {
    all: bool,
    names: Vec<&'static str>,
}

impl Force {
    /// Parses `values` (each `true`, `false`, or a comma-separated list of names),
    /// allowing only the names in `known`.
    pub(crate) fn parse<'a>(
        values: impl IntoIterator<Item = &'a str>,
        known: &[&'static str],
    ) -> Result<Self, String> {
        let mut force = Force::default();
        for value in values {
            for name in value.split(',').map(str::trim) {
                match name {
                    "" | "false" => {}
                    "true" => force.all = true,
                    name => match known.iter().find(|known| **known == name) {
                        Some(known) => force.names.push(known),
                        None => {
                            return Err(format!(
                                "unknown force {name:?}: use true, or a list of {}",
                                known.join(", ")
                            ));
                        }
                    },
                }
            }
        }
        Ok(force)
    }

    /// Whether the check `name` is overridden.
    pub(crate) fn allows(&self, name: &str) -> bool {
        self.all || self.names.contains(&name)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const KNOWN: &[&str] = &["switchDelaySecs", "supersede"];

    #[test]
    fn all_some_or_none() {
        let all = Force::parse(["true"], KNOWN).unwrap();
        assert!(all.allows("supersede") && all.allows("switchDelaySecs"));

        let some = Force::parse(["supersede"], KNOWN).unwrap();
        assert!(some.allows("supersede") && !some.allows("switchDelaySecs"));
        let list = Force::parse(["switchDelaySecs, supersede"], KNOWN).unwrap();
        assert!(list.allows("supersede") && list.allows("switchDelaySecs"));

        for none in [vec![], vec!["false"], vec![""]] {
            assert!(!Force::parse(none, KNOWN).unwrap().allows("supersede"));
        }
        assert!(Force::parse(["swarm-id"], KNOWN).is_err());
    }
}