open_lark/service/cloud_docs/permission/public_v1/password/
create.rs1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::core::{
5 api_req::ApiRequest,
6 api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
7 config::Config,
8 constants::AccessTokenType,
9 endpoints::{cloud_docs::*, EndpointBuilder},
10 http::Transport,
11 query_params::QueryParams,
12 req_option::RequestOption,
13 SDKResult,
14};
15
16#[derive(Debug, Serialize, Default, Clone)]
18pub struct CreatePasswordRequest {
19 #[serde(skip)]
20 api_request: ApiRequest,
21 #[serde(skip)]
23 token: String,
24 #[serde(skip)]
26 obj_type: String,
27 password: String,
29}
30
31impl CreatePasswordRequest {
32 pub fn builder() -> CreatePasswordRequestBuilder {
33 CreatePasswordRequestBuilder::default()
34 }
35
36 pub fn new(token: impl ToString, obj_type: impl ToString, password: impl ToString) -> Self {
37 Self {
38 token: token.to_string(),
39 obj_type: obj_type.to_string(),
40 password: password.to_string(),
41 ..Default::default()
42 }
43 }
44
45 pub fn for_doc(token: impl ToString, password: impl ToString) -> Self {
47 Self::new(token, "doc", password)
48 }
49
50 pub fn for_sheet(token: impl ToString, password: impl ToString) -> Self {
52 Self::new(token, "sheet", password)
53 }
54
55 pub fn for_bitable(token: impl ToString, password: impl ToString) -> Self {
57 Self::new(token, "bitable", password)
58 }
59
60 pub fn for_wiki(token: impl ToString, password: impl ToString) -> Self {
62 Self::new(token, "wiki", password)
63 }
64}
65
66#[derive(Default)]
67pub struct CreatePasswordRequestBuilder {
68 request: CreatePasswordRequest,
69}
70
71impl CreatePasswordRequestBuilder {
72 pub fn token(mut self, token: impl ToString) -> Self {
74 self.request.token = token.to_string();
75 self
76 }
77
78 pub fn obj_type(mut self, obj_type: impl ToString) -> Self {
80 self.request.obj_type = obj_type.to_string();
81 self
82 }
83
84 pub fn as_doc(mut self) -> Self {
86 self.request.obj_type = "doc".to_string();
87 self
88 }
89
90 pub fn as_sheet(mut self) -> Self {
92 self.request.obj_type = "sheet".to_string();
93 self
94 }
95
96 pub fn as_bitable(mut self) -> Self {
98 self.request.obj_type = "bitable".to_string();
99 self
100 }
101
102 pub fn as_wiki(mut self) -> Self {
104 self.request.obj_type = "wiki".to_string();
105 self
106 }
107
108 pub fn password(mut self, password: impl ToString) -> Self {
110 self.request.password = password.to_string();
111 self
112 }
113
114 pub fn simple_password(mut self, digits: u32) -> Self {
116 self.request.password = format!("{:06}", digits % 1000000);
117 self
118 }
119
120 pub fn random_password(mut self, length: usize) -> Self {
122 use rand::{distributions::Alphanumeric, thread_rng, Rng};
123
124 let password: String = thread_rng()
125 .sample_iter(&Alphanumeric)
126 .take(length.clamp(6, 32))
127 .map(char::from)
128 .collect();
129
130 self.request.password = password;
131 self
132 }
133
134 pub fn build(mut self) -> CreatePasswordRequest {
135 self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
136 self.request
137 }
138}
139
140#[derive(Debug, Deserialize)]
142pub struct PasswordResult {
143 pub password: String,
145 pub create_time: Option<i64>,
147 pub expire_time: Option<i64>,
149}
150
151#[derive(Debug, Deserialize)]
153pub struct CreatePasswordResponse {
154 pub password: PasswordResult,
156}
157
158impl ApiResponseTrait for CreatePasswordResponse {
159 fn data_format() -> ResponseFormat {
160 ResponseFormat::Data
161 }
162}
163
164pub async fn create_password(
166 request: CreatePasswordRequest,
167 config: &Config,
168 option: Option<RequestOption>,
169) -> SDKResult<BaseResponse<CreatePasswordResponse>> {
170 let mut api_req = request.api_request;
171 api_req.http_method = Method::POST;
172 api_req.api_path = EndpointBuilder::replace_param(
173 DRIVE_V1_PERMISSIONS_PUBLIC_PASSWORD,
174 "token",
175 &request.token,
176 );
177
178 api_req
180 .query_params
181 .insert(QueryParams::TYPE, request.obj_type);
182
183 api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
184
185 let api_resp = Transport::request(api_req, config, option).await?;
186 Ok(api_resp)
187}
188
189impl PasswordResult {
190 pub fn has_create_time(&self) -> bool {
192 self.create_time.is_some()
193 }
194
195 pub fn has_expire_time(&self) -> bool {
197 self.expire_time.is_some()
198 }
199
200 pub fn create_time_formatted(&self) -> Option<String> {
202 self.create_time
203 .map(|timestamp| format!("创建时间: {timestamp}"))
204 }
205
206 pub fn expire_time_formatted(&self) -> Option<String> {
208 self.expire_time
209 .map(|timestamp| format!("过期时间: {timestamp}"))
210 }
211
212 pub fn password_strength(&self) -> &'static str {
214 let password = &self.password;
215 let length = password.len();
216
217 if length < 6 {
218 "弱"
219 } else if length < 8 {
220 "中等"
221 } else if password.chars().any(|c| c.is_ascii_digit())
222 && password.chars().any(|c| c.is_ascii_alphabetic())
223 {
224 "强"
225 } else {
226 "中等"
227 }
228 }
229
230 pub fn is_numeric_password(&self) -> bool {
232 self.password.chars().all(|c| c.is_ascii_digit())
233 }
234
235 pub fn password_length(&self) -> usize {
237 self.password.len()
238 }
239
240 pub fn password_summary(&self) -> String {
242 format!(
243 "密码长度: {}, 强度: {}, 类型: {}",
244 self.password_length(),
245 self.password_strength(),
246 if self.is_numeric_password() {
247 "纯数字"
248 } else {
249 "混合字符"
250 }
251 )
252 }
253}
254
255impl CreatePasswordResponse {
256 pub fn password_info(&self) -> &PasswordResult {
258 &self.password
259 }
260
261 pub fn password_value(&self) -> &str {
263 &self.password.password
264 }
265
266 pub fn is_created(&self) -> bool {
268 !self.password.password.is_empty()
269 }
270
271 pub fn creation_summary(&self) -> String {
273 if self.is_created() {
274 format!("密码保护已开启 - {}", self.password.password_summary())
275 } else {
276 "密码保护开启失败".to_string()
277 }
278 }
279
280 pub fn security_tips(&self) -> Vec<String> {
282 let mut tips = Vec::new();
283
284 if self.password.is_numeric_password() {
285 tips.push("建议使用包含字母和数字的混合密码".to_string());
286 }
287
288 if self.password.password_length() < 8 {
289 tips.push("建议使用8位以上的密码".to_string());
290 }
291
292 tips.push("请妥善保管密码,遗失后需要重新设置".to_string());
293
294 if self.password.has_expire_time() {
295 tips.push("密码有过期时间,请注意及时更新".to_string());
296 }
297
298 tips
299 }
300
301 pub fn operation_recommendations(&self) -> Vec<String> {
303 let mut recommendations = Vec::new();
304
305 recommendations.push("建议定期更换密码".to_string());
306 recommendations.push("不要在不安全的环境中输入密码".to_string());
307 recommendations.push("可以设置更复杂的密码提高安全性".to_string());
308
309 if self.password.password_strength() == "弱" {
310 recommendations.push("当前密码强度较弱,建议立即更换".to_string());
311 }
312
313 recommendations
314 }
315}
316
317#[cfg(test)]
318#[allow(unused_variables, unused_unsafe)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_create_password_request_builder() {
324 let request = CreatePasswordRequest::builder()
325 .token("doccnxxxxxx")
326 .as_doc()
327 .password("123456")
328 .build();
329
330 assert_eq!(request.token, "doccnxxxxxx");
331 assert_eq!(request.obj_type, "doc");
332 assert_eq!(request.password, "123456");
333 }
334
335 #[test]
336 fn test_convenience_methods() {
337 let request = CreatePasswordRequest::for_doc("doccnxxxxxx", "password123");
338 assert_eq!(request.obj_type, "doc");
339 assert_eq!(request.password, "password123");
340
341 let request = CreatePasswordRequest::for_sheet("shtcnxxxxxx", "sheet456");
342 assert_eq!(request.obj_type, "sheet");
343 assert_eq!(request.password, "sheet456");
344 }
345
346 #[test]
347 fn test_password_builder_methods() {
348 let request = CreatePasswordRequest::builder()
349 .token("doccnxxxxxx")
350 .as_doc()
351 .simple_password(123456)
352 .build();
353
354 assert_eq!(request.password, "123456");
355 }
356
357 #[test]
358 fn test_password_result_methods() {
359 let result = PasswordResult {
360 password: "password123".to_string(),
361 create_time: Some(1234567890),
362 expire_time: None,
363 };
364
365 assert!(result.has_create_time());
366 assert!(!result.has_expire_time());
367 assert!(!result.is_numeric_password());
368 assert_eq!(result.password_length(), 11);
369 assert_eq!(result.password_strength(), "强");
370
371 let numeric_result = PasswordResult {
372 password: "123456".to_string(),
373 create_time: None,
374 expire_time: None,
375 };
376
377 assert!(numeric_result.is_numeric_password());
378 assert_eq!(numeric_result.password_strength(), "中等");
379 }
380}