Skip to main content

openlark_security/security/acs/v1/users/
mod.rs

1//! 门禁用户管理 API
2//!
3//! 提供用户信息的增删改查功能。
4
5use openlark_core::error::api_error;
6use std::sync::Arc;
7
8/// 用户管理服务
9#[derive(Debug)]
10pub struct UsersService {
11    config: Arc<crate::models::SecurityConfig>,
12}
13
14impl UsersService {
15    /// 创建新的用户管理服务实例
16    pub fn new(config: Arc<crate::models::SecurityConfig>) -> Self {
17        Self { config }
18    }
19
20    /// 获取单个用户信息
21    pub fn get(&self) -> GetUserBuilder {
22        GetUserBuilder {
23            config: self.config.clone(),
24            user_id: String::new(),
25        }
26    }
27
28    /// 获取用户列表
29    pub fn list(&self) -> ListUsersBuilder {
30        ListUsersBuilder {
31            config: self.config.clone(),
32            page_size: Some(20),
33            page_token: None,
34            department_id: None,
35            status: None,
36        }
37    }
38
39    /// 修改用户部分信息
40    pub fn patch(&self) -> PatchUserBuilder {
41        PatchUserBuilder {
42            config: self.config.clone(),
43            user_id: String::new(),
44            name: None,
45            email: None,
46            mobile: None,
47            department_ids: None,
48            status: None,
49            rule_ids: None,
50        }
51    }
52}
53
54/// 获取单个用户信息构建器
55#[derive(Debug)]
56pub struct GetUserBuilder {
57    config: Arc<crate::models::SecurityConfig>,
58    user_id: String,
59}
60
61impl GetUserBuilder {
62    /// 设置用户ID
63    pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
64        self.user_id = user_id.into();
65        self
66    }
67
68    /// 发送请求获取用户信息
69    pub async fn send(self) -> crate::SecurityResult<crate::models::acs::UserInfo> {
70        let url = format!(
71            "{}/open-apis/acs/v1/users/{}",
72            self.config.base_url, self.user_id
73        );
74
75        let response = reqwest::Client::new()
76            .get(&url)
77            .header(
78                "Authorization",
79                format!("Bearer {}", get_app_token(&self.config).await?),
80            )
81            .header("Content-Type", "application/json")
82            .send()
83            .await?;
84
85        if response.status().is_success() {
86            let api_response: crate::models::ApiResponse<crate::models::acs::UserInfo> =
87                response.json().await?;
88            match api_response.data {
89                Some(user) => Ok(user),
90                None => Err(api_error(
91                    api_response.code as u16,
92                    "/acs/v1/users",
93                    &api_response.msg,
94                    None,
95                )),
96            }
97        } else {
98            Err(api_error(
99                response.status().as_u16(),
100                "/acs/v1/users",
101                format!("HTTP: {}", response.status()),
102                None,
103            ))
104        }
105    }
106}
107
108/// 获取用户列表构建器
109#[derive(Debug)]
110pub struct ListUsersBuilder {
111    config: Arc<crate::models::SecurityConfig>,
112    page_size: Option<i32>,
113    page_token: Option<String>,
114    department_id: Option<String>,
115    status: Option<crate::models::Status>,
116}
117
118impl ListUsersBuilder {
119    /// 设置页面大小
120    pub fn page_size(mut self, page_size: i32) -> Self {
121        self.page_size = Some(page_size);
122        self
123    }
124
125    /// 设置分页标记
126    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
127        self.page_token = Some(page_token.into());
128        self
129    }
130
131    /// 设置部门ID过滤
132    pub fn department_id(mut self, department_id: impl Into<String>) -> Self {
133        self.department_id = Some(department_id.into());
134        self
135    }
136
137    /// 设置状态过滤
138    pub fn status(mut self, status: crate::models::Status) -> Self {
139        self.status = Some(status);
140        self
141    }
142
143    /// 发送请求获取用户列表
144    pub async fn send(self) -> crate::SecurityResult<crate::models::acs::UserListResponse> {
145        let mut url = format!("{}/open-apis/acs/v1/users", self.config.base_url);
146        let mut query_params = Vec::new();
147
148        if let Some(page_size) = self.page_size {
149            query_params.push(format!("page_size={page_size}"));
150        }
151        if let Some(page_token) = &self.page_token {
152            query_params.push(format!("page_token={page_token}"));
153        }
154        if let Some(department_id) = &self.department_id {
155            query_params.push(format!("department_id={department_id}"));
156        }
157        if let Some(status) = &self.status {
158            query_params.push(format!(
159                "status={}",
160                serde_json::to_string(status).unwrap_or_default()
161            ));
162        }
163
164        if !query_params.is_empty() {
165            url.push_str(&format!("?{}", query_params.join("&")));
166        }
167
168        let response = reqwest::Client::new()
169            .get(&url)
170            .header(
171                "Authorization",
172                format!("Bearer {}", get_app_token(&self.config).await?),
173            )
174            .header("Content-Type", "application/json")
175            .send()
176            .await?;
177
178        if response.status().is_success() {
179            let api_response: crate::models::ApiResponse<crate::models::acs::UserListResponse> =
180                response.json().await?;
181            match api_response.data {
182                Some(users) => Ok(users),
183                None => Err(api_error(
184                    api_response.code as u16,
185                    "/acs/v1/users",
186                    &api_response.msg,
187                    None,
188                )),
189            }
190        } else {
191            Err(api_error(
192                response.status().as_u16(),
193                "/acs/v1/users",
194                format!("HTTP: {}", response.status()),
195                None,
196            ))
197        }
198    }
199}
200
201/// 修改用户信息构建器
202#[derive(Debug)]
203pub struct PatchUserBuilder {
204    config: Arc<crate::models::SecurityConfig>,
205    user_id: String,
206    name: Option<String>,
207    email: Option<String>,
208    mobile: Option<String>,
209    department_ids: Option<Vec<String>>,
210    status: Option<crate::models::Status>,
211    rule_ids: Option<Vec<String>>,
212}
213
214impl PatchUserBuilder {
215    /// 设置用户ID
216    pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
217        self.user_id = user_id.into();
218        self
219    }
220
221    /// 设置用户姓名
222    pub fn name(mut self, name: impl Into<String>) -> Self {
223        self.name = Some(name.into());
224        self
225    }
226
227    /// 设置用户邮箱
228    pub fn email(mut self, email: impl Into<String>) -> Self {
229        self.email = Some(email.into());
230        self
231    }
232
233    /// 设置用户手机号
234    pub fn mobile(mut self, mobile: impl Into<String>) -> Self {
235        self.mobile = Some(mobile.into());
236        self
237    }
238
239    /// 设置部门ID列表
240    pub fn department_ids(mut self, department_ids: Vec<String>) -> Self {
241        self.department_ids = Some(department_ids);
242        self
243    }
244
245    /// 设置用户状态
246    pub fn status(mut self, status: crate::models::Status) -> Self {
247        self.status = Some(status);
248        self
249    }
250
251    /// 设置权限组ID列表
252    pub fn rule_ids(mut self, rule_ids: Vec<String>) -> Self {
253        self.rule_ids = Some(rule_ids);
254        self
255    }
256
257    /// 发送请求修改用户信息
258    pub async fn send(self) -> crate::SecurityResult<crate::models::acs::UserInfo> {
259        let url = format!(
260            "{}/open-apis/acs/v1/users/{}",
261            self.config.base_url, self.user_id
262        );
263
264        let mut request_body = serde_json::Map::new();
265
266        if let Some(name) = self.name {
267            request_body.insert("name".to_string(), serde_json::Value::String(name));
268        }
269        if let Some(email) = self.email {
270            request_body.insert("email".to_string(), serde_json::Value::String(email));
271        }
272        if let Some(mobile) = self.mobile {
273            request_body.insert("mobile".to_string(), serde_json::Value::String(mobile));
274        }
275        if let Some(department_ids) = self.department_ids {
276            request_body.insert(
277                "department_ids".to_string(),
278                serde_json::Value::Array(
279                    department_ids
280                        .into_iter()
281                        .map(serde_json::Value::String)
282                        .collect(),
283                ),
284            );
285        }
286        if let Some(status) = self.status {
287            request_body.insert(
288                "status".to_string(),
289                serde_json::to_value(status).unwrap_or(serde_json::Value::Null),
290            );
291        }
292        if let Some(rule_ids) = self.rule_ids {
293            request_body.insert(
294                "rule_ids".to_string(),
295                serde_json::Value::Array(
296                    rule_ids
297                        .into_iter()
298                        .map(serde_json::Value::String)
299                        .collect(),
300                ),
301            );
302        }
303
304        let response = reqwest::Client::new()
305            .patch(&url)
306            .header(
307                "Authorization",
308                format!("Bearer {}", get_app_token(&self.config).await?),
309            )
310            .header("Content-Type", "application/json")
311            .json(&request_body)
312            .send()
313            .await?;
314
315        if response.status().is_success() {
316            let api_response: crate::models::ApiResponse<crate::models::acs::UserInfo> =
317                response.json().await?;
318            match api_response.data {
319                Some(user) => Ok(user),
320                None => Err(api_error(
321                    api_response.code as u16,
322                    "/acs/v1/users",
323                    &api_response.msg,
324                    None,
325                )),
326            }
327        } else {
328            Err(api_error(
329                response.status().as_u16(),
330                "/acs/v1/users",
331                format!("HTTP: {}", response.status()),
332                None,
333            ))
334        }
335    }
336}
337
338/// 获取应用访问令牌的辅助函数
339async fn get_app_token(config: &crate::models::SecurityConfig) -> crate::SecurityResult<String> {
340    // 使用 SecurityConfig 的 get_app_access_token 方法获取真实的 token
341    config.get_app_access_token().await
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347    use std::sync::Arc;
348
349    fn create_test_config() -> Arc<crate::models::SecurityConfig> {
350        Arc::new(crate::models::SecurityConfig {
351            app_id: "test_app_id".to_string(),
352            app_secret: "test_app_secret".to_string(),
353            base_url: "https://open.feishu.cn".to_string(),
354        })
355    }
356
357    #[test]
358    fn test_users_service_creation() {
359        let config = create_test_config();
360        let service = UsersService::new(config.clone());
361        assert_eq!(service.config.app_id, "test_app_id");
362    }
363
364    #[test]
365    fn test_get_user_builder() {
366        let config = create_test_config();
367        let service = UsersService::new(config);
368        let builder = service.get().user_id("user_123");
369        assert_eq!(builder.user_id, "user_123");
370    }
371
372    #[test]
373    fn test_list_users_builder_defaults() {
374        let config = create_test_config();
375        let service = UsersService::new(config);
376        let builder = service.list();
377        assert_eq!(builder.page_size, Some(20));
378        assert_eq!(builder.page_token, None);
379        assert_eq!(builder.department_id, None);
380        assert_eq!(builder.status, None);
381    }
382
383    #[test]
384    fn test_list_users_builder_with_params() {
385        let config = create_test_config();
386        let service = UsersService::new(config);
387        let builder = service
388            .list()
389            .page_size(50)
390            .page_token("token_123")
391            .department_id("dept_456")
392            .status(crate::models::Status::Active);
393
394        assert_eq!(builder.page_size, Some(50));
395        assert_eq!(builder.page_token, Some("token_123".to_string()));
396        assert_eq!(builder.department_id, Some("dept_456".to_string()));
397        assert!(builder.status.is_some());
398    }
399
400    #[test]
401    fn test_patch_user_builder() {
402        let config = create_test_config();
403        let service = UsersService::new(config);
404        let builder = service
405            .patch()
406            .user_id("user_789")
407            .name("张三")
408            .email("zhangsan@example.com")
409            .mobile("13800138000")
410            .department_ids(vec!["dept_1".to_string(), "dept_2".to_string()])
411            .status(crate::models::Status::Active)
412            .rule_ids(vec!["rule_1".to_string()]);
413
414        assert_eq!(builder.user_id, "user_789");
415        assert_eq!(builder.name, Some("张三".to_string()));
416        assert_eq!(builder.email, Some("zhangsan@example.com".to_string()));
417        assert_eq!(builder.mobile, Some("13800138000".to_string()));
418        assert_eq!(
419            builder.department_ids,
420            Some(vec!["dept_1".to_string(), "dept_2".to_string()])
421        );
422        assert!(builder.status.is_some());
423        assert_eq!(builder.rule_ids, Some(vec!["rule_1".to_string()]));
424    }
425
426    #[test]
427    fn test_patch_user_builder_chaining() {
428        let config = create_test_config();
429        let service = UsersService::new(config);
430        let builder = service
431            .patch()
432            .user_id("user_123")
433            .name("李四")
434            .email("lisi@example.com");
435
436        assert_eq!(builder.user_id, "user_123");
437        assert_eq!(builder.name, Some("李四".to_string()));
438        assert_eq!(builder.email, Some("lisi@example.com".to_string()));
439        assert!(builder.mobile.is_none()); // 未设置的字段应保持 None
440    }
441}