Skip to main content

sz_rust_capability/
facade.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-2026 SZ-Rust Team
3//
4use std::sync::{Arc, OnceLock};
5
6use crate::capability::Capability;
7use crate::error::{CapError, CapResult};
8use crate::metrics::CapMetrics;
9use crate::permission::PermissionChecker;
10use crate::registry::CapabilityRegistry;
11use crate::source::CapabilitySource;
12
13struct CapInstance {
14    registry: Arc<CapabilityRegistry>,
15}
16
17static GLOBAL: OnceLock<CapInstance> = OnceLock::new();
18
19/// Capability Registry 全局 facade,对齐 `Ai` facade 模式。
20///
21/// 使用 `OnceLock<CapInstance>` 全局单例,所有静态方法通过 `instance()` 获取后委托给内部 [`CapabilityRegistry`]。
22///
23/// # 使用方式
24///
25/// ```no_run
26/// use sz_rust_capability::Cap;
27///
28/// Cap::init().ok(); // 初始化(仅需一次)
29/// let metrics = Cap::metrics().unwrap();
30/// ```
31pub struct Cap;
32
33impl Cap {
34    /// 使用外部 registry 初始化全局 facade(与调用方共享同一实例)
35    ///
36    /// 业务应用(如 sz300)持有自己的 `Arc<CapabilityRegistry>` 用于注入
37    /// `AppState` 时,应使用本方法而非 [`Cap::init`]——否则全局 facade 与
38    /// 应用局部 registry 是**两个独立实例**,`Cap::register` 注册的能力
39    /// 无法被业务 handler 访问(2026-08-15 双实例缺陷修复)。
40    pub fn init_with(registry: Arc<CapabilityRegistry>) -> CapResult<()> {
41        GLOBAL
42            .set(CapInstance { registry })
43            .map_err(|_| CapError::NotInitialized)
44    }
45
46    pub fn init() -> CapResult<()> {
47        Self::init_with(Arc::new(CapabilityRegistry::new()))
48    }
49
50    pub fn is_initialized() -> bool {
51        GLOBAL.get().is_some()
52    }
53
54    fn instance() -> CapResult<&'static CapInstance> {
55        GLOBAL.get().ok_or(CapError::NotInitialized)
56    }
57
58    pub fn register(cap: Arc<dyn Capability>) -> CapResult<Option<Arc<dyn Capability>>> {
59        Ok(Self::instance()?.registry.register(cap))
60    }
61
62    pub fn unregister(name: &str) -> CapResult<Option<Arc<dyn Capability>>> {
63        Ok(Self::instance()?.registry.unregister(name))
64    }
65
66    pub fn get(name: &str) -> CapResult<Option<Arc<dyn Capability>>> {
67        Ok(Self::instance()?.registry.get(name))
68    }
69
70    pub fn find_by_tags(
71        tags: &[&str],
72        source: Option<CapabilitySource>,
73    ) -> CapResult<Vec<Arc<dyn Capability>>> {
74        Ok(Self::instance()?.registry.find_by_tags(tags, source))
75    }
76
77    pub fn search(query: &str) -> CapResult<Vec<Arc<dyn Capability>>> {
78        Ok(Self::instance()?.registry.search(query))
79    }
80
81    pub fn list_all() -> CapResult<Vec<Arc<dyn Capability>>> {
82        Ok(Self::instance()?.registry.list_all())
83    }
84
85    pub fn list_by_source(source: CapabilitySource) -> CapResult<Vec<Arc<dyn Capability>>> {
86        Ok(Self::instance()?.registry.list_by_source(source))
87    }
88
89    pub async fn call(name: &str, args: serde_json::Value) -> CapResult<serde_json::Value> {
90        Self::instance()?.registry.call(name, args).await
91    }
92
93    /// 调用能力,携带租户上下文用于权限检查。
94    pub async fn call_with_tenant(
95        name: &str,
96        args: serde_json::Value,
97        tenant_id: i64,
98    ) -> CapResult<serde_json::Value> {
99        Self::instance()?
100            .registry
101            .call_with_tenant(name, args, tenant_id)
102            .await
103    }
104
105    /// 设置权限检查器。
106    pub fn set_permission_checker(checker: Arc<dyn PermissionChecker>) -> CapResult<()> {
107        Self::instance()?.registry.set_permission_checker(checker);
108        Ok(())
109    }
110
111    pub fn metrics() -> CapResult<CapMetrics> {
112        Ok(Self::instance()?.registry.metrics())
113    }
114
115    pub fn len() -> CapResult<usize> {
116        Ok(Self::instance()?.registry.len())
117    }
118
119    pub fn is_empty() -> CapResult<bool> {
120        Ok(Self::instance()?.registry.is_empty())
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use async_trait::async_trait;
128    use serde_json::json;
129
130    struct TestCapability;
131
132    #[async_trait]
133    impl Capability for TestCapability {
134        fn name(&self) -> &'static str {
135            "test_cap"
136        }
137        fn description(&self) -> &'static str {
138            "测试能力"
139        }
140        fn schema(&self) -> serde_json::Value {
141            json!({})
142        }
143        fn tags(&self) -> &[&'static str] {
144            &["test"]
145        }
146        fn source(&self) -> CapabilitySource {
147            CapabilitySource::Skill
148        }
149        async fn call(&self, args: serde_json::Value) -> CapResult<serde_json::Value> {
150            Ok(args)
151        }
152    }
153
154    struct PluginCapability;
155
156    #[async_trait]
157    impl Capability for PluginCapability {
158        fn name(&self) -> &'static str {
159            "plugin_cap"
160        }
161        fn description(&self) -> &'static str {
162            "插件能力"
163        }
164        fn schema(&self) -> serde_json::Value {
165            json!({})
166        }
167        fn tags(&self) -> &[&'static str] {
168            &["plugin", "data"]
169        }
170        fn source(&self) -> CapabilitySource {
171            CapabilitySource::Plugin
172        }
173        async fn call(&self, args: serde_json::Value) -> CapResult<serde_json::Value> {
174            Ok(args)
175        }
176    }
177
178    #[test]
179    fn test_facade_lifecycle() {
180        Cap::init().ok();
181        let cap = Arc::new(TestCapability) as Arc<dyn Capability>;
182        // GLOBAL 注册表为进程级共享,并行测试可能已注册同名 capability
183        // —— register 返回 Err 属正常竞争,语义断言以 get/len 为准(v1.4 flaky 修复)
184        Cap::register(cap).ok();
185        assert!(Cap::get("test_cap").unwrap().is_some());
186        assert!(Cap::len().unwrap() >= 1);
187    }
188
189    #[tokio::test]
190    async fn test_call_through_facade() {
191        Cap::init().ok();
192        let cap = Arc::new(TestCapability) as Arc<dyn Capability>;
193        Cap::register(cap).ok();
194        let result = Cap::call("test_cap", json!({"hello": "world"})).await;
195        assert!(result.is_ok());
196    }
197
198    #[test]
199    fn test_is_initialized_after_init() {
200        Cap::init().ok();
201        assert!(Cap::is_initialized());
202    }
203
204    #[test]
205    fn test_unregister_returns_removed_cap() {
206        Cap::init().ok();
207        let cap = Arc::new(TestCapability) as Arc<dyn Capability>;
208        Cap::register(cap).ok();
209        let removed = Cap::unregister("test_cap").unwrap();
210        assert!(removed.is_some());
211        assert_eq!(removed.unwrap().name(), "test_cap");
212        assert!(Cap::get("test_cap").unwrap().is_none());
213        let again = Cap::unregister("test_cap").unwrap();
214        assert!(again.is_none());
215    }
216
217    #[test]
218    fn test_find_by_tags_returns_matching_names() {
219        Cap::init().ok();
220        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
221        Cap::register(cap).ok();
222        let results = Cap::find_by_tags(&["plugin"], Some(CapabilitySource::Plugin)).unwrap();
223        let names: Vec<&str> = results.iter().map(|c| c.name()).collect();
224        assert!(names.contains(&"plugin_cap"));
225        let none = Cap::find_by_tags(&["zzz_no_such_tag_zzz"], None).unwrap();
226        assert!(none.is_empty());
227    }
228
229    #[test]
230    fn test_search_returns_matching_names() {
231        Cap::init().ok();
232        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
233        Cap::register(cap).ok();
234        let results = Cap::search("plugin_cap").unwrap();
235        let names: Vec<&str> = results.iter().map(|c| c.name()).collect();
236        assert!(names.contains(&"plugin_cap"));
237        let empty = Cap::search("zzz_no_match_zzz").unwrap();
238        assert!(empty.is_empty());
239    }
240
241    #[test]
242    fn test_list_all_contains_registered_cap() {
243        Cap::init().ok();
244        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
245        Cap::register(cap).ok();
246        let all = Cap::list_all().unwrap();
247        let names: Vec<&str> = all.iter().map(|c| c.name()).collect();
248        assert!(names.contains(&"plugin_cap"));
249    }
250
251    #[test]
252    fn test_list_by_source_filters_correctly() {
253        Cap::init().ok();
254        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
255        Cap::register(cap).ok();
256        let plugins = Cap::list_by_source(CapabilitySource::Plugin).unwrap();
257        let names: Vec<&str> = plugins.iter().map(|c| c.name()).collect();
258        assert!(names.contains(&"plugin_cap"));
259        let services = Cap::list_by_source(CapabilitySource::Service).unwrap();
260        let service_names: Vec<&str> = services.iter().map(|c| c.name()).collect();
261        assert!(!service_names.contains(&"plugin_cap"));
262    }
263
264    #[test]
265    fn test_is_empty_and_len_consistent() {
266        Cap::init().ok();
267        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
268        Cap::register(cap).ok();
269        let len = Cap::len().unwrap();
270        let is_empty = Cap::is_empty().unwrap();
271        assert!(len >= 1);
272        assert!(!is_empty);
273        assert_eq!(is_empty, len == 0);
274    }
275
276    #[test]
277    fn test_metrics_reports_registered_count() {
278        Cap::init().ok();
279        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
280        Cap::register(cap).ok();
281        let metrics = Cap::metrics().unwrap();
282        assert!(metrics.total >= 1);
283    }
284
285    #[test]
286    fn test_set_permission_checker_succeeds() {
287        Cap::init().ok();
288        let checker = Arc::new(crate::permission::AllowAll) as Arc<dyn PermissionChecker>;
289        assert!(Cap::set_permission_checker(checker).is_ok());
290    }
291
292    #[tokio::test]
293    async fn test_call_with_tenant_returns_expected_value() {
294        Cap::init().ok();
295        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
296        Cap::register(cap).ok();
297        let checker = Arc::new(crate::permission::AllowAll) as Arc<dyn PermissionChecker>;
298        Cap::set_permission_checker(checker).ok();
299        let result = Cap::call_with_tenant("plugin_cap", json!({"x": 1}), 42).await;
300        assert!(result.is_ok());
301        assert_eq!(result.unwrap(), json!({"x": 1}));
302    }
303
304    #[test]
305    fn test_init_with_already_initialized_fails() {
306        Cap::init().ok();
307        let registry = Arc::new(CapabilityRegistry::new());
308        let result = Cap::init_with(registry);
309        assert!(result.is_err());
310    }
311
312    #[test]
313    fn test_get_nonexistent_returns_none() {
314        Cap::init().ok();
315        assert!(Cap::get("zzz_nonexistent_zzz").unwrap().is_none());
316    }
317}