sandbox_runtime/config/
loader.rs

1//! Configuration loader from ~/.srt-settings.json.
2
3use std::path::{Path, PathBuf};
4
5use crate::config::schema::SandboxRuntimeConfig;
6use crate::error::{ConfigError, SandboxError};
7
8/// Default settings file name.
9const DEFAULT_SETTINGS_FILE: &str = ".srt-settings.json";
10
11/// Get the default settings file path.
12pub fn default_settings_path() -> Option<PathBuf> {
13    dirs::home_dir().map(|home| home.join(DEFAULT_SETTINGS_FILE))
14}
15
16/// Load configuration from a file path.
17pub fn load_config(path: &Path) -> Result<SandboxRuntimeConfig, SandboxError> {
18    if !path.exists() {
19        return Err(ConfigError::FileNotFound(path.display().to_string()).into());
20    }
21
22    let content = std::fs::read_to_string(path).map_err(|e| {
23        ConfigError::ParseError(format!("Failed to read config file: {}", e))
24    })?;
25
26    parse_config(&content)
27}
28
29/// Load configuration from the default path, or return default config if not found.
30pub fn load_default_config() -> Result<SandboxRuntimeConfig, SandboxError> {
31    match default_settings_path() {
32        Some(path) if path.exists() => load_config(&path),
33        _ => Ok(SandboxRuntimeConfig::default()),
34    }
35}
36
37/// Parse configuration from a JSON string.
38pub fn parse_config(json: &str) -> Result<SandboxRuntimeConfig, SandboxError> {
39    let config: SandboxRuntimeConfig = serde_json::from_str(json).map_err(|e| {
40        ConfigError::ParseError(format!("Failed to parse config JSON: {}", e))
41    })?;
42
43    // Validate the configuration
44    config.validate()?;
45
46    Ok(config)
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_parse_minimal_config() {
55        let json = r#"{}"#;
56        let config = parse_config(json).unwrap();
57        assert!(config.network.allowed_domains.is_empty());
58        assert!(config.filesystem.allow_write.is_empty());
59    }
60
61    #[test]
62    fn test_parse_full_config() {
63        let json = r#"{
64            "network": {
65                "allowedDomains": ["github.com", "*.npmjs.org"],
66                "deniedDomains": ["evil.com"],
67                "allowLocalBinding": true,
68                "mitmProxy": {
69                    "socketPath": "/tmp/mitm.sock",
70                    "domains": ["api.example.com"]
71                }
72            },
73            "filesystem": {
74                "denyRead": ["/etc/passwd"],
75                "allowWrite": ["/tmp"],
76                "denyWrite": ["/tmp/secret"],
77                "allowGitConfig": false
78            },
79            "mandatoryDenySearchDepth": 5,
80            "allowPty": true
81        }"#;
82
83        let config = parse_config(json).unwrap();
84        assert_eq!(config.network.allowed_domains.len(), 2);
85        assert_eq!(config.network.denied_domains.len(), 1);
86        assert_eq!(config.network.allow_local_binding, Some(true));
87        assert!(config.network.mitm_proxy.is_some());
88        assert_eq!(config.filesystem.deny_read.len(), 1);
89        assert_eq!(config.filesystem.allow_write.len(), 1);
90        assert_eq!(config.filesystem.deny_write.len(), 1);
91        assert_eq!(config.mandatory_deny_search_depth, Some(5));
92        assert_eq!(config.allow_pty, Some(true));
93    }
94
95    #[test]
96    fn test_invalid_domain_pattern() {
97        let json = r#"{
98            "network": {
99                "allowedDomains": ["*.com"]
100            }
101        }"#;
102
103        let result = parse_config(json);
104        assert!(result.is_err());
105    }
106}