sandbox_runtime/config/
loader.rs1use std::path::{Path, PathBuf};
4
5use crate::config::schema::SandboxRuntimeConfig;
6use crate::error::{ConfigError, SandboxError};
7
8const DEFAULT_SETTINGS_FILE: &str = ".srt-settings.json";
10
11pub fn default_settings_path() -> Option<PathBuf> {
13 dirs::home_dir().map(|home| home.join(DEFAULT_SETTINGS_FILE))
14}
15
16pub 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
29pub 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
37pub 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 config.validate()?;
45
46 Ok(config)
47}
48
49pub fn load_config_from_string(content: &str) -> Option<SandboxRuntimeConfig> {
53 let trimmed = content.trim();
54 if trimmed.is_empty() {
55 return None;
56 }
57
58 match parse_config(trimmed) {
59 Ok(config) => Some(config),
60 Err(e) => {
61 tracing::debug!("Failed to parse config from string: {}", e);
62 None
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_parse_minimal_config() {
73 let json = r#"{}"#;
74 let config = parse_config(json).unwrap();
75 assert!(config.network.allowed_domains.is_empty());
76 assert!(config.filesystem.allow_write.is_empty());
77 }
78
79 #[test]
80 fn test_parse_full_config() {
81 let json = r#"{
82 "network": {
83 "allowedDomains": ["github.com", "*.npmjs.org"],
84 "deniedDomains": ["evil.com"],
85 "allowLocalBinding": true,
86 "mitmProxy": {
87 "socketPath": "/tmp/mitm.sock",
88 "domains": ["api.example.com"]
89 }
90 },
91 "filesystem": {
92 "denyRead": ["/etc/passwd"],
93 "allowWrite": ["/tmp"],
94 "denyWrite": ["/tmp/secret"],
95 "allowGitConfig": false
96 },
97 "mandatoryDenySearchDepth": 5,
98 "allowPty": true
99 }"#;
100
101 let config = parse_config(json).unwrap();
102 assert_eq!(config.network.allowed_domains.len(), 2);
103 assert_eq!(config.network.denied_domains.len(), 1);
104 assert_eq!(config.network.allow_local_binding, Some(true));
105 assert!(config.network.mitm_proxy.is_some());
106 assert_eq!(config.filesystem.deny_read.len(), 1);
107 assert_eq!(config.filesystem.allow_write.len(), 1);
108 assert_eq!(config.filesystem.deny_write.len(), 1);
109 assert_eq!(config.mandatory_deny_search_depth, Some(5));
110 assert_eq!(config.allow_pty, Some(true));
111 }
112
113 #[test]
114 fn test_invalid_domain_pattern() {
115 let json = r#"{
116 "network": {
117 "allowedDomains": ["*.com"]
118 }
119 }"#;
120
121 let result = parse_config(json);
122 assert!(result.is_err());
123 }
124
125 #[test]
126 fn test_load_config_from_string_valid() {
127 let json = r#"{"network": {"allowedDomains": ["github.com"]}}"#;
128 let config = load_config_from_string(json);
129 assert!(config.is_some());
130 let config = config.unwrap();
131 assert_eq!(config.network.allowed_domains.len(), 1);
132 assert_eq!(config.network.allowed_domains[0], "github.com");
133 }
134
135 #[test]
136 fn test_load_config_from_string_empty() {
137 assert!(load_config_from_string("").is_none());
138 assert!(load_config_from_string(" ").is_none());
139 assert!(load_config_from_string("\n\t").is_none());
140 }
141
142 #[test]
143 fn test_load_config_from_string_invalid_json() {
144 assert!(load_config_from_string("not json").is_none());
145 assert!(load_config_from_string("{invalid}").is_none());
146 assert!(load_config_from_string("{\"network\": }").is_none());
147 }
148
149 #[test]
150 fn test_load_config_from_string_with_whitespace() {
151 let json = r#" {"network": {"allowedDomains": ["example.com"]}} "#;
152 let config = load_config_from_string(json);
153 assert!(config.is_some());
154 assert_eq!(config.unwrap().network.allowed_domains[0], "example.com");
155 }
156}