use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TinyAst {
ExecuteCommand {
command_name: String,
args: Vec<String>,
},
SetEnvironmentVariable {
name: String,
value: String,
},
Sequence {
commands: Vec<TinyAst>,
},
ChangeDirectory {
path: String,
},
}
pub const ALLOWED_COMMANDS: &[&str] = &[
"mkdir",
"echo",
"rm",
"cp",
"mv",
"chmod",
"chown",
"id",
"test",
"wget",
"curl",
"tar",
"gzip",
"gunzip",
"sha256sum",
"sha512sum",
];
impl TinyAst {
pub fn validate_command(command: &str) -> bool {
ALLOWED_COMMANDS.contains(&command)
}
pub fn validate_variable_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
let first_char = name.chars().next().unwrap();
if !first_char.is_ascii_alphabetic() && first_char != '_' {
return false;
}
name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub fn is_valid(&self) -> bool {
match self {
TinyAst::ExecuteCommand { command_name, .. } => Self::validate_command(command_name),
TinyAst::SetEnvironmentVariable { name, .. } => Self::validate_variable_name(name),
TinyAst::Sequence { commands } => {
!commands.is_empty() && commands.iter().all(|cmd| cmd.is_valid())
}
TinyAst::ChangeDirectory { path } => !path.is_empty() && !path.contains('\0'),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_command() {
assert!(TinyAst::validate_command("echo"));
assert!(TinyAst::validate_command("mkdir"));
assert!(!TinyAst::validate_command("sudo"));
assert!(!TinyAst::validate_command("eval"));
}
#[test]
fn test_validate_variable_name() {
assert!(TinyAst::validate_variable_name("PATH"));
assert!(TinyAst::validate_variable_name("_var"));
assert!(TinyAst::validate_variable_name("var123"));
assert!(!TinyAst::validate_variable_name("123var"));
assert!(!TinyAst::validate_variable_name("var-name"));
assert!(!TinyAst::validate_variable_name(""));
}
#[test]
fn test_ast_validation() {
let valid_echo = TinyAst::ExecuteCommand {
command_name: "echo".to_string(),
args: vec!["Hello".to_string()],
};
assert!(valid_echo.is_valid());
let invalid_command = TinyAst::ExecuteCommand {
command_name: "sudo".to_string(),
args: vec![],
};
assert!(!invalid_command.is_valid());
let valid_var = TinyAst::SetEnvironmentVariable {
name: "INSTALL_DIR".to_string(),
value: "/opt/rash".to_string(),
};
assert!(valid_var.is_valid());
let invalid_var = TinyAst::SetEnvironmentVariable {
name: "install-dir".to_string(),
value: "/opt/rash".to_string(),
};
assert!(!invalid_var.is_valid());
}
}