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        Cap::register(cap).unwrap();
183        assert!(Cap::get("test_cap").unwrap().is_some());
184        assert!(Cap::len().unwrap() >= 1);
185    }
186
187    #[tokio::test]
188    async fn test_call_through_facade() {
189        Cap::init().ok();
190        let cap = Arc::new(TestCapability) as Arc<dyn Capability>;
191        Cap::register(cap).ok();
192        let result = Cap::call("test_cap", json!({"hello": "world"})).await;
193        assert!(result.is_ok());
194    }
195
196    #[test]
197    fn test_is_initialized_after_init() {
198        Cap::init().ok();
199        assert!(Cap::is_initialized());
200    }
201
202    #[test]
203    fn test_unregister_returns_removed_cap() {
204        Cap::init().ok();
205        let cap = Arc::new(TestCapability) as Arc<dyn Capability>;
206        Cap::register(cap).ok();
207        let removed = Cap::unregister("test_cap").unwrap();
208        assert!(removed.is_some());
209        assert_eq!(removed.unwrap().name(), "test_cap");
210        assert!(Cap::get("test_cap").unwrap().is_none());
211        let again = Cap::unregister("test_cap").unwrap();
212        assert!(again.is_none());
213    }
214
215    #[test]
216    fn test_find_by_tags_returns_matching_names() {
217        Cap::init().ok();
218        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
219        Cap::register(cap).ok();
220        let results = Cap::find_by_tags(&["plugin"], Some(CapabilitySource::Plugin)).unwrap();
221        let names: Vec<&str> = results.iter().map(|c| c.name()).collect();
222        assert!(names.contains(&"plugin_cap"));
223        let none = Cap::find_by_tags(&["zzz_no_such_tag_zzz"], None).unwrap();
224        assert!(none.is_empty());
225    }
226
227    #[test]
228    fn test_search_returns_matching_names() {
229        Cap::init().ok();
230        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
231        Cap::register(cap).ok();
232        let results = Cap::search("plugin_cap").unwrap();
233        let names: Vec<&str> = results.iter().map(|c| c.name()).collect();
234        assert!(names.contains(&"plugin_cap"));
235        let empty = Cap::search("zzz_no_match_zzz").unwrap();
236        assert!(empty.is_empty());
237    }
238
239    #[test]
240    fn test_list_all_contains_registered_cap() {
241        Cap::init().ok();
242        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
243        Cap::register(cap).ok();
244        let all = Cap::list_all().unwrap();
245        let names: Vec<&str> = all.iter().map(|c| c.name()).collect();
246        assert!(names.contains(&"plugin_cap"));
247    }
248
249    #[test]
250    fn test_list_by_source_filters_correctly() {
251        Cap::init().ok();
252        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
253        Cap::register(cap).ok();
254        let plugins = Cap::list_by_source(CapabilitySource::Plugin).unwrap();
255        let names: Vec<&str> = plugins.iter().map(|c| c.name()).collect();
256        assert!(names.contains(&"plugin_cap"));
257        let services = Cap::list_by_source(CapabilitySource::Service).unwrap();
258        let service_names: Vec<&str> = services.iter().map(|c| c.name()).collect();
259        assert!(!service_names.contains(&"plugin_cap"));
260    }
261
262    #[test]
263    fn test_is_empty_and_len_consistent() {
264        Cap::init().ok();
265        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
266        Cap::register(cap).ok();
267        let len = Cap::len().unwrap();
268        let is_empty = Cap::is_empty().unwrap();
269        assert!(len >= 1);
270        assert!(!is_empty);
271        assert_eq!(is_empty, len == 0);
272    }
273
274    #[test]
275    fn test_metrics_reports_registered_count() {
276        Cap::init().ok();
277        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
278        Cap::register(cap).ok();
279        let metrics = Cap::metrics().unwrap();
280        assert!(metrics.total >= 1);
281    }
282
283    #[test]
284    fn test_set_permission_checker_succeeds() {
285        Cap::init().ok();
286        let checker = Arc::new(crate::permission::AllowAll) as Arc<dyn PermissionChecker>;
287        assert!(Cap::set_permission_checker(checker).is_ok());
288    }
289
290    #[tokio::test]
291    async fn test_call_with_tenant_returns_expected_value() {
292        Cap::init().ok();
293        let cap = Arc::new(PluginCapability) as Arc<dyn Capability>;
294        Cap::register(cap).ok();
295        let checker = Arc::new(crate::permission::AllowAll) as Arc<dyn PermissionChecker>;
296        Cap::set_permission_checker(checker).ok();
297        let result = Cap::call_with_tenant("plugin_cap", json!({"x": 1}), 42).await;
298        assert!(result.is_ok());
299        assert_eq!(result.unwrap(), json!({"x": 1}));
300    }
301
302    #[test]
303    fn test_init_with_already_initialized_fails() {
304        Cap::init().ok();
305        let registry = Arc::new(CapabilityRegistry::new());
306        let result = Cap::init_with(registry);
307        assert!(result.is_err());
308    }
309
310    #[test]
311    fn test_get_nonexistent_returns_none() {
312        Cap::init().ok();
313        assert!(Cap::get("zzz_nonexistent_zzz").unwrap().is_none());
314    }
315}