open_lark/service/authentication/v1/
auth.rs

1use crate::core::{
2    api_req::ApiRequest,
3    api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
4    config::Config,
5    constants::AccessTokenType,
6    endpoints::auth::*,
7    http::Transport,
8    req_option::RequestOption,
9    standard_response::StandardResponse,
10    trait_system::Service,
11    SDKResult,
12};
13use serde::{Deserialize, Serialize};
14
15pub struct UserInfoService {
16    config: Config,
17}
18
19impl UserInfoService {
20    pub fn new(config: Config) -> Self {
21        Self { config }
22    }
23
24    /// 获取登录用户信息
25    ///
26    /// <https://open.feishu.cn/document/server-docs/authentication-v1/user/get>
27    pub async fn get(&self, user_access_token: impl ToString) -> SDKResult<UserInfo> {
28        let api_req = ApiRequest {
29            api_path: AUTHEN_V1_USER_INFO.to_string(),
30            supported_access_token_types: vec![AccessTokenType::Tenant, AccessTokenType::User],
31            ..Default::default()
32        };
33
34        let option = RequestOption::builder()
35            .user_access_token(user_access_token)
36            .build();
37        let api_resp: BaseResponse<UserInfo> =
38            Transport::request(api_req, &self.config, Some(option)).await?;
39        api_resp.into_result()
40    }
41}
42
43/// 登录用户信息
44#[derive(Debug, Deserialize, Serialize)]
45pub struct UserInfo {
46    /// 用户姓名
47    pub name: String,
48    /// 用户英文名称
49    pub en_name: String,
50    /// 用户头像
51    pub avatar_url: String,
52    /// 用户头像 72x72
53    pub avatar_thumb: String,
54    /// 用户头像 240x240
55    pub avatar_middle: String,
56    /// 用户头像 640x640
57    pub avatar_big: String,
58    /// 用户在应用内的唯一标识
59    pub open_id: String,
60    /// 用户对ISV的唯一标识,对于同一个ISV,用户在其名下所有应用的union_id相同
61    pub union_id: String,
62    /// 用户邮箱
63    pub email: Option<String>,
64    /// 企业邮箱,请先确保已在管理后台启用飞书邮箱服务
65    pub enterprise_email: Option<String>,
66    /// 用户 user_id
67    pub user_id: String,
68    /// 用户手机号
69    pub mobile: Option<String>,
70    /// 当前企业标识
71    pub tenant_key: String,
72    /// 用户工号
73    pub employee_no: String,
74}
75
76impl ApiResponseTrait for UserInfo {
77    fn data_format() -> ResponseFormat {
78        ResponseFormat::Data
79    }
80}
81
82impl Service for UserInfoService {
83    fn config(&self) -> &Config {
84        &self.config
85    }
86
87    fn service_name() -> &'static str {
88        "user_info"
89    }
90
91    fn service_version() -> &'static str {
92        "v1"
93    }
94}
95
96#[cfg(test)]
97#[allow(unused_variables, unused_unsafe)]
98mod tests {
99    use super::*;
100    use crate::core::config::Config;
101
102    #[test]
103    fn test_user_info_deserialization() {
104        let json_str = r#"{
105            "name": "zhangsan",
106            "en_name": "zhangsan",
107            "avatar_url": "www.feishu.cn/avatar/icon",
108            "avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
109            "avatar_middle": "www.feishu.cn/avatar/icon_middle",
110            "avatar_big": "www.feishu.cn/avatar/icon_big",
111            "open_id": "ou-caecc734c2e3328a62489fe0648c4b98779515d3",
112            "union_id": "on-d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj",
113            "email": "zhangsan@feishu.cn",
114            "enterprise_email": "demo@mail.com",
115            "user_id": "5d9bdxxx",
116            "mobile": "+86130002883xx",
117            "tenant_key": "736588c92lxf175d",
118            "employee_no": "111222333"
119        }"#;
120
121        let user_info: UserInfo =
122            serde_json::from_str(json_str).expect("Failed to parse test user info JSON");
123
124        assert_eq!(user_info.name, "zhangsan");
125        assert_eq!(user_info.en_name, "zhangsan");
126        assert_eq!(user_info.avatar_url, "www.feishu.cn/avatar/icon");
127        assert_eq!(user_info.avatar_thumb, "www.feishu.cn/avatar/icon_thumb");
128        assert_eq!(user_info.avatar_middle, "www.feishu.cn/avatar/icon_middle");
129        assert_eq!(user_info.avatar_big, "www.feishu.cn/avatar/icon_big");
130        assert_eq!(
131            user_info.open_id,
132            "ou-caecc734c2e3328a62489fe0648c4b98779515d3"
133        );
134        assert_eq!(
135            user_info.union_id,
136            "on-d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj"
137        );
138        assert_eq!(user_info.email, Some("zhangsan@feishu.cn".to_string()));
139        assert_eq!(
140            user_info.enterprise_email,
141            Some("demo@mail.com".to_string())
142        );
143        assert_eq!(user_info.user_id, "5d9bdxxx");
144        assert_eq!(user_info.mobile, Some("+86130002883xx".to_string()));
145        assert_eq!(user_info.tenant_key, "736588c92lxf175d");
146        assert_eq!(user_info.employee_no, "111222333");
147    }
148
149    #[test]
150    fn test_user_info_optional_fields() {
151        let json_str = r#"{
152            "name": "testuser",
153            "en_name": "testuser",
154            "avatar_url": "www.feishu.cn/avatar/icon",
155            "avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
156            "avatar_middle": "www.feishu.cn/avatar/icon_middle",
157            "avatar_big": "www.feishu.cn/avatar/icon_big",
158            "open_id": "ou-test123456789",
159            "union_id": "on-test123456789",
160            "user_id": "test123",
161            "tenant_key": "test_tenant",
162            "employee_no": "EMP001"
163        }"#;
164
165        let user_info: UserInfo = serde_json::from_str(json_str).unwrap();
166
167        assert_eq!(user_info.name, "testuser");
168        assert_eq!(user_info.user_id, "test123");
169        assert!(user_info.email.is_none());
170        assert!(user_info.enterprise_email.is_none());
171        assert!(user_info.mobile.is_none());
172    }
173
174    #[test]
175    fn test_user_info_service_new() {
176        let config = Config::default();
177        let service = UserInfoService::new(config.clone());
178
179        // Check that the service was created with the provided config
180        assert_eq!(service.config.base_url, config.base_url);
181        assert_eq!(service.config.app_id, config.app_id);
182        assert_eq!(service.config.app_secret, config.app_secret);
183    }
184
185    #[test]
186    fn test_user_info_api_response_trait() {
187        // Test that the data_format method exists and returns the expected type
188        let format = UserInfo::data_format();
189        // We can't compare directly, but we can check that the method exists
190        // This tests that the ApiResponseTrait is properly implemented
191        assert!(matches!(format, ResponseFormat::Data));
192    }
193
194    #[test]
195    fn test_user_info_debug_trait() {
196        let user_info = UserInfo {
197            name: "test".to_string(),
198            en_name: "test".to_string(),
199            avatar_url: "url".to_string(),
200            avatar_thumb: "thumb".to_string(),
201            avatar_middle: "middle".to_string(),
202            avatar_big: "big".to_string(),
203            open_id: "open_id".to_string(),
204            union_id: "union_id".to_string(),
205            email: Some("test@example.com".to_string()),
206            enterprise_email: None,
207            user_id: "user_id".to_string(),
208            mobile: None,
209            tenant_key: "tenant".to_string(),
210            employee_no: "emp001".to_string(),
211        };
212
213        let debug_str = format!("{:?}", user_info);
214        assert!(debug_str.contains("test"));
215        assert!(debug_str.contains("UserInfo"));
216    }
217
218    #[test]
219    fn test_user_info_serde_round_trip() {
220        let original = UserInfo {
221            name: "test user".to_string(),
222            en_name: "test_user".to_string(),
223            avatar_url: "https://example.com/avatar.jpg".to_string(),
224            avatar_thumb: "https://example.com/avatar_thumb.jpg".to_string(),
225            avatar_middle: "https://example.com/avatar_middle.jpg".to_string(),
226            avatar_big: "https://example.com/avatar_big.jpg".to_string(),
227            open_id: "ou-12345".to_string(),
228            union_id: "on-67890".to_string(),
229            email: Some("test@company.com".to_string()),
230            enterprise_email: Some("test@enterprise.com".to_string()),
231            user_id: "u12345".to_string(),
232            mobile: Some("+1234567890".to_string()),
233            tenant_key: "tenant123".to_string(),
234            employee_no: "E12345".to_string(),
235        };
236
237        // Serialize to JSON
238        let json = serde_json::to_string(&original).unwrap();
239
240        // Deserialize back
241        let deserialized: UserInfo = serde_json::from_str(&json).unwrap();
242
243        assert_eq!(original.name, deserialized.name);
244        assert_eq!(original.en_name, deserialized.en_name);
245        assert_eq!(original.avatar_url, deserialized.avatar_url);
246        assert_eq!(original.open_id, deserialized.open_id);
247        assert_eq!(original.union_id, deserialized.union_id);
248        assert_eq!(original.email, deserialized.email);
249        assert_eq!(original.enterprise_email, deserialized.enterprise_email);
250        assert_eq!(original.user_id, deserialized.user_id);
251        assert_eq!(original.mobile, deserialized.mobile);
252        assert_eq!(original.tenant_key, deserialized.tenant_key);
253        assert_eq!(original.employee_no, deserialized.employee_no);
254    }
255
256    #[test]
257    fn test_user_info_with_unicode_characters() {
258        let json_str = r#"{
259            "name": "张三",
260            "en_name": "zhangsan",
261            "avatar_url": "www.feishu.cn/avatar/icon",
262            "avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
263            "avatar_middle": "www.feishu.cn/avatar/icon_middle",
264            "avatar_big": "www.feishu.cn/avatar/icon_big",
265            "open_id": "ou-test",
266            "union_id": "on-test",
267            "email": "张三@公司.com",
268            "user_id": "user123",
269            "tenant_key": "tenant",
270            "employee_no": "工号001"
271        }"#;
272
273        let user_info: UserInfo = serde_json::from_str(json_str).unwrap();
274
275        assert_eq!(user_info.name, "张三");
276        assert_eq!(user_info.email, Some("张三@公司.com".to_string()));
277        assert_eq!(user_info.employee_no, "工号001");
278    }
279
280    #[test]
281    fn test_user_info_invalid_json() {
282        let invalid_json = r#"{
283            "name": "test",
284            "invalid_field": "should_not_cause_error"
285        }"#;
286
287        // This should fail because required fields are missing
288        let result = serde_json::from_str::<UserInfo>(invalid_json);
289        assert!(result.is_err());
290    }
291
292    #[test]
293    fn test_user_info_empty_string_fields() {
294        let json_str = r#"{
295            "name": "",
296            "en_name": "",
297            "avatar_url": "",
298            "avatar_thumb": "",
299            "avatar_middle": "",
300            "avatar_big": "",
301            "open_id": "",
302            "union_id": "",
303            "user_id": "",
304            "tenant_key": "",
305            "employee_no": ""
306        }"#;
307
308        let user_info: UserInfo = serde_json::from_str(json_str).unwrap();
309
310        assert_eq!(user_info.name, "");
311        assert_eq!(user_info.en_name, "");
312        assert_eq!(user_info.open_id, "");
313        assert!(user_info.email.is_none());
314        assert!(user_info.mobile.is_none());
315    }
316
317    #[test]
318    fn test_user_info_service_config_independence() {
319        let config1 = Config::builder()
320            .app_id("app1")
321            .app_secret("secret1")
322            .build();
323        let config2 = Config::builder()
324            .app_id("app2")
325            .app_secret("secret2")
326            .build();
327
328        let service1 = UserInfoService::new(config1);
329        let service2 = UserInfoService::new(config2);
330
331        assert_eq!(service1.config.app_id, "app1");
332        assert_eq!(service2.config.app_id, "app2");
333        assert_ne!(service1.config.app_id, service2.config.app_id);
334    }
335}