actr_config/
raw.rs

1//! Raw configuration structures - direct TOML mapping
2
3use crate::error::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::str::FromStr;
8
9/// Actr.toml 的直接映射(无任何处理)
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct RawConfig {
12    /// 配置文件格式版本(决定使用哪个 Parser)
13    #[serde(default = "default_edition")]
14    pub edition: u32,
15
16    /// 继承的父配置文件路径
17    #[serde(default)]
18    pub inherit: Option<PathBuf>,
19
20    /// Lock file 文件所在的目录
21    #[serde(default)]
22    pub config_dir: Option<PathBuf>,
23
24    /// 包信息
25    pub package: RawPackageConfig,
26
27    /// 导出的 proto 文件列表
28    #[serde(default)]
29    pub exports: Vec<PathBuf>,
30
31    /// 服务依赖
32    #[serde(default)]
33    pub dependencies: HashMap<String, RawDependency>,
34
35    /// 系统配置
36    #[serde(default)]
37    pub system: RawSystemConfig,
38
39    /// 访问控制列表(原始 TOML 值,稍后解析)
40    #[serde(default)]
41    pub acl: Option<toml::Value>,
42
43    /// 脚本命令
44    #[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    /// Service tags (e.g., ["latest", "stable", "v1.0"])
63    #[serde(default)]
64    pub tags: Vec<String>,
65}
66
67/// Actor type configuration under [package.actr_type]
68#[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    /// 带指纹的依赖配置(必须先匹配,因为它有 required 字段)
78    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    /// 空依赖声明:{}(由 actr install 填充)
92    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/// WebRTC 配置
140#[derive(Debug, Clone, Serialize, Deserialize, Default)]
141pub struct RawWebRtcConfig {
142    /// STUN 服务器 URL 列表 (例如 ["stun:localhost:3478"])
143    #[serde(default)]
144    pub stun_urls: Vec<String>,
145
146    /// TURN 服务器 URL 列表 (例如 ["turn:localhost:3478"])
147    #[serde(default)]
148    pub turn_urls: Vec<String>,
149
150    /// 是否强制使用 TURN 中继 (默认 false)
151    #[serde(default)]
152    pub force_relay: bool,
153}
154#[derive(Debug, Clone, Serialize, Deserialize, Default)]
155pub struct RawObservabilityConfig {
156    /// Filter level (e.g., "info", "debug", "warn", "info,webrtc=debug").
157    /// Used when RUST_LOG environment variable is not set. Default: "info".
158    #[serde(default)]
159    pub filter_level: Option<String>,
160
161    #[serde(default)]
162    pub tracing_enabled: Option<bool>,
163
164    /// OTLP/Jaeger gRPC endpoint. Default: http://localhost:4317
165    #[serde(default)]
166    pub tracing_endpoint: Option<String>,
167
168    /// Service name reported to the tracing backend. Default: package.name
169    #[serde(default)]
170    pub tracing_service_name: Option<String>,
171}
172
173fn default_edition() -> u32 {
174    1
175}
176
177impl RawConfig {
178    /// 从文件加载原始配置
179    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    /// 保存到文件
185    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}