use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub general: GeneralConfig,
pub vm: VmConfig,
pub container: ContainerConfig,
pub credentials: CredentialsConfig,
pub session: SessionConfig,
pub cache: CacheConfig,
pub home: HomeConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct GeneralConfig {
pub verbose: bool,
pub log_format: String,
pub audit_log: bool,
pub update_check: bool,
}
impl Default for GeneralConfig {
fn default() -> Self {
Self {
verbose: false,
log_format: "text".to_string(),
audit_log: true,
update_check: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct VmConfig {
pub name: String,
pub distro: String,
}
impl Default for VmConfig {
fn default() -> Self {
Self {
name: "mino".to_string(),
distro: "fedora".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ContainerConfig {
pub image: String,
pub env: HashMap<String, String>,
pub volumes: Vec<String>,
pub network: String,
pub workdir: String,
#[serde(default)]
pub network_allow: Vec<String>,
#[serde(default)]
pub network_preset: Option<String>,
#[serde(default)]
pub layers: Vec<String>,
#[serde(default)]
pub read_only: bool,
}
impl Default for ContainerConfig {
fn default() -> Self {
Self {
image: "fedora:43".to_string(),
env: HashMap::new(),
volumes: vec![],
network: "bridge".to_string(),
workdir: "/workspace".to_string(),
network_allow: vec![],
network_preset: None,
layers: vec![],
read_only: false,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct CredentialsConfig {
pub aws: AwsConfig,
pub gcp: GcpConfig,
pub azure: AzureConfig,
pub github: GithubConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct AwsConfig {
pub enabled: bool,
pub session_duration_secs: u32,
pub role_arn: Option<String>,
pub external_id: Option<String>,
pub profile: Option<String>,
pub region: Option<String>,
}
impl Default for AwsConfig {
fn default() -> Self {
Self {
enabled: false,
session_duration_secs: 3600,
role_arn: None,
external_id: None,
profile: None,
region: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct GcpConfig {
pub enabled: bool,
pub project: Option<String>,
pub service_account: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct AzureConfig {
pub enabled: bool,
pub subscription: Option<String>,
pub tenant: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct GithubConfig {
pub host: String,
}
impl Default for GithubConfig {
fn default() -> Self {
Self {
host: "github.com".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct SessionConfig {
pub shell: String,
pub auto_cleanup_hours: u32,
}
impl Default for SessionConfig {
fn default() -> Self {
Self {
shell: "/bin/bash".to_string(),
auto_cleanup_hours: 720,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct HomeConfig {
pub enabled: bool,
}
impl Default for HomeConfig {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct CacheConfig {
pub enabled: bool,
pub gc_days: u32,
pub max_total_gb: u32,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
enabled: true,
gc_days: 30,
max_total_gb: 50,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_serializes() {
let config = Config::default();
let toml = toml::to_string_pretty(&config).unwrap();
assert!(toml.contains("[general]"));
assert!(toml.contains("[vm]"));
}
#[test]
fn config_deserializes_empty() {
let config: Config = toml::from_str("").unwrap();
assert_eq!(config.vm.name, "mino");
}
#[test]
fn config_deserializes_read_only() {
let toml = r#"
[container]
read_only = true
"#;
let config: Config = toml::from_str(toml).unwrap();
assert!(config.container.read_only);
}
#[test]
fn config_read_only_defaults_false() {
let config: Config = toml::from_str("").unwrap();
assert!(!config.container.read_only);
}
#[test]
fn config_deserializes_update_check() {
let toml = r#"
[general]
update_check = false
"#;
let config: Config = toml::from_str(toml).unwrap();
assert!(!config.general.update_check);
}
#[test]
fn config_update_check_defaults_true() {
let config: Config = toml::from_str("").unwrap();
assert!(config.general.update_check);
}
#[test]
fn config_home_enabled_defaults_true() {
let config: Config = toml::from_str("").unwrap();
assert!(config.home.enabled);
}
#[test]
fn config_deserializes_home_disabled() {
let toml = r#"
[home]
enabled = false
"#;
let config: Config = toml::from_str(toml).unwrap();
assert!(!config.home.enabled);
}
#[test]
fn config_deserializes_partial() {
let toml = r#"
[vm]
name = "custom-vm"
"#;
let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.vm.name, "custom-vm");
assert_eq!(config.container.image, "fedora:43"); }
}