use crate::sandbox::grants::{EnvGrant, FsGrant, ProcGrant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum GrantReach {
#[default]
Declared,
Unrestricted,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct GrantSet {
reach: GrantReach,
fs: FsGrant,
env: EnvGrant,
proc: ProcGrant,
}
impl GrantSet {
#[must_use]
pub const fn declared() -> Self {
Self {
reach: GrantReach::Declared,
fs: FsGrant::none(),
env: EnvGrant::none(),
proc: ProcGrant::none(),
}
}
#[must_use]
pub const fn unrestricted() -> Self {
Self {
reach: GrantReach::Unrestricted,
fs: FsGrant::none(),
env: EnvGrant::none(),
proc: ProcGrant::none(),
}
}
#[must_use]
pub fn with_fs(mut self, build: impl FnOnce(FsGrant) -> FsGrant) -> Self {
self.fs = build(self.fs);
self
}
#[must_use]
pub fn with_env(mut self, build: impl FnOnce(EnvGrant) -> EnvGrant) -> Self {
self.env = build(self.env);
self
}
#[must_use]
pub fn with_proc(mut self, build: impl FnOnce(ProcGrant) -> ProcGrant) -> Self {
self.proc = build(self.proc);
self
}
#[must_use]
pub const fn fs(&self) -> &FsGrant {
&self.fs
}
#[must_use]
pub const fn env(&self) -> &EnvGrant {
&self.env
}
#[must_use]
pub const fn proc(&self) -> &ProcGrant {
&self.proc
}
#[must_use]
pub const fn is_unrestricted(&self) -> bool {
matches!(self.reach, GrantReach::Unrestricted)
}
#[must_use]
pub fn is_empty(&self) -> bool {
matches!(self.reach, GrantReach::Declared)
&& self.fs.is_empty()
&& self.env.is_empty()
&& self.proc.is_empty()
}
}
impl core::fmt::Display for GrantSet {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_unrestricted() {
return f.write_str("unrestricted");
}
if self.is_empty() {
return f.write_str("none");
}
let mut parts = Vec::new();
for root in self.fs.read_roots() {
parts.push(format!("read {}", root.display()));
}
for root in self.fs.write_roots() {
parts.push(format!("write {}", root.display()));
}
if !self.env.is_empty() {
parts.push(format!(
"env {}",
self.env.names().collect::<Vec<_>>().join(",")
));
}
if !self.proc.is_empty() {
parts.push(format!(
"exec {}",
self.proc.executables().collect::<Vec<_>>().join(",")
));
}
f.write_str(&parts.join("; "))
}
}
#[cfg(test)]
mod tests {
use super::GrantSet;
#[test]
fn declared_is_the_default_reach() {
assert_eq!(GrantSet::default(), GrantSet::declared());
}
#[test]
fn declared_grants_nothing_and_waives_nothing() {
let grants = GrantSet::declared();
assert!(grants.is_empty());
assert!(!grants.is_unrestricted());
}
#[test]
fn unrestricted_waives_containment() {
let grants = GrantSet::unrestricted();
assert!(grants.is_unrestricted());
assert!(!grants.is_empty());
}
#[test]
fn the_two_reaches_are_distinguishable() {
assert_ne!(GrantSet::declared(), GrantSet::unrestricted());
}
#[test]
fn each_reach_renders_for_a_report() {
assert_eq!(GrantSet::declared().to_string(), "none");
assert_eq!(GrantSet::unrestricted().to_string(), "unrestricted");
}
}