1use crate::error::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::str::FromStr;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct RawConfig {
12 #[serde(default = "default_edition")]
14 pub edition: u32,
15
16 #[serde(default)]
18 pub inherit: Option<PathBuf>,
19
20 #[serde(default)]
22 pub config_dir: Option<PathBuf>,
23
24 pub package: RawPackageConfig,
26
27 #[serde(default)]
29 pub exports: Vec<PathBuf>,
30
31 #[serde(default)]
33 pub dependencies: HashMap<String, RawDependency>,
34
35 #[serde(default)]
37 pub system: RawSystemConfig,
38
39 #[serde(default)]
41 pub acl: Option<toml::Value>,
42
43 #[serde(default)]
45 pub scripts: HashMap<String, String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct RawPackageConfig {
50 pub name: String,
51 pub actr_type: RawActrType,
52
53 #[serde(default)]
54 pub description: Option<String>,
55
56 #[serde(default)]
57 pub authors: Option<Vec<String>>,
58
59 #[serde(default)]
60 pub license: Option<String>,
61
62 #[serde(default)]
64 pub tags: Vec<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct RawActrType {
70 pub manufacturer: String,
71 pub name: String,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(untagged)]
76pub enum RawDependency {
77 WithFingerprint {
79 #[serde(default)]
80 name: Option<String>,
81
82 #[serde(default)]
83 realm: Option<u32>,
84
85 #[serde(default)]
86 actr_type: Option<String>,
87
88 fingerprint: String,
89 },
90
91 Empty {},
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, Default)]
96pub struct RawSystemConfig {
97 #[serde(default)]
98 pub signaling: RawSignalingConfig,
99
100 #[serde(default)]
101 pub deployment: RawDeploymentConfig,
102
103 #[serde(default)]
104 pub discovery: RawDiscoveryConfig,
105
106 #[serde(default)]
107 pub storage: RawStorageConfig,
108
109 #[serde(default)]
110 pub webrtc: RawWebRtcConfig,
111 #[serde(default)]
112 pub observability: RawObservabilityConfig,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, Default)]
116pub struct RawSignalingConfig {
117 #[serde(default)]
118 pub url: Option<String>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, Default)]
122pub struct RawDeploymentConfig {
123 #[serde(default)]
124 pub realm_id: Option<u32>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, Default)]
128pub struct RawDiscoveryConfig {
129 #[serde(default)]
130 pub visible: Option<bool>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, Default)]
134pub struct RawStorageConfig {
135 #[serde(default)]
136 pub mailbox_path: Option<PathBuf>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, Default)]
141pub struct RawWebRtcConfig {
142 #[serde(default)]
144 pub stun_urls: Vec<String>,
145
146 #[serde(default)]
148 pub turn_urls: Vec<String>,
149
150 #[serde(default)]
152 pub force_relay: bool,
153}
154#[derive(Debug, Clone, Serialize, Deserialize, Default)]
155pub struct RawObservabilityConfig {
156 #[serde(default)]
159 pub filter_level: Option<String>,
160
161 #[serde(default)]
162 pub tracing_enabled: Option<bool>,
163
164 #[serde(default)]
166 pub tracing_endpoint: Option<String>,
167
168 #[serde(default)]
170 pub tracing_service_name: Option<String>,
171}
172
173fn default_edition() -> u32 {
174 1
175}
176
177impl RawConfig {
178 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
180 let content = std::fs::read_to_string(path)?;
181 content.parse()
182 }
183
184 pub fn save_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
186 let content = toml::to_string_pretty(self)?;
187 std::fs::write(path, content)?;
188 Ok(())
189 }
190}
191
192impl FromStr for RawConfig {
193 type Err = crate::error::ConfigError;
194
195 fn from_str(s: &str) -> Result<Self> {
196 toml::from_str(s).map_err(Into::into)
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_parse_basic_config() {
206 let toml_content = r#"
207edition = 1
208exports = ["proto/test.proto"]
209
210[package]
211name = "test-service"
212[package.actr_type]
213manufacturer = "acme"
214name = "test-service"
215
216[dependencies]
217user-service = {}
218
219[system.signaling]
220url = "ws://localhost:8081"
221
222[system.deployment]
223realm_id = 1001
224
225[scripts]
226run = "cargo run"
227"#;
228
229 let config = RawConfig::from_str(toml_content).unwrap();
230 assert_eq!(config.edition, 1);
231 assert_eq!(config.package.name, "test-service");
232 assert_eq!(config.exports.len(), 1);
233 assert!(config.dependencies.contains_key("user-service"));
234 }
235
236 #[test]
237 fn test_parse_dependency_with_empty_attributes() {
238 let toml_content = r#"
239[package]
240name = "test"
241[package.actr_type]
242manufacturer = "acme"
243name = "test"
244[dependencies]
245user-service = {}
246"#;
247 let config = RawConfig::from_str(toml_content).unwrap();
248 let dep = config.dependencies.get("user-service").unwrap();
249 assert!(matches!(dep, RawDependency::Empty {}));
250 }
251
252 #[test]
253 fn test_parse_dependency_with_name() {
254 let toml_content = r#"
255[package]
256name = "test"
257[package.actr_type]
258manufacturer = "acme"
259name = "test"
260[dependencies]
261shared = { name = "logging-service", fingerprint = "service_semantic:abc" }
262"#;
263 let config = RawConfig::from_str(toml_content).unwrap();
264 let dep = config.dependencies.get("shared").unwrap();
265 if let RawDependency::WithFingerprint {
266 name, fingerprint, ..
267 } = dep
268 {
269 assert_eq!(name.as_ref().unwrap(), "logging-service");
270 assert_eq!(fingerprint, "service_semantic:abc");
271 } else {
272 panic!("Expected WithFingerprint");
273 }
274 }
275
276 #[test]
277 fn test_parse_dependency_without_name() {
278 let toml_content = r#"
279[package]
280name = "test"
281[package.actr_type]
282manufacturer = "acme"
283name = "test"
284[dependencies]
285shared = { fingerprint = "service_semantic:abc" }
286"#;
287 let config = RawConfig::from_str(toml_content).unwrap();
288 let dep = config.dependencies.get("shared").unwrap();
289 if let RawDependency::WithFingerprint { name, .. } = dep {
290 assert!(name.is_none());
291 } else {
292 panic!("Expected WithFingerprint");
293 }
294 }
295}