use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AppConfig {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub sandbox: SandboxConfig,
#[serde(default)]
pub secrets: SecretsConfig,
#[serde(default)]
pub deploy: DeployConfig,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SandboxConfig {
#[serde(default)]
pub kind: SandboxKind,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SandboxKind {
#[default]
None,
Local,
Bashkit,
Microvm,
}
impl SandboxKind {
pub fn as_str(&self) -> &'static str {
match self {
SandboxKind::None => "none",
SandboxKind::Local => "local",
SandboxKind::Bashkit => "bashkit",
SandboxKind::Microvm => "microvm",
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SecretsConfig {
#[serde(default)]
pub required: Vec<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DeployConfig {
#[serde(default)]
pub target: Option<String>,
}
impl AppConfig {
pub fn parse(text: &str) -> crate::Result<Self> {
Ok(toml::from_str(text)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_full_config() {
let config = AppConfig::parse(
r#"
name = "revenue-analyst"
[sandbox]
kind = "bashkit"
[secrets]
required = ["WAREHOUSE_URL"]
[deploy]
target = "everruns-cloud"
"#,
)
.unwrap();
assert_eq!(config.name.as_deref(), Some("revenue-analyst"));
assert_eq!(config.sandbox.kind, SandboxKind::Bashkit);
assert_eq!(config.secrets.required, vec!["WAREHOUSE_URL"]);
}
#[test]
fn empty_config_defaults_to_no_sandbox() {
let config = AppConfig::parse("").unwrap();
assert_eq!(config.sandbox.kind, SandboxKind::None);
}
#[test]
fn unknown_keys_are_rejected() {
assert!(AppConfig::parse("sandbx = 1").is_err());
}
}