actr_config/
config.rs

1//! Final configuration structures - fully parsed and validated
2
3use actr_protocol::{Acl, ActrType, Realm};
4use std::collections::HashMap;
5use std::path::PathBuf;
6use url::Url;
7
8/// 最终配置(已处理继承、默认值、验证、类型转换)
9/// 注意:没有 edition 字段,edition 只作用于解析阶段
10#[derive(Debug, Clone)]
11pub struct Config {
12    /// 包信息
13    pub package: PackageInfo,
14
15    /// 导出的 proto 文件(已读取内容)
16    pub exports: Vec<ProtoFile>,
17
18    /// 服务依赖(已展开)
19    pub dependencies: Vec<Dependency>,
20
21    /// 信令服务器 URL(已验证)
22    pub signaling_url: Url,
23
24    /// 所属 Realm (Security Realm)
25    pub realm: Realm,
26
27    /// 是否在服务发现中可见
28    pub visible_in_discovery: bool,
29
30    /// 访问控制列表
31    pub acl: Option<Acl>,
32
33    /// Mailbox 数据库路径
34    ///
35    /// - `Some(path)`: 使用持久化 SQLite 数据库
36    /// - `None`: 使用内存模式 (`:memory:`)
37    pub mailbox_path: Option<PathBuf>,
38
39    /// Service tags (e.g., "latest", "stable", "v1.0")
40    pub tags: Vec<String>,
41
42    /// 脚本命令
43    pub scripts: HashMap<String, String>,
44
45    /// WebRTC 配置
46    pub webrtc: WebRtcConfig,
47
48    /// Observability configuration (logging + tracing)
49    pub observability: ObservabilityConfig,
50}
51
52/// 包信息
53#[derive(Debug, Clone)]
54pub struct PackageInfo {
55    /// 包名
56    pub name: String,
57
58    /// Actor 类型
59    pub actr_type: ActrType,
60
61    /// 描述
62    pub description: Option<String>,
63
64    /// 作者列表
65    pub authors: Vec<String>,
66
67    /// 许可证
68    pub license: Option<String>,
69}
70
71/// 已解析的 proto 文件(文件级别)
72#[derive(Debug, Clone)]
73pub struct ProtoFile {
74    /// 文件路径(绝对路径)
75    pub path: PathBuf,
76
77    /// 文件内容
78    pub content: String,
79}
80
81/// 已展开的依赖
82#[derive(Debug, Clone)]
83pub struct Dependency {
84    /// 依赖别名(dependencies 中的 key)
85    pub alias: String,
86
87    /// 所属 Realm
88    pub realm: Realm,
89
90    /// Actor 类型
91    pub actr_type: ActrType,
92
93    /// 服务指纹
94    pub fingerprint: Option<String>,
95}
96
97/// ICE 传输策略
98#[derive(Clone, Debug, Default, PartialEq, Eq)]
99pub enum IceTransportPolicy {
100    /// 使用所有可用候选(默认)
101    #[default]
102    All,
103    /// 仅使用 TURN 中继候选
104    Relay,
105}
106
107/// ICE 服务器配置
108#[derive(Clone, Debug, Default)]
109pub struct IceServer {
110    /// 服务器 URL 列表
111    pub urls: Vec<String>,
112    /// 用户名(TURN 服务器需要)
113    pub username: Option<String>,
114    /// 凭证(TURN 服务器需要)
115    pub credential: Option<String>,
116}
117
118/// WebRTC 配置
119#[derive(Clone, Debug, Default)]
120pub struct WebRtcConfig {
121    /// ICE 服务器列表
122    pub ice_servers: Vec<IceServer>,
123    /// ICE 传输策略(All 或 Relay)
124    pub ice_transport_policy: IceTransportPolicy,
125}
126/// Observability configuration (logging + tracing) resolved from raw config
127#[derive(Debug, Clone)]
128pub struct ObservabilityConfig {
129    /// Filter level (e.g., "info", "debug", "warn", "info,webrtc=debug").
130    /// Used when RUST_LOG environment variable is not set. Default: "info".
131    pub filter_level: String,
132
133    /// Whether to enable distributed tracing
134    pub tracing_enabled: bool,
135
136    /// OTLP/Jaeger gRPC endpoint
137    pub tracing_endpoint: String,
138
139    /// Service name reported to the tracing backend
140    pub tracing_service_name: String,
141}
142
143// ============================================================================
144// Config 辅助方法
145// ============================================================================
146
147impl Config {
148    /// 获取包的 ActrType(用于注册)
149    pub fn actr_type(&self) -> &ActrType {
150        &self.package.actr_type
151    }
152
153    /// 获取所有 proto 文件路径
154    pub fn proto_paths(&self) -> Vec<&PathBuf> {
155        self.exports.iter().map(|p| &p.path).collect()
156    }
157
158    /// 获取所有 proto 内容(用于计算服务指纹)
159    pub fn proto_contents(&self) -> Vec<&str> {
160        self.exports.iter().map(|p| p.content.as_str()).collect()
161    }
162
163    /// 根据别名查找依赖
164    pub fn get_dependency(&self, alias: &str) -> Option<&Dependency> {
165        self.dependencies.iter().find(|d| d.alias == alias)
166    }
167
168    /// 根据 ActrType 查找所有匹配的依赖
169    pub fn find_dependencies_by_type(&self, actr_type: &ActrType) -> Vec<&Dependency> {
170        self.dependencies
171            .iter()
172            .filter(|d| &d.actr_type == actr_type)
173            .collect()
174    }
175
176    /// 获取所有跨 Realm 的依赖
177    pub fn cross_realm_dependencies(&self) -> Vec<&Dependency> {
178        self.dependencies
179            .iter()
180            .filter(|d| d.realm.realm_id != self.realm.realm_id)
181            .collect()
182    }
183
184    /// 获取脚本命令
185    pub fn get_script(&self, name: &str) -> Option<&str> {
186        self.scripts.get(name).map(|s| s.as_str())
187    }
188
189    /// 列出所有脚本名称
190    pub fn list_scripts(&self) -> Vec<&str> {
191        self.scripts.keys().map(|s| s.as_str()).collect()
192    }
193
194    /// Calculate ServiceSpec from config
195    ///
196    /// Returns None if no proto files are exported
197    pub fn calculate_service_spec(&self) -> Option<actr_protocol::ServiceSpec> {
198        // If no exports, no ServiceSpec
199        if self.exports.is_empty() {
200            return None;
201        }
202
203        // Convert exports to ProtoFile format for fingerprint calculation
204        let proto_files: Vec<actr_version::ProtoFile> = self
205            .exports
206            .iter()
207            .map(|export| actr_version::ProtoFile {
208                name: export
209                    .path
210                    .file_name()
211                    .and_then(|n| n.to_str())
212                    .unwrap_or("unknown.proto")
213                    .to_string(),
214                content: export.content.clone(),
215                path: export.path.to_str().map(|s| s.to_string()),
216            })
217            .collect();
218
219        // Calculate service fingerprint
220        let fingerprint =
221            actr_version::Fingerprint::calculate_service_semantic_fingerprint(&proto_files).ok()?;
222
223        // Build Protobuf entries
224        let protobufs = self
225            .exports
226            .iter()
227            .map(|export| {
228                // Calculate individual file fingerprint
229                let file_fingerprint =
230                    actr_version::Fingerprint::calculate_proto_semantic_fingerprint(
231                        &export.content,
232                    )
233                    .unwrap_or_else(|_| "error".to_string());
234
235                actr_protocol::service_spec::Protobuf {
236                    package: export
237                        .path
238                        .file_stem()
239                        .and_then(|n| n.to_str())
240                        .unwrap_or("unknown")
241                        .to_string(),
242                    content: export.content.clone(),
243                    fingerprint: file_fingerprint,
244                }
245            })
246            .collect();
247
248        // Get current timestamp
249        let published_at = std::time::SystemTime::now()
250            .duration_since(std::time::UNIX_EPOCH)
251            .ok()?
252            .as_secs() as i64;
253
254        Some(actr_protocol::ServiceSpec {
255            description: self.package.description.clone(),
256            fingerprint,
257            protobufs,
258            published_at: Some(published_at),
259            tags: self.tags.clone(),
260        })
261    }
262}
263
264// ============================================================================
265// PackageInfo 辅助方法
266// ============================================================================
267
268impl PackageInfo {
269    /// 获取 manufacturer(ActrType.manufacturer)
270    pub fn manufacturer(&self) -> &str {
271        &self.actr_type.manufacturer
272    }
273
274    /// 获取 type name(ActrType.name)
275    pub fn type_name(&self) -> &str {
276        &self.actr_type.name
277    }
278}
279
280// ============================================================================
281// Dependency 辅助方法
282// ============================================================================
283
284impl Dependency {
285    /// 是否跨 Realm 依赖
286    pub fn is_cross_realm(&self, self_realm: &Realm) -> bool {
287        self.realm.realm_id != self_realm.realm_id
288    }
289
290    /// 检查指纹是否匹配
291    pub fn matches_fingerprint(&self, fingerprint: &str) -> bool {
292        self.fingerprint
293            .as_ref()
294            .map(|fp| fp == fingerprint)
295            .unwrap_or(true) // 无指纹要求则总是匹配
296    }
297}
298
299// ============================================================================
300// ProtoFile 辅助方法
301// ============================================================================
302
303impl ProtoFile {
304    /// 获取文件名
305    pub fn file_name(&self) -> Option<&str> {
306        self.path.file_name()?.to_str()
307    }
308
309    /// 获取文件扩展名
310    pub fn extension(&self) -> Option<&str> {
311        self.path.extension()?.to_str()
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn test_config_methods() {
321        let config = Config {
322            package: PackageInfo {
323                name: "test-service".to_string(),
324                actr_type: ActrType {
325                    manufacturer: "acme".to_string(),
326                    name: "test-service".to_string(),
327                },
328                description: None,
329                authors: vec![],
330                license: None,
331            },
332            exports: vec![],
333            dependencies: vec![
334                Dependency {
335                    alias: "user-service".to_string(),
336                    realm: Realm { realm_id: 1001 },
337                    actr_type: ActrType {
338                        manufacturer: "acme".to_string(),
339                        name: "user-service".to_string(),
340                    },
341                    fingerprint: Some("service_semantic:abc123...".to_string()),
342                },
343                Dependency {
344                    alias: "shared-logger".to_string(),
345                    realm: Realm { realm_id: 9999 },
346                    actr_type: ActrType {
347                        manufacturer: "common".to_string(),
348                        name: "logging-service".to_string(),
349                    },
350                    fingerprint: None,
351                },
352            ],
353            signaling_url: Url::parse("ws://localhost:8081").unwrap(),
354            realm: Realm { realm_id: 1001 },
355            visible_in_discovery: true,
356            acl: None,
357            mailbox_path: None,
358            tags: vec![],
359            scripts: HashMap::new(),
360            webrtc: WebRtcConfig::default(),
361            observability: ObservabilityConfig {
362                filter_level: "info".to_string(),
363                tracing_enabled: false,
364                tracing_endpoint: "http://localhost:4317".to_string(),
365                tracing_service_name: "test-service".to_string(),
366            },
367        };
368
369        // 测试依赖查找
370        assert!(config.get_dependency("user-service").is_some());
371        assert!(config.get_dependency("not-exists").is_none());
372
373        // 测试跨 Realm 依赖
374        let cross_realm = config.cross_realm_dependencies();
375        assert_eq!(cross_realm.len(), 1);
376        assert_eq!(cross_realm[0].alias, "shared-logger");
377
378        // 测试指纹匹配
379        let user_dep = config.get_dependency("user-service").unwrap();
380        assert!(user_dep.matches_fingerprint("service_semantic:abc123..."));
381        assert!(!user_dep.matches_fingerprint("service_semantic:different"));
382    }
383}