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