#[cfg(feature = "opencode")]
use crate::config::overrides::opencode::OpenCodeSettings;
#[cfg(feature = "zed")]
use crate::config::overrides::zed::ZedSettings;
use crate::{
SandboxType,
config::{
Override, OverrideFile, OverrideHomeDir, SandboxConfig,
overrides::ExternalTool as ExternalToolOverride,
},
error::{Result, with_io_context},
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, ffi::OsStr, fmt::Debug, path::PathBuf};
#[derive(Debug, Deserialize, Serialize)]
pub struct ConfigFile {
pub default_sandbox: Option<Sandbox>,
pub sandboxes: HashMap<String, Sandbox>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Sandbox {
#[serde(rename = "type")]
pub sandbox_type: SandboxType,
pub working_dir: Option<PathBuf>,
pub allow_network_access: Option<bool>,
pub writable_dirs: Option<Vec<PathBuf>>,
pub override_home_dirs: Option<Vec<OverrideHomeDirDescription>>,
pub passthrough_home_dirs: Option<Vec<PathBuf>>,
pub default_command: Option<DefaultCommand>,
}
impl Sandbox {
pub fn exec<'a, S>(
self,
process: &OsStr,
args: impl Iterator<Item = &'a S>,
dry_run: bool,
) -> Result<()>
where
S: AsRef<OsStr> + 'a,
{
let mut config = SandboxConfig {
allow_network_access: self.allow_network_access.unwrap_or_default(),
writable_dirs: self.writable_dirs,
override_home_dirs: self.override_home_dirs.map(|overrides| {
overrides
.into_iter()
.map(OverrideHomeDirDescription::into_override_home_dir)
.collect()
}),
passthrough_home_dirs: self.passthrough_home_dirs,
};
if let Some(working_dir) = self.working_dir {
std::env::set_current_dir(&working_dir).map_err(|e| {
with_io_context(process.display(), "setting working directory in sandbox", e)
})?;
}
let current_dir = std::env::current_dir().map_err(|e| {
with_io_context(
process.display(),
"getting current directory for process in sandbox",
e,
)
})?;
config
.writable_dirs
.get_or_insert_default()
.push(current_dir);
let sandbox = config.build_sandbox(self.sandbox_type);
let args = args.map(S::as_ref);
if dry_run {
sandbox.dry_run(process, args)?;
} else {
sandbox.exec(process, args)?;
}
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct OverrideHomeDirDescription {
pub subpath: PathBuf,
pub overrides: Option<Vec<OverrideDescription>>,
}
impl OverrideHomeDirDescription {
pub fn into_override_home_dir(self) -> OverrideHomeDir {
OverrideHomeDir {
subpath: self.subpath,
overrides: self.overrides.map(|overrides| {
overrides
.into_iter()
.map(OverrideDescription::into_override_file)
.collect()
}),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct OverrideDescription {
pub file: PathBuf,
#[serde(rename = "type")]
pub override_type: OverrideType,
}
impl OverrideDescription {
pub fn into_override_file(self) -> OverrideFile<Box<dyn Override>> {
OverrideFile {
path: self.file,
behavior: self.override_type.into_override(),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub enum OverrideType {
#[cfg(feature = "zed")]
Zed(ZedSettings),
#[cfg(feature = "opencode")]
OpenCode(OpenCodeSettings),
ExternalTool(ExternalToolOverride),
}
impl OverrideType {
fn into_override(self) -> Box<dyn Override> {
match self {
#[cfg(feature = "zed")]
Self::Zed(settings) => Box::new(settings),
#[cfg(feature = "opencode")]
Self::OpenCode(settings) => Box::new(settings),
Self::ExternalTool(tool) => Box::new(tool),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct DefaultCommand {
pub name: String,
pub args: Option<Vec<String>>,
}