use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DependencyDelta {
pub name: String,
pub is_installed: bool,
pub installed_version: Option<String>,
pub version_satisfied: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SandboxInfo {
pub package_name: String,
pub depends: Vec<DependencyDelta>,
pub makedepends: Vec<DependencyDelta>,
pub checkdepends: Vec<DependencyDelta>,
pub optdepends: Vec<DependencyDelta>,
}
impl SandboxInfo {
#[must_use]
pub fn missing_packages(&self) -> Vec<&str> {
self.depends
.iter()
.chain(&self.makedepends)
.chain(&self.checkdepends)
.filter(|delta| !delta.is_installed)
.map(|delta| delta.name.as_str())
.collect()
}
#[must_use]
pub fn is_ready_to_build(&self) -> bool {
self.depends
.iter()
.chain(&self.makedepends)
.chain(&self.checkdepends)
.all(|delta| delta.is_installed)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SandboxRuleId {
#[serde(rename = "SB001")]
CommandSubstitution,
#[serde(rename = "SB002")]
RemoteDownload,
#[serde(rename = "SB003")]
PrivilegedCommand,
#[serde(rename = "SB004")]
DestructiveRemoval,
#[serde(rename = "SB005")]
DynamicEvaluation,
}
impl SandboxRuleId {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::CommandSubstitution => "SB001",
Self::RemoteDownload => "SB002",
Self::PrivilegedCommand => "SB003",
Self::DestructiveRemoval => "SB004",
Self::DynamicEvaluation => "SB005",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SandboxFinding {
pub rule_id: SandboxRuleId,
pub line: usize,
pub evidence: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SandboxAnalysisLimitation {
#[serde(rename = "text-only-no-execution")]
TextOnlyNoExecution,
#[serde(rename = "not-a-full-shell-parser")]
NotFullShellParser,
#[serde(rename = "no-external-reputation-or-scanner")]
NoExternalReputationOrScanner,
#[serde(rename = "not-proof-of-malicious-intent")]
NotProofOfMaliciousIntent,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SandboxStaticAnalysis {
pub package_name: String,
pub findings: Vec<SandboxFinding>,
pub limitations: Vec<SandboxAnalysisLimitation>,
}
#[cfg(test)]
mod tests {
use super::*;
fn delta(name: &str, installed: bool) -> DependencyDelta {
DependencyDelta {
name: name.to_string(),
is_installed: installed,
installed_version: installed.then(|| "1.0".to_string()),
version_satisfied: installed,
}
}
#[test]
fn missing_packages_excludes_optdepends() {
let info = SandboxInfo {
package_name: "pkg".to_string(),
depends: vec![delta("a", true), delta("b", false)],
makedepends: vec![delta("c", false)],
checkdepends: vec![delta("d", true)],
optdepends: vec![delta("e", false)],
};
assert_eq!(info.missing_packages(), ["b", "c"]);
}
#[test]
fn readiness() {
let ready = SandboxInfo {
package_name: "pkg".to_string(),
depends: vec![delta("a", true)],
makedepends: vec![delta("b", true)],
checkdepends: vec![],
optdepends: vec![delta("c", false)],
};
assert!(ready.is_ready_to_build());
let not_ready = SandboxInfo {
makedepends: vec![delta("b", false)],
..ready
};
assert!(!not_ready.is_ready_to_build());
}
#[test]
fn serde_and_default() {
let info = SandboxInfo {
package_name: "pkg".to_string(),
depends: vec![delta("glibc>=2.38", true)],
..Default::default()
};
let back: SandboxInfo =
serde_json::from_str(&serde_json::to_string(&info).expect("serialize"))
.expect("deserialize");
assert_eq!(back, info);
let empty = SandboxInfo::default();
assert!(empty.missing_packages().is_empty());
assert!(empty.is_ready_to_build());
}
}