open_lark/service/search/
mod.rs

1//! 搜索(Search)服务
2//!
3//! 提供飞书开放平台的智能搜索功能,支持全文搜索、数据源管理、
4//! 索引构建等企业级搜索能力。为企业提供统一的信息检索和知识发现服务。
5//!
6//! # 核心功能
7//!
8//! ## 数据源管理
9//! - 📊 自定义数据源创建和配置
10//! - 🔄 数据源同步和更新机制
11//! - 🏷️ 数据分类和标签管理
12//! - 📋 数据源权限控制
13//!
14//! ## 搜索引擎
15//! - 🔍 全文搜索和精确匹配
16//! - 🎯 智能推荐和相关性排序
17//! - 🔗 跨数据源联合搜索
18//! - 📈 搜索结果统计和分析
19//!
20//! ## 索引管理
21//! - 🏗️ 自动索引构建和维护
22//! - ⚡ 实时索引更新
23//! - 🗂️ Schema定义和字段映射
24//! - 🔧 索引优化和性能调优
25//!
26//! ## 数据项操作
27//! - 📝 数据项的增删改查
28//! - 🏷️ 元数据管理和标签
29//! - 🔐 访问权限和安全控制
30//! - 📊 数据质量监控
31//!
32//! # 使用示例
33//!
34//! ```rust
35//! use open_lark::prelude::*;
36//!
37//! let client = LarkClient::builder("app_id", "app_secret")
38//!     .with_app_type(AppType::SelfBuild)
39//!     .build();
40//!
41//! // 获取搜索服务
42//! let search = &client.search;
43//!
44//! // 创建数据源(v2版本)
45//! // let data_source_request = CreateDataSourceRequest::builder()
46//! //     .name("企业知识库")
47//! //     .description("包含公司所有技术文档")
48//! //     .build();
49//! // let data_source = search.v2.data_source.create(data_source_request, None).await?;
50//!
51//! // 执行搜索
52//! // let search_request = SearchRequest::builder()
53//! //     .query("飞书API")
54//! //     .data_source_id("ds_123")
55//! //     .build();
56//! // let results = search.v2.suite_search.search(search_request, None).await?;
57//! ```
58//!
59//! # API版本
60//!
61//! ## v1版本
62//! 基础搜索功能,提供简单的全文搜索能力。
63//!
64//! ## v2版本(推荐)
65//! 增强版搜索引擎,支持:
66//! - 自定义数据源和Schema
67//! - 高级搜索语法
68//! - 多维度结果排序
69//! - 搜索分析和统计
70//!
71//! # 搜索特性
72//!
73//! - 🚀 毫秒级搜索响应
74//! - 🎯 智能相关性算法
75//! - 📱 多端搜索体验统一
76//! - 🔐 细粒度权限控制
77//! - 📊 丰富的搜索分析
78
79use crate::core::config::Config;
80
81/// 搜索服务 v1 版本
82pub mod v1;
83/// 搜索服务 v2 版本
84pub mod v2;
85
86/// 搜索服务
87///
88/// 企业级智能搜索解决方案的统一入口,提供数据索引、全文检索、
89/// 智能推荐等完整的搜索功能体系。
90///
91/// # 服务架构
92///
93/// - **v1**: 基础搜索功能,简单易用
94/// - **v2**: 增强搜索引擎,功能丰富(推荐使用)
95///
96/// # 核心特性
97///
98/// - 🔍 高性能全文搜索引擎
99/// - 📊 灵活的数据源管理
100/// - 🎯 智能搜索推荐算法
101/// - 🔐 企业级权限控制
102/// - 📈 搜索效果分析统计
103///
104/// # 适用场景
105///
106/// - 企业知识库搜索
107/// - 文档管理系统
108/// - 内容发现和推荐
109/// - 数据分析和挖掘
110/// - 跨系统信息检索
111///
112/// # 最佳实践
113///
114/// - 合理设计数据源结构
115/// - 定期优化搜索索引
116/// - 监控搜索性能指标
117/// - 收集用户搜索反馈
118/// - 持续优化搜索算法
119pub struct SearchService {
120    /// v1版本搜索API服务
121    pub v1: v1::V1,
122    /// v2版本搜索API服务(推荐)
123    pub v2: v2::V2,
124}
125
126impl SearchService {
127    /// 创建新的搜索服务实例
128    ///
129    /// # 参数
130    /// - `config`: 客户端配置,包含认证信息和API设置
131    ///
132    /// # 返回值
133    /// 配置完成的搜索服务实例,包含v1和v2版本API
134    pub fn new(config: Config) -> Self {
135        Self {
136            v1: v1::V1::new(config.clone()),
137            v2: v2::V2::new(config),
138        }
139    }
140
141    /// 使用共享配置(实验性)
142    pub fn new_from_shared(shared: std::sync::Arc<Config>) -> Self {
143        Self {
144            v1: v1::V1::new(shared.as_ref().clone()),
145            v2: v2::V2::new(shared.as_ref().clone()),
146        }
147    }
148}
149
150#[cfg(test)]
151#[allow(unused_variables, unused_unsafe)]
152mod tests {
153    use super::*;
154    use crate::core::config::Config;
155
156    fn create_test_config() -> Config {
157        Config::default()
158    }
159
160    #[test]
161    fn test_search_service_creation() {
162        let config = create_test_config();
163        let search_service = SearchService::new(config);
164
165        // Verify service structure
166    }
167
168    #[test]
169    fn test_search_service_debug_trait() {
170        let config = create_test_config();
171        let search_service = SearchService::new(config);
172
173        // Test that service can be used (services don't need to implement Debug)
174    }
175
176    #[test]
177    fn test_search_service_api_versions_independence() {
178        let config = create_test_config();
179        let search_service = SearchService::new(config);
180
181        // Test that API versions are independent
182        let v1_ptr = std::ptr::addr_of!(search_service.v1) as *const u8;
183        let v2_ptr = std::ptr::addr_of!(search_service.v2) as *const u8;
184
185        assert_ne!(v1_ptr, v2_ptr, "API versions should be independent");
186    }
187
188    #[test]
189    fn test_search_service_with_custom_configurations() {
190        let test_configs = vec![
191            Config::builder()
192                .app_id("search_basic")
193                .app_secret("basic_secret")
194                .build(),
195            Config::builder()
196                .app_id("search_timeout")
197                .app_secret("timeout_secret")
198                .req_timeout(std::time::Duration::from_millis(25000))
199                .build(),
200            Config::builder()
201                .app_id("search_custom")
202                .app_secret("custom_secret")
203                .base_url("https://search.enterprise.com")
204                .build(),
205            Config::builder()
206                .app_id("search_full")
207                .app_secret("full_secret")
208                .req_timeout(std::time::Duration::from_millis(30000))
209                .base_url("https://full.search.com")
210                .enable_token_cache(false)
211                .build(),
212        ];
213
214        for config in test_configs {
215            let search_service = SearchService::new(config);
216
217            // Each configuration should create a valid service
218        }
219    }
220
221    #[test]
222    fn test_search_service_multiple_instances() {
223        let config1 = create_test_config();
224        let config2 = Config::builder()
225            .app_id("search2")
226            .app_secret("secret2")
227            .build();
228
229        let search_service1 = SearchService::new(config1);
230        let search_service2 = SearchService::new(config2);
231
232        // Services should be independent instances
233        let service1_ptr = std::ptr::addr_of!(search_service1) as *const u8;
234        let service2_ptr = std::ptr::addr_of!(search_service2) as *const u8;
235
236        assert_ne!(
237            service1_ptr, service2_ptr,
238            "Services should be independent instances"
239        );
240
241        // Each service should have valid API versions
242    }
243
244    #[test]
245    fn test_search_service_config_cloning_behavior() {
246        let original_config = create_test_config();
247
248        // Test that the service works with cloned configs
249        let search_service1 = SearchService::new(original_config.clone());
250        let search_service2 = SearchService::new(original_config);
251
252        // Both should work independently
253
254        // But should be different service instances
255        let service1_ptr = std::ptr::addr_of!(search_service1) as *const u8;
256        let service2_ptr = std::ptr::addr_of!(search_service2) as *const u8;
257        assert_ne!(service1_ptr, service2_ptr);
258    }
259
260    #[test]
261    fn test_search_service_v1_v2_api_access() {
262        let config = create_test_config();
263        let search_service = SearchService::new(config);
264
265        // Verify that both v1 and v2 APIs are accessible
266        let v1_ptr = std::ptr::addr_of!(search_service.v1) as *const u8;
267        let v2_ptr = std::ptr::addr_of!(search_service.v2) as *const u8;
268
269        assert!(
270            !v1_ptr.is_null(),
271            "V1 service should be properly instantiated"
272        );
273        assert!(
274            !v2_ptr.is_null(),
275            "V2 service should be properly instantiated"
276        );
277        assert_ne!(
278            v1_ptr, v2_ptr,
279            "V1 and V2 services should be independent instances"
280        );
281    }
282
283    #[test]
284    fn test_search_service_with_various_configurations() {
285        let variations = vec![
286            (
287                "minimal",
288                Config::builder()
289                    .app_id("minimal")
290                    .app_secret("secret")
291                    .build(),
292            ),
293            (
294                "with_timeout",
295                Config::builder()
296                    .app_id("timeout")
297                    .app_secret("secret")
298                    .req_timeout(std::time::Duration::from_millis(35000))
299                    .build(),
300            ),
301            (
302                "with_base_url",
303                Config::builder()
304                    .app_id("base_url")
305                    .app_secret("secret")
306                    .base_url("https://test.search.api.com")
307                    .build(),
308            ),
309            (
310                "full_featured",
311                Config::builder()
312                    .app_id("full")
313                    .app_secret("secret")
314                    .req_timeout(std::time::Duration::from_millis(40000))
315                    .base_url("https://full.test.search.api.com")
316                    .enable_token_cache(true)
317                    .build(),
318            ),
319        ];
320
321        let mut services = Vec::new();
322        for (name, config) in variations {
323            let service = SearchService::new(config);
324            services.push((name, service));
325        }
326
327        // All variations should create valid services
328        assert_eq!(services.len(), 4);
329
330        // Test that all services are independent
331        for (i, (_, service1)) in services.iter().enumerate() {
332            for (_, service2) in services.iter().skip(i + 1) {
333                let ptr1 = std::ptr::addr_of!(*service1) as *const u8;
334                let ptr2 = std::ptr::addr_of!(*service2) as *const u8;
335                assert_ne!(
336                    ptr1, ptr2,
337                    "Services with different configs should be independent"
338                );
339            }
340        }
341    }
342
343    #[test]
344    fn test_search_service_concurrent_creation() {
345        let configs = vec![
346            Config::builder()
347                .app_id("search_concurrent_1")
348                .app_secret("secret_1")
349                .build(),
350            Config::builder()
351                .app_id("search_concurrent_2")
352                .app_secret("secret_2")
353                .build(),
354            Config::builder()
355                .app_id("search_concurrent_3")
356                .app_secret("secret_3")
357                .build(),
358        ];
359
360        let mut services = Vec::new();
361        for config in configs {
362            let service = SearchService::new(config);
363            services.push(service);
364        }
365
366        // All services should be created successfully
367        assert_eq!(services.len(), 3);
368
369        // Verify all services are independent
370        for (i, service1) in services.iter().enumerate() {
371            for service2 in services.iter().skip(i + 1) {
372                let ptr1 = std::ptr::addr_of!(*service1) as *const u8;
373                let ptr2 = std::ptr::addr_of!(*service2) as *const u8;
374                assert_ne!(ptr1, ptr2, "Services should be independent instances");
375            }
376        }
377    }
378
379    #[test]
380    fn test_search_service_extreme_configurations() {
381        let extreme_configs = vec![
382            // Very short timeout
383            Config::builder()
384                .app_id("search_fast")
385                .app_secret("fast_secret")
386                .req_timeout(std::time::Duration::from_millis(50))
387                .build(),
388            // Very long timeout
389            Config::builder()
390                .app_id("search_slow")
391                .app_secret("slow_secret")
392                .req_timeout(std::time::Duration::from_secs(600))
393                .build(),
394            // Token cache disabled
395            Config::builder()
396                .app_id("search_no_cache")
397                .app_secret("no_cache_secret")
398                .enable_token_cache(false)
399                .build(),
400            // Custom search URL
401            Config::builder()
402                .app_id("search_custom_base")
403                .app_secret("custom_base_secret")
404                .base_url("https://custom.search.api.endpoint")
405                .build(),
406        ];
407
408        for config in extreme_configs {
409            let search_service = SearchService::new(config);
410
411            // Each service should be created successfully regardless of extreme config
412            let service_ptr = std::ptr::addr_of!(search_service) as *const u8;
413            assert!(
414                !service_ptr.is_null(),
415                "Service should be created with extreme config"
416            );
417        }
418    }
419
420    #[test]
421    fn test_search_service_api_version_structure() {
422        let config = create_test_config();
423        let search_service = SearchService::new(config);
424
425        // Verify the service contains exactly two API versions
426        let v1_offset = std::ptr::addr_of!(search_service.v1) as usize
427            - std::ptr::addr_of!(search_service) as usize;
428        let v2_offset = std::ptr::addr_of!(search_service.v2) as usize
429            - std::ptr::addr_of!(search_service) as usize;
430
431        // V1 and V2 should have different memory offsets
432        assert_ne!(
433            v1_offset, v2_offset,
434            "V1 and V2 should occupy different memory positions"
435        );
436
437        // Both offsets should be reasonable (within struct bounds)
438        assert!(v1_offset < 4096, "V1 offset should be reasonable");
439        assert!(v2_offset < 4096, "V2 offset should be reasonable");
440    }
441
442    #[test]
443    fn test_search_service_memory_consistency() {
444        let config = create_test_config();
445        let search_service = SearchService::new(config);
446
447        // Test that the service maintains memory consistency across accesses
448        let service_ptr1 = std::ptr::addr_of!(search_service) as *const u8;
449        let service_ptr2 = std::ptr::addr_of!(search_service) as *const u8;
450
451        assert_eq!(
452            service_ptr1, service_ptr2,
453            "Service memory address should be consistent"
454        );
455
456        // Test API version consistency
457        let v1_ptr1 = std::ptr::addr_of!(search_service.v1) as *const u8;
458        let v1_ptr2 = std::ptr::addr_of!(search_service.v1) as *const u8;
459        let v2_ptr1 = std::ptr::addr_of!(search_service.v2) as *const u8;
460        let v2_ptr2 = std::ptr::addr_of!(search_service.v2) as *const u8;
461
462        assert_eq!(
463            v1_ptr1, v1_ptr2,
464            "V1 API memory address should be consistent"
465        );
466        assert_eq!(
467            v2_ptr1, v2_ptr2,
468            "V2 API memory address should be consistent"
469        );
470    }
471
472    #[test]
473    fn test_search_service_v1_api_completeness() {
474        let config = create_test_config();
475        let search_service = SearchService::new(config);
476
477        // Test V1 API structure exists and is accessible
478        let v1_ptr = std::ptr::addr_of!(search_service.v1) as *const u8;
479        assert!(!v1_ptr.is_null(), "V1 Search API should be instantiated");
480    }
481
482    #[test]
483    fn test_search_service_v2_api_completeness() {
484        let config = create_test_config();
485        let search_service = SearchService::new(config);
486
487        // Test V2 API structure exists and is accessible
488        let v2_ptr = std::ptr::addr_of!(search_service.v2) as *const u8;
489        assert!(!v2_ptr.is_null(), "V2 Search API should be instantiated");
490    }
491
492    #[test]
493    fn test_search_service_config_independence() {
494        let config1 = Config::builder()
495            .app_id("search_app1")
496            .app_secret("search_secret1")
497            .build();
498        let config2 = Config::builder()
499            .app_id("search_app2")
500            .app_secret("search_secret2")
501            .build();
502
503        let search_service1 = SearchService::new(config1);
504        let search_service2 = SearchService::new(config2);
505
506        // Services should be independent instances
507        let service1_ptr = std::ptr::addr_of!(search_service1) as *const u8;
508        let service2_ptr = std::ptr::addr_of!(search_service2) as *const u8;
509
510        assert_ne!(
511            service1_ptr, service2_ptr,
512            "Services should be independent instances"
513        );
514
515        // API versions should also be independent
516        let v1_ptr1 = std::ptr::addr_of!(search_service1.v1) as *const u8;
517        let v1_ptr2 = std::ptr::addr_of!(search_service2.v1) as *const u8;
518        let v2_ptr1 = std::ptr::addr_of!(search_service1.v2) as *const u8;
519        let v2_ptr2 = std::ptr::addr_of!(search_service2.v2) as *const u8;
520
521        assert_ne!(v1_ptr1, v1_ptr2, "V1 services should be independent");
522        assert_ne!(v2_ptr1, v2_ptr2, "V2 services should be independent");
523    }
524
525    #[test]
526    fn test_search_service_configuration_scenarios() {
527        // Test empty config handling
528        let empty_config = Config::default();
529        let search_service_empty = SearchService::new(empty_config);
530        let empty_ptr = std::ptr::addr_of!(search_service_empty) as *const u8;
531        assert!(!empty_ptr.is_null(), "Service should handle empty config");
532
533        // Test minimal config
534        let minimal_config = Config::builder().app_id("min").app_secret("sec").build();
535        let search_service_minimal = SearchService::new(minimal_config);
536        let minimal_ptr = std::ptr::addr_of!(search_service_minimal) as *const u8;
537        assert!(
538            !minimal_ptr.is_null(),
539            "Service should handle minimal config"
540        );
541
542        // Test Unicode config
543        let unicode_config = Config::builder()
544            .app_id("搜索应用")
545            .app_secret("搜索密钥")
546            .base_url("https://搜索.com")
547            .build();
548        let search_service_unicode = SearchService::new(unicode_config);
549        let unicode_ptr = std::ptr::addr_of!(search_service_unicode) as *const u8;
550        assert!(
551            !unicode_ptr.is_null(),
552            "Service should handle Unicode config"
553        );
554    }
555}