open_lark/service/cloud_docs/wiki/v2/
search_wiki.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    core::{
6        api_req::ApiRequest,
7        api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
8        config::Config,
9        constants::AccessTokenType,
10        endpoints::cloud_docs::*,
11        http::Transport,
12        req_option::RequestOption,
13        SDKResult,
14    },
15    impl_executable_builder_owned,
16};
17
18/// 搜索Wiki请求
19#[derive(Debug, Serialize, Default)]
20pub struct SearchWikiRequest {
21    #[serde(skip)]
22    api_request: ApiRequest,
23    /// 搜索关键词
24    query: String,
25    /// 分页大小,最大值为50
26    #[serde(skip_serializing_if = "Option::is_none")]
27    page_size: Option<i32>,
28    /// 分页标记
29    #[serde(skip_serializing_if = "Option::is_none")]
30    page_token: Option<String>,
31    /// 指定搜索的知识空间id列表,不填时搜索所有有权限的知识空间
32    #[serde(skip_serializing_if = "Option::is_none")]
33    space_ids: Option<Vec<String>>,
34}
35
36impl SearchWikiRequest {
37    pub fn builder() -> SearchWikiRequestBuilder {
38        SearchWikiRequestBuilder::default()
39    }
40
41    pub fn new(query: impl ToString) -> Self {
42        Self {
43            query: query.to_string(),
44            ..Default::default()
45        }
46    }
47}
48
49#[derive(Default)]
50pub struct SearchWikiRequestBuilder {
51    request: SearchWikiRequest,
52}
53
54impl SearchWikiRequestBuilder {
55    /// 搜索关键词
56    pub fn query(mut self, query: impl ToString) -> Self {
57        self.request.query = query.to_string();
58        self
59    }
60
61    /// 分页大小,最大值为50
62    pub fn page_size(mut self, page_size: i32) -> Self {
63        self.request.page_size = Some(page_size);
64        self
65    }
66
67    /// 分页标记
68    pub fn page_token(mut self, page_token: impl ToString) -> Self {
69        self.request.page_token = Some(page_token.to_string());
70        self
71    }
72
73    /// 指定搜索的知识空间id列表
74    pub fn space_ids(mut self, space_ids: Vec<String>) -> Self {
75        self.request.space_ids = Some(space_ids);
76        self
77    }
78
79    /// 添加单个知识空间id
80    pub fn add_space_id(mut self, space_id: impl ToString) -> Self {
81        if self.request.space_ids.is_none() {
82            self.request.space_ids = Some(Vec::new());
83        }
84        if let Some(ref mut space_ids) = self.request.space_ids {
85            space_ids.push(space_id.to_string());
86        }
87        self
88    }
89
90    /// 搜索所有有权限的知识空间
91    pub fn search_all_spaces(mut self) -> Self {
92        self.request.space_ids = None;
93        self
94    }
95
96    pub fn build(mut self) -> SearchWikiRequest {
97        self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
98        self.request
99    }
100}
101
102impl_executable_builder_owned!(
103    SearchWikiRequestBuilder,
104    crate::service::cloud_docs::wiki::v2::V2,
105    SearchWikiRequest,
106    SearchWikiResponse,
107    search_wiki
108);
109
110/// 搜索结果项
111#[derive(Debug, Deserialize)]
112pub struct WikiSearchItem {
113    /// 节点token
114    pub node_token: String,
115    /// 知识空间id
116    pub space_id: String,
117    /// 文档标题
118    pub title: Option<String>,
119    /// 文档类型
120    pub obj_type: Option<String>,
121    /// 原始文档token
122    pub obj_token: Option<String>,
123    /// 父节点token
124    pub parent_node_token: Option<String>,
125    /// 知识空间名称
126    pub space_name: Option<String>,
127    /// 匹配的文本片段
128    pub snippet: Option<String>,
129    /// 最后编辑时间(毫秒时间戳)
130    pub obj_edit_time: Option<String>,
131    /// 最后编辑者
132    pub obj_edit_user: Option<String>,
133}
134
135/// 搜索Wiki响应
136#[derive(Debug, Deserialize)]
137pub struct SearchWikiResponse {
138    /// 是否还有更多项
139    pub has_more: bool,
140    /// 分页标记,当 has_more 为 true 时,会同时返回新的 page_token
141    pub page_token: Option<String>,
142    /// 搜索结果列表
143    pub items: Vec<WikiSearchItem>,
144}
145
146impl ApiResponseTrait for SearchWikiResponse {
147    fn data_format() -> ResponseFormat {
148        ResponseFormat::Data
149    }
150}
151
152/// 搜索Wiki
153pub async fn search_wiki(
154    request: SearchWikiRequest,
155    config: &Config,
156    option: Option<RequestOption>,
157) -> SDKResult<BaseResponse<SearchWikiResponse>> {
158    let mut api_req = request.api_request;
159    api_req.http_method = Method::POST;
160    api_req.api_path = WIKI_V2_SEARCH.to_string();
161    api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
162
163    let api_resp = Transport::request(api_req, config, option).await?;
164    Ok(api_resp)
165}
166
167impl WikiSearchItem {
168    /// 获取文档URL(如果有obj_token)
169    pub fn get_doc_url(&self) -> Option<String> {
170        self.obj_token
171            .as_ref()
172            .map(|token| match self.obj_type.as_deref() {
173                Some("doc") => format!("https://feishu.cn/docs/{token}"),
174                Some("sheet") => format!("https://feishu.cn/sheets/{token}"),
175                Some("bitable") => format!("https://feishu.cn/base/{token}"),
176                Some("mindnote") => format!("https://feishu.cn/mindnote/{token}"),
177                _ => format!("https://feishu.cn/wiki/{token}"),
178            })
179    }
180
181    /// 是否有匹配的文本片段
182    pub fn has_snippet(&self) -> bool {
183        self.snippet.is_some()
184    }
185
186    /// 获取显示标题(优先使用title,否则使用obj_token)
187    pub fn display_title(&self) -> String {
188        self.title.as_ref().cloned().unwrap_or_else(|| {
189            self.obj_token
190                .as_ref()
191                .cloned()
192                .unwrap_or_else(|| self.node_token.clone())
193        })
194    }
195}
196
197#[cfg(test)]
198#[allow(unused_variables, unused_unsafe)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_search_wiki_request_builder() {
204        let request = SearchWikiRequest::builder()
205            .query("测试搜索")
206            .page_size(20)
207            .add_space_id("spcxxxxxx")
208            .add_space_id("spcyyyyyy")
209            .build();
210
211        assert_eq!(request.query, "测试搜索");
212        assert_eq!(request.page_size, Some(20));
213        assert_eq!(
214            request.space_ids,
215            Some(vec!["spcxxxxxx".to_string(), "spcyyyyyy".to_string()])
216        );
217    }
218
219    #[test]
220    fn test_search_all_spaces() {
221        let request = SearchWikiRequest::builder()
222            .query("测试搜索")
223            .search_all_spaces()
224            .build();
225
226        assert_eq!(request.query, "测试搜索");
227        assert_eq!(request.space_ids, None);
228    }
229
230    #[test]
231    fn test_wiki_search_item_methods() {
232        let item = WikiSearchItem {
233            node_token: "wikcnxxxxxx".to_string(),
234            space_id: "spcxxxxxx".to_string(),
235            title: Some("测试文档".to_string()),
236            obj_type: Some("doc".to_string()),
237            obj_token: Some("doccnxxxxxx".to_string()),
238            parent_node_token: None,
239            space_name: Some("测试空间".to_string()),
240            snippet: Some("这是匹配的文本片段".to_string()),
241            obj_edit_time: None,
242            obj_edit_user: None,
243        };
244
245        assert_eq!(item.display_title(), "测试文档");
246        assert!(item.has_snippet());
247        assert_eq!(
248            item.get_doc_url(),
249            Some("https://feishu.cn/docs/doccnxxxxxx".to_string())
250        );
251    }
252}