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 ManifestRawConfig {
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 acl: Option<toml::Value>,
38
39 #[serde(default)]
41 pub scripts: HashMap<String, String>,
42
43 #[serde(default)]
45 pub binary: Option<RawBinaryConfig>,
46
47 #[serde(default)]
49 pub build: Option<RawBuildConfig>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct RawPackageConfig {
54 pub name: String,
56
57 pub manufacturer: String,
59
60 #[serde(default)]
62 pub version: String,
63
64 #[serde(default)]
65 pub description: Option<String>,
66
67 #[serde(default)]
68 pub authors: Option<Vec<String>>,
69
70 #[serde(default)]
71 pub license: Option<String>,
72
73 #[serde(default)]
75 pub tags: Vec<String>,
76
77 #[serde(default)]
79 pub signature_algorithm: Option<String>,
80
81 #[serde(default)]
83 pub exports: Vec<PathBuf>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct RawBinaryConfig {
88 pub path: PathBuf,
89
90 #[serde(default)]
91 pub target: Option<String>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, Default)]
95pub struct RawBuildConfig {
96 #[serde(default)]
97 pub tool: Option<String>,
98
99 #[serde(default)]
100 pub manifest_path: Option<PathBuf>,
101
102 #[serde(default)]
103 pub artifact: Option<String>,
104
105 #[serde(default)]
106 pub target: Option<String>,
107
108 #[serde(default)]
109 pub profile: Option<String>,
110
111 #[serde(default)]
112 pub features: Vec<String>,
113
114 #[serde(default)]
115 pub no_default_features: bool,
116
117 #[serde(default)]
118 pub post_build: Vec<String>,
119}
120
121impl RawPackageConfig {
122 pub fn into_package_info(self) -> Result<crate::config::PackageInfo> {
123 Ok(crate::config::PackageInfo {
124 name: self.name.clone(),
125 actr_type: actr_protocol::ActrType {
126 manufacturer: self.manufacturer.clone(),
127 name: self.name,
128 version: self.version,
129 },
130 description: self.description,
131 authors: self.authors.unwrap_or_default(),
132 license: self.license,
133 })
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(untagged)]
139pub enum RawDependency {
140 Specified {
148 #[serde(rename = "actr_type")]
150 actr_type: String,
151
152 #[serde(default)]
155 service: Option<String>,
156
157 #[serde(default)]
159 realm: Option<u32>,
160 },
161
162 Empty {},
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, Default)]
167pub struct RawSystemConfig {
168 #[serde(default)]
169 pub signaling: RawSignalingConfig,
170
171 #[serde(default)]
172 pub ais_endpoint: RawAisEndpointConfig,
173
174 #[serde(default)]
175 pub deployment: RawDeploymentConfig,
176
177 #[serde(default)]
178 pub discovery: RawDiscoveryConfig,
179
180 #[serde(default)]
181 pub storage: RawStorageConfig,
182
183 #[serde(default)]
184 pub webrtc: RawWebRtcConfig,
185 #[serde(default)]
186 pub websocket: RawWebSocketConfig,
187 #[serde(default)]
188 pub observability: RawObservabilityConfig,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, Default)]
200pub struct RawWebSocketConfig {
201 #[serde(default)]
207 pub listen_port: Option<u16>,
208
209 #[serde(default)]
216 pub advertised_host: Option<String>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize, Default)]
220pub struct RawSignalingConfig {
221 #[serde(default)]
222 pub url: Option<String>,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, Default)]
226pub struct RawAisEndpointConfig {
227 #[serde(default)]
228 pub url: Option<String>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, Default)]
232pub struct RawDeploymentConfig {
233 #[serde(default)]
234 pub realm_id: Option<u32>,
235
236 #[serde(default)]
238 pub realm_secret: Option<String>,
239
240 #[serde(default)]
242 pub ais_endpoint: Option<String>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize, Default)]
246pub struct RawDiscoveryConfig {
247 #[serde(default)]
248 pub visible: Option<bool>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize, Default)]
252#[serde(deny_unknown_fields)]
253pub struct RawStorageConfig {
254 #[serde(default)]
255 pub mailbox_path: Option<PathBuf>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, Default)]
263pub struct RawWebRtcConfig {
264 #[serde(default)]
266 pub stun_urls: Vec<String>,
267
268 #[serde(default)]
270 pub turn_urls: Vec<String>,
271
272 #[serde(default)]
274 pub force_relay: bool,
275
276 #[serde(default)]
278 pub ice_host_acceptance_min_wait: Option<u64>,
279
280 #[serde(default)]
282 pub ice_srflx_acceptance_min_wait: Option<u64>,
283
284 #[serde(default)]
286 pub ice_prflx_acceptance_min_wait: Option<u64>,
287
288 #[serde(default)]
290 pub ice_relay_acceptance_min_wait: Option<u64>,
291
292 #[serde(default)]
294 pub port_range_start: Option<u16>,
295
296 #[serde(default)]
298 pub port_range_end: Option<u16>,
299
300 #[serde(default)]
302 pub public_ips: Vec<String>,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize, Default)]
306pub struct RawObservabilityConfig {
307 #[serde(default)]
310 pub filter_level: Option<String>,
311
312 #[serde(default)]
313 pub tracing_enabled: Option<bool>,
314
315 #[serde(default)]
317 pub tracing_endpoint: Option<String>,
318
319 #[serde(default)]
321 pub tracing_service_name: Option<String>,
322}
323
324fn default_edition() -> u32 {
325 1
326}
327
328impl ManifestRawConfig {
329 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
331 let content = std::fs::read_to_string(path)?;
332 content.parse()
333 }
334
335 pub fn save_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
337 let content = toml::to_string_pretty(self)?;
338 std::fs::write(path, content)?;
339 Ok(())
340 }
341}
342
343impl FromStr for ManifestRawConfig {
344 type Err = crate::error::ConfigError;
345
346 fn from_str(s: &str) -> Result<Self> {
347 toml::from_str(s).map_err(Into::into)
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_parse_basic_config() {
357 let toml_content = r#"
358edition = 1
359exports = ["proto/test.proto"]
360
361[package]
362name = "test-service"
363manufacturer = "acme"
364
365[dependencies]
366user-service = {}
367
368[scripts]
369run = "cargo run"
370"#;
371
372 let config = ManifestRawConfig::from_str(toml_content).unwrap();
373 assert_eq!(config.edition, 1);
374 assert_eq!(config.package.name, "test-service");
375 assert_eq!(config.exports.len(), 1);
376 assert!(config.dependencies.contains_key("user-service"));
377 }
378
379 #[test]
380 fn test_parse_dependency_with_empty_attributes() {
381 let toml_content = r#"
382[package]
383name = "test"
384manufacturer = "acme"
385[dependencies]
386user-service = {}
387"#;
388 let config = ManifestRawConfig::from_str(toml_content).unwrap();
389 let dep = config.dependencies.get("user-service").unwrap();
390 assert!(matches!(dep, RawDependency::Empty {}));
391 }
392
393 #[test]
394 fn test_parse_dependency_specified() {
395 let toml_content = r#"
396[package]
397name = "test"
398manufacturer = "acme"
399[dependencies]
400shared = { actr_type = "acme:logging-service:1.0.0", service = "LoggingService:abc123", realm = 9999 }
401"#;
402 let config = ManifestRawConfig::from_str(toml_content).unwrap();
403 let dep = config.dependencies.get("shared").unwrap();
404 if let RawDependency::Specified {
405 actr_type,
406 service,
407 realm,
408 } = dep
409 {
410 assert_eq!(actr_type, "acme:logging-service:1.0.0");
411 assert_eq!(service.as_deref(), Some("LoggingService:abc123"));
412 assert_eq!(*realm, Some(9999));
413 } else {
414 panic!("Expected Specified");
415 }
416 }
417
418 #[test]
419 fn test_parse_dependency_specified_no_service() {
420 let toml_content = r#"
421[package]
422name = "test"
423manufacturer = "acme"
424[dependencies]
425shared = { actr_type = "acme:logging-service:1.0.0" }
426"#;
427 let config = ManifestRawConfig::from_str(toml_content).unwrap();
428 let dep = config.dependencies.get("shared").unwrap();
429 if let RawDependency::Specified { service, .. } = dep {
430 assert!(service.is_none());
431 } else {
432 panic!("Expected Specified");
433 }
434 }
435}