use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum WorkspaceMode {
#[default]
Safe,
Guided,
Autonomous,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum EscapePolicy {
#[default]
Ask,
Deny,
Allow,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub(crate) struct AutoAllowPaths {
#[serde(default)]
pub(crate) read: Vec<PathBuf>,
#[serde(default)]
pub(crate) write: Vec<PathBuf>,
#[serde(default)]
pub(crate) patterns: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct WorkspaceConfig {
pub(crate) root: PathBuf,
#[serde(default)]
pub(crate) mode: WorkspaceMode,
#[serde(default)]
pub(crate) escape_policy: EscapePolicy,
#[serde(default)]
pub(crate) auto_allow: AutoAllowPaths,
#[serde(default)]
pub(crate) never_allow: Vec<PathBuf>,
#[serde(default)]
pub(crate) allow_create_outside: bool,
#[serde(default)]
pub(crate) allow_delete_outside: bool,
}
impl WorkspaceConfig {
#[must_use]
pub(crate) fn new(root: impl Into<PathBuf>) -> Self {
Self {
root: root.into(),
mode: WorkspaceMode::Safe,
escape_policy: EscapePolicy::Ask,
auto_allow: AutoAllowPaths::default(),
never_allow: vec![
PathBuf::from("/etc"),
PathBuf::from("/var"),
PathBuf::from("/usr"),
PathBuf::from("/bin"),
PathBuf::from("/sbin"),
PathBuf::from("/boot"),
PathBuf::from("/root"),
],
allow_create_outside: false,
allow_delete_outside: false,
}
}
#[must_use]
pub(crate) fn with_mode(mut self, mode: WorkspaceMode) -> Self {
self.mode = mode;
self
}
#[must_use]
pub(crate) fn with_escape_policy(mut self, policy: EscapePolicy) -> Self {
self.escape_policy = policy;
self
}
#[must_use]
pub(crate) fn allow_read(mut self, path: impl Into<PathBuf>) -> Self {
self.auto_allow.read.push(path.into());
self
}
#[must_use]
pub(crate) fn allow_write(mut self, path: impl Into<PathBuf>) -> Self {
self.auto_allow.write.push(path.into());
self
}
#[must_use]
pub(crate) fn never_allow(mut self, path: impl Into<PathBuf>) -> Self {
self.never_allow.push(path.into());
self
}
#[must_use]
pub(crate) fn is_in_workspace(&self, path: &std::path::Path) -> bool {
path.starts_with(&self.root)
}
}
impl Default for WorkspaceConfig {
fn default() -> Self {
Self::new(".")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workspace_config_creation() {
let config = WorkspaceConfig::new("/home/user/project");
assert_eq!(config.root, PathBuf::from("/home/user/project"));
assert_eq!(config.mode, WorkspaceMode::Safe);
}
#[test]
fn test_workspace_mode() {
let config = WorkspaceConfig::new("/test").with_mode(WorkspaceMode::Autonomous);
assert_eq!(config.mode, WorkspaceMode::Autonomous);
}
#[test]
fn test_is_in_workspace() {
let config = WorkspaceConfig::new("/home/user/project");
assert!(config.is_in_workspace(std::path::Path::new("/home/user/project/src/main.rs")));
assert!(!config.is_in_workspace(std::path::Path::new("/home/user/other")));
}
}