summaryrefslogtreecommitdiffstats
path: root/src/force.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/force.rs')
-rw-r--r--src/force.rs72
1 files changed, 72 insertions, 0 deletions
diff --git a/src/force.rs b/src/force.rs
new file mode 100644
index 0000000..40f0ffc
--- /dev/null
+++ b/src/force.rs
@@ -0,0 +1,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());
+ }
+}