process_exec_capability.rs

  1use serde::{Deserialize, Serialize};
  2
  3#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
  4#[serde(rename_all = "snake_case")]
  5pub struct ProcessExecCapability {
  6    /// The command to execute.
  7    pub command: String,
  8    /// The arguments to pass to the command. Use `*` for a single wildcard argument.
  9    /// If the last element is `**`, then any trailing arguments are allowed.
 10    pub args: Vec<String>,
 11}
 12
 13impl ProcessExecCapability {
 14    /// Returns whether the capability allows the given command and arguments.
 15    pub fn allows(
 16        &self,
 17        desired_command: &str,
 18        desired_args: &[impl AsRef<str> + std::fmt::Debug],
 19    ) -> bool {
 20        if self.command != desired_command && self.command != "*" {
 21            return false;
 22        }
 23
 24        for (ix, arg) in self.args.iter().enumerate() {
 25            if arg == "**" {
 26                return true;
 27            }
 28
 29            if ix >= desired_args.len() {
 30                return false;
 31            }
 32
 33            if arg != "*" && arg != desired_args[ix].as_ref() {
 34                return false;
 35            }
 36        }
 37
 38        if self.args.len() < desired_args.len() {
 39            return false;
 40        }
 41
 42        true
 43    }
 44}
 45
 46#[cfg(test)]
 47mod tests {
 48    use pretty_assertions::assert_eq;
 49
 50    use super::*;
 51
 52    #[test]
 53    fn test_allows_with_exact_match() {
 54        let capability = ProcessExecCapability {
 55            command: "ls".to_string(),
 56            args: vec!["-la".to_string()],
 57        };
 58
 59        assert_eq!(capability.allows("ls", &["-la"]), true);
 60        assert_eq!(capability.allows("ls", &["-l"]), false);
 61        assert_eq!(capability.allows("pwd", &[] as &[&str]), false);
 62    }
 63
 64    #[test]
 65    fn test_allows_with_wildcard_arg() {
 66        let capability = ProcessExecCapability {
 67            command: "git".to_string(),
 68            args: vec!["*".to_string()],
 69        };
 70
 71        assert_eq!(capability.allows("git", &["status"]), true);
 72        assert_eq!(capability.allows("git", &["commit"]), true);
 73        // Too many args.
 74        assert_eq!(capability.allows("git", &["status", "-s"]), false);
 75        // Wrong command.
 76        assert_eq!(capability.allows("npm", &["install"]), false);
 77    }
 78
 79    #[test]
 80    fn test_allows_with_double_wildcard() {
 81        let capability = ProcessExecCapability {
 82            command: "cargo".to_string(),
 83            args: vec!["test".to_string(), "**".to_string()],
 84        };
 85
 86        assert_eq!(capability.allows("cargo", &["test"]), true);
 87        assert_eq!(capability.allows("cargo", &["test", "--all"]), true);
 88        assert_eq!(
 89            capability.allows("cargo", &["test", "--all", "--no-fail-fast"]),
 90            true
 91        );
 92        // Wrong first arg.
 93        assert_eq!(capability.allows("cargo", &["build"]), false);
 94    }
 95
 96    #[test]
 97    fn test_allows_with_mixed_wildcards() {
 98        let capability = ProcessExecCapability {
 99            command: "docker".to_string(),
100            args: vec!["run".to_string(), "*".to_string(), "**".to_string()],
101        };
102
103        assert_eq!(capability.allows("docker", &["run", "nginx"]), true);
104        assert_eq!(capability.allows("docker", &["run"]), false);
105        assert_eq!(
106            capability.allows("docker", &["run", "ubuntu", "bash"]),
107            true
108        );
109        assert_eq!(
110            capability.allows("docker", &["run", "alpine", "sh", "-c", "echo hello"]),
111            true
112        );
113        // Wrong first arg.
114        assert_eq!(capability.allows("docker", &["ps"]), false);
115    }
116}