use thiserror::Error;
pub const CODEX_WS_APT_PACKAGES_ENV: &str = "CODEX_WS_APT_PACKAGES";
pub const CODEX_WS_SETUP_COMMANDS_ENV: &str = "CODEX_WS_SETUP_COMMANDS";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuntimeEnvironmentVariable {
name: &'static str,
value: String,
}
impl RuntimeEnvironmentVariable {
#[must_use]
pub fn new(name: &'static str, value: String) -> Self {
Self { name, value }
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub fn value(&self) -> &str {
&self.value
}
#[must_use]
pub fn docker_assignment(&self) -> String {
format!("{}={}", self.name, self.value)
}
}
pub fn validate_apt_packages(packages: Vec<String>) -> Result<Vec<String>, RuntimeSpecError> {
let mut validated_packages = Vec::with_capacity(packages.len());
for package in packages {
let package = package.trim().to_owned();
if package.is_empty() {
return Err(RuntimeSpecError::EmptyAptPackage);
}
if !is_valid_apt_package(&package) {
return Err(RuntimeSpecError::InvalidAptPackage { package });
}
validated_packages.push(package);
}
Ok(validated_packages)
}
pub fn validate_setup_commands(commands: Vec<String>) -> Result<Vec<String>, RuntimeSpecError> {
let mut validated_commands = Vec::with_capacity(commands.len());
for command in commands {
let command = command.trim().to_owned();
if command.is_empty() {
return Err(RuntimeSpecError::EmptySetupCommand);
}
validated_commands.push(command);
}
Ok(validated_commands)
}
fn is_valid_apt_package(package: &str) -> bool {
package.bytes().all(|byte| {
byte.is_ascii_alphanumeric()
|| matches!(byte, b'+' | b'-' | b'.' | b'_' | b':' | b'=' | b'~')
})
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum RuntimeSpecError {
#[error("runtime apt package cannot be empty")]
EmptyAptPackage,
#[error("invalid runtime apt package '{package}'")]
InvalidAptPackage {
package: String,
},
#[error("runtime setup command cannot be empty")]
EmptySetupCommand,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_apt_packages_accepts_common_package_syntax() {
let packages = validate_apt_packages(vec![
" python3 ".to_owned(),
"libssl-dev:amd64".to_owned(),
"nodejs=22.0.0-1nodesource1".to_owned(),
])
.expect("apt packages should validate");
assert_eq!(
packages,
vec![
"python3".to_owned(),
"libssl-dev:amd64".to_owned(),
"nodejs=22.0.0-1nodesource1".to_owned()
]
);
}
#[test]
fn validate_apt_packages_rejects_shell_metacharacters() {
let error = validate_apt_packages(vec!["python3;curl".to_owned()])
.expect_err("shell metacharacters should fail");
assert!(matches!(
error,
RuntimeSpecError::InvalidAptPackage { package } if package == "python3;curl"
));
}
#[test]
fn validate_setup_commands_rejects_empty_commands() {
let error =
validate_setup_commands(vec![" ".to_owned()]).expect_err("blank command should fail");
assert_eq!(error, RuntimeSpecError::EmptySetupCommand);
}
}