#![allow(dead_code)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SandboxLevel {
ReadOnly,
#[default]
WorkspaceWrite,
FullAccess,
}
impl SandboxLevel {
#[must_use]
pub const fn allows_write(&self) -> bool {
matches!(self, Self::WorkspaceWrite | Self::FullAccess)
}
#[must_use]
pub const fn allows_outside_workspace(&self) -> bool {
matches!(self, Self::FullAccess)
}
}
#[derive(Debug, Clone)]
pub struct SandboxPermissions {
pub level: SandboxLevel,
pub workspace_root: Option<String>,
}
impl Default for SandboxPermissions {
fn default() -> Self {
Self {
level: SandboxLevel::WorkspaceWrite,
workspace_root: None,
}
}
}
impl SandboxPermissions {
#[must_use]
pub const fn new(level: SandboxLevel) -> Self {
Self {
level,
workspace_root: None,
}
}
#[must_use]
pub fn with_workspace(mut self, root: String) -> Self {
self.workspace_root = Some(root);
self
}
#[must_use]
pub fn is_write_allowed(&self, path: &str) -> bool {
match self.level {
SandboxLevel::ReadOnly => false,
SandboxLevel::FullAccess => true,
SandboxLevel::WorkspaceWrite => self
.workspace_root
.as_ref()
.is_none_or(|root| path.starts_with(root)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_only_denies_writes() {
let perms = SandboxPermissions::new(SandboxLevel::ReadOnly);
assert!(!perms.is_write_allowed("/any/path"));
}
#[test]
fn full_access_allows_all() {
let perms = SandboxPermissions::new(SandboxLevel::FullAccess);
assert!(perms.is_write_allowed("/any/path"));
}
#[test]
fn workspace_write_respects_root() {
let perms = SandboxPermissions::new(SandboxLevel::WorkspaceWrite)
.with_workspace("/home/user/project".to_string());
assert!(perms.is_write_allowed("/home/user/project/src/main.rs"));
assert!(!perms.is_write_allowed("/etc/passwd"));
}
}