open_lark/service/search/v1/
user.rs

1use log::error;
2use reqwest::Method;
3use serde::{Deserialize, Serialize};
4
5use crate::core::{
6    api_req::ApiRequest,
7    api_resp::{ApiResponseTrait, BaseResponse},
8    config::Config,
9    constants::AccessTokenType,
10    http::Transport,
11    req_option::RequestOption,
12    standard_response::StandardResponse,
13    validation::{self, ValidationResult},
14    SDKResult,
15};
16use crate::impl_full_service;
17
18pub struct UserService {
19    config: Config,
20}
21
22impl UserService {
23    pub fn new(config: Config) -> Self {
24        Self { config }
25    }
26
27    /// 搜索用户
28    ///
29    /// <https://open.feishu.cn/document/server-docs/search-v1/user/search>
30    pub async fn search_user(
31        &self,
32        search_user_request: SearchUserRequest,
33        option: Option<RequestOption>,
34    ) -> SDKResult<SearchUserResponse> {
35        let mut api_req = search_user_request.api_request;
36        api_req.http_method = Method::GET;
37        api_req.api_path = crate::core::endpoints::search::SEARCH_V1_USER.to_string();
38        api_req.supported_access_token_types = vec![AccessTokenType::User];
39
40        let api_resp: BaseResponse<SearchUserResponse> =
41            Transport::request(api_req, &self.config, option).await?;
42        api_resp.into_result()
43    }
44
45    /// 搜索用户 (返回BaseResponse供迭代器使用)
46    async fn search_user_with_base_response(
47        &self,
48        search_user_request: SearchUserRequest,
49        option: Option<RequestOption>,
50    ) -> SDKResult<BaseResponse<SearchUserResponse>> {
51        let mut api_req = search_user_request.api_request;
52        api_req.http_method = Method::GET;
53        api_req.api_path = crate::core::endpoints::search::SEARCH_V1_USER.to_string();
54        api_req.supported_access_token_types = vec![AccessTokenType::User];
55
56        Transport::request(api_req, &self.config, option).await
57    }
58
59    pub fn search_user_iter(
60        &self,
61        search_user_request: SearchUserRequest,
62        option: Option<RequestOption>,
63    ) -> SearchUserIterator<'_> {
64        SearchUserIterator {
65            user_service: self,
66            request: search_user_request,
67            option,
68            has_more: true,
69        }
70    }
71
72    /// 使用分页验证搜索用户
73    ///
74    /// 提供一个更安全的方式来搜索用户,自动验证分页参数
75    pub async fn search_user_with_validated_pagination(
76        &self,
77        query: impl ToString,
78        page_size: Option<u32>,
79        page_token: Option<String>,
80        option: Option<RequestOption>,
81    ) -> SDKResult<SearchUserResponse> {
82        // 创建请求构建器
83        let builder = SearchUserRequest::builder()
84            .query(query)
85            .with_pagination(page_size, page_token)?;
86
87        self.search_user(builder.build(), option).await
88    }
89}
90
91impl_full_service!(UserService, "search.user", "v1");
92
93/// 搜索用户请求
94#[derive(Default, Clone)]
95pub struct SearchUserRequest {
96    api_request: ApiRequest,
97}
98
99impl SearchUserRequest {
100    pub fn builder() -> SearchUserRequestBuilder {
101        SearchUserRequestBuilder::default()
102    }
103}
104
105#[derive(Default)]
106pub struct SearchUserRequestBuilder {
107    search_user_request: SearchUserRequest,
108}
109
110impl SearchUserRequestBuilder {
111    /// 要执行搜索的字符串,一般为用户名。
112    pub fn query(mut self, query: impl ToString) -> Self {
113        self.search_user_request
114            .api_request
115            .query_params
116            .insert("query", query.to_string());
117        self
118    }
119
120    /// 分页大小,最小为 1,最大为 200,默认为 20。
121    ///
122    /// # 验证规则
123    ///
124    /// 分页大小必须在 1-200 之间(搜索服务限制),推荐值为 20
125    pub fn page_size(mut self, page_size: i32) -> Self {
126        // 搜索服务的分页大小限制更严格(1-200)
127        if !(1..=200).contains(&page_size) {
128            log::warn!(
129                "Page size {} is out of valid range (1-200) for search service",
130                page_size
131            );
132        }
133
134        self.search_user_request
135            .api_request
136            .query_params
137            .insert("page_size", page_size.to_string());
138        self
139    }
140
141    /// 分页标识,获取首页不需要填写,获取下一页时传入上一页返回的分页标识值。
142    /// 请注意此字段的值并没有特殊含义,请使用每次请求所返回的标识值。
143    pub fn page_token(mut self, page_token: impl ToString) -> Self {
144        let token = page_token.to_string();
145
146        // 验证分页标记格式
147        match validation::validate_page_token(&token, "page_token") {
148            ValidationResult::Valid => {}
149            ValidationResult::Warning(msg) => {
150                log::warn!("Page token validation warning: {}", msg);
151            }
152            ValidationResult::Invalid(msg) => {
153                log::error!("Invalid page token: {}", msg);
154            }
155        }
156
157        self.search_user_request
158            .api_request
159            .query_params
160            .insert("page_token", token);
161        self
162    }
163
164    pub fn build(self) -> SearchUserRequest {
165        self.search_user_request
166    }
167
168    /// 使用分页验证构建器设置分页参数
169    ///
170    /// 这个方法提供了一个更安全的分页参数设置方式,会自动验证参数的有效性
171    /// 搜索服务的分页大小限制为 1-200
172    pub fn with_pagination(
173        mut self,
174        page_size: Option<u32>,
175        page_token: Option<String>,
176    ) -> SDKResult<Self> {
177        let mut pagination_builder =
178            validation::pagination::PaginationRequestBuilder::<SearchUserResponse>::new();
179
180        if let Some(size) = page_size {
181            // 搜索服务有更严格的分页大小限制(1-200)
182            if size > 200 {
183                return Err(crate::core::error::LarkAPIError::illegal_param(format!(
184                    "Page size {} exceeds maximum limit of 200 for search service",
185                    size
186                )));
187            }
188            pagination_builder = pagination_builder.with_page_size(size);
189        }
190
191        if let Some(token) = page_token {
192            pagination_builder = pagination_builder.with_page_token(token);
193        }
194
195        // 构建分页参数
196        let params = pagination_builder.build()?;
197
198        // 应用到请求中
199        for (key, value) in params {
200            self.search_user_request
201                .api_request
202                .query_params
203                .insert(key, value);
204        }
205
206        Ok(self)
207    }
208}
209
210crate::impl_executable_builder_owned!(
211    SearchUserRequestBuilder,
212    UserService,
213    SearchUserRequest,
214    SearchUserResponse,
215    search_user
216);
217
218#[derive(Debug, Serialize, Deserialize)]
219pub struct SearchUserResponse {
220    /// 搜索到的用户列表。
221    pub users: Vec<UserInSearchResponse>,
222    /// 是否还有更多用户,值为 true 表示存在下一页。
223    pub has_more: bool,
224    /// 分页标识,存在下一页的时候返回。下次请求带上此标识可以获取下一页的用户。
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub page_token: Option<String>,
227}
228
229/// 搜索到的用户信息。
230#[derive(Debug, Serialize, Deserialize)]
231pub struct UserInSearchResponse {
232    /// 用户的头像 URL。
233    pub avatar: UserAvatar,
234    /// 用户的部门信息。
235    pub department_ids: Vec<String>,
236    /// 用户的姓名。
237    pub name: String,
238    /// 用户的 open_id。
239    pub open_id: String,
240    /// 用户的 open_id。
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub user_id: Option<String>,
243}
244
245/// 用户的头像信息。
246#[derive(Debug, Serialize, Deserialize)]
247pub struct UserAvatar {
248    /// 用户的头像图片 URL,72×72px。
249    pub avatar_72: String,
250    /// 用户的头像图片 URL,240×240px。
251    pub avatar_240: String,
252    /// 用户的头像图片 URL,640×640px。
253    pub avatar_640: String,
254    /// 用户的头像图片 URL,原始大小。
255    pub avatar_origin: String,
256}
257
258impl ApiResponseTrait for SearchUserResponse {
259    fn data_format() -> crate::core::api_resp::ResponseFormat {
260        crate::core::api_resp::ResponseFormat::Data
261    }
262}
263
264pub struct SearchUserIterator<'a> {
265    user_service: &'a UserService,
266    request: SearchUserRequest,
267    option: Option<RequestOption>,
268    has_more: bool,
269}
270
271impl SearchUserIterator<'_> {
272    pub async fn next(&mut self) -> Option<Vec<UserInSearchResponse>> {
273        if !self.has_more {
274            return None;
275        }
276
277        match self
278            .user_service
279            .search_user_with_base_response(self.request.clone(), self.option.clone())
280            .await
281        {
282            Ok(resp) => match resp.data {
283                Some(data) => {
284                    self.has_more = data.has_more;
285                    if data.has_more {
286                        if let Some(token) = data.page_token {
287                            self.request
288                                .api_request
289                                .query_params
290                                .insert("page_token", token);
291                            Some(data.users)
292                        } else {
293                            // has_more is true but no page_token. Stop iterating to avoid panic.
294                            self.has_more = false;
295                            Some(data.users)
296                        }
297                    } else if data.users.is_empty() {
298                        None
299                    } else {
300                        Some(data.users)
301                    }
302                }
303                None => None,
304            },
305            Err(e) => {
306                error!("Error: {e:?}");
307                None
308            }
309        }
310    }
311}