1use crate::internal::request::urlencoding;
8use crate::pagination::{ListOptions, QueryEncode};
9use crate::types::enums::AccessTokenScope;
10use crate::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Default)]
13pub struct ListEmailsOptions {
15 pub list_options: ListOptions,
16}
17
18impl QueryEncode for ListEmailsOptions {
19 fn query_encode(&self) -> String {
20 self.list_options.query_encode()
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CreateEmailOption {
27 pub emails: Vec<String>,
29}
30
31impl CreateEmailOption {
32 pub fn validate(&self) -> crate::Result<()> {
34 if self.emails.is_empty() {
35 return Err(crate::Error::Validation(
36 "at least one email is required".to_string(),
37 ));
38 }
39 for email in &self.emails {
40 if email.is_empty() {
41 return Err(crate::Error::Validation(
42 "email addresses must not be empty".to_string(),
43 ));
44 }
45 }
46 Ok(())
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct DeleteEmailOption {
53 pub emails: Vec<String>,
55}
56
57impl DeleteEmailOption {
58 pub fn validate(&self) -> crate::Result<()> {
60 if self.emails.is_empty() {
61 return Err(crate::Error::Validation(
62 "at least one email is required".to_string(),
63 ));
64 }
65 for email in &self.emails {
66 if email.is_empty() {
67 return Err(crate::Error::Validation(
68 "email addresses must not be empty".to_string(),
69 ));
70 }
71 }
72 Ok(())
73 }
74}
75
76#[derive(Debug, Clone, Default)]
77pub struct ListPublicKeysOptions {
79 pub list_options: ListOptions,
80}
81
82impl QueryEncode for ListPublicKeysOptions {
83 fn query_encode(&self) -> String {
84 self.list_options.query_encode()
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct CreateKeyOption {
91 pub title: String,
93 pub key: String,
95 #[serde(default)]
97 pub read_only: bool,
98}
99
100impl CreateKeyOption {
101 pub fn validate(&self) -> crate::Result<()> {
103 if self.key.is_empty() {
104 return Err(crate::Error::Validation("key is required".to_string()));
105 }
106 if self.title.is_empty() {
107 return Err(crate::Error::Validation("title is required".to_string()));
108 }
109 Ok(())
110 }
111}
112
113#[derive(Debug, Clone, Default)]
114pub struct ListFollowersOptions {
116 pub list_options: ListOptions,
117}
118
119impl QueryEncode for ListFollowersOptions {
120 fn query_encode(&self) -> String {
121 self.list_options.query_encode()
122 }
123}
124
125#[derive(Debug, Clone, Default)]
126pub struct ListFollowingOptions {
128 pub list_options: ListOptions,
129}
130
131impl QueryEncode for ListFollowingOptions {
132 fn query_encode(&self) -> String {
133 self.list_options.query_encode()
134 }
135}
136
137#[derive(Debug, Clone, Default)]
138pub struct ListAccessTokensOptions {
140 pub list_options: ListOptions,
141}
142
143impl QueryEncode for ListAccessTokensOptions {
144 fn query_encode(&self) -> String {
145 self.list_options.query_encode()
146 }
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct CreateAccessTokenOption {
152 pub name: String,
153 #[serde(default)]
154 pub scopes: Vec<AccessTokenScope>,
155}
156
157impl CreateAccessTokenOption {
158 pub fn validate(&self) -> crate::Result<()> {
160 if self.name.is_empty() {
161 return Err(crate::Error::Validation("name is required".to_string()));
162 }
163 Ok(())
164 }
165}
166
167#[derive(Debug, Clone, Default, Serialize, Deserialize)]
168pub struct UserSettingsOptions {
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub full_name: Option<String>,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub website: Option<String>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub description: Option<String>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub location: Option<String>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub language: Option<String>,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 pub theme: Option<String>,
182 #[serde(skip_serializing_if = "Option::is_none", rename = "diff_view_style")]
183 pub diff_view_style: Option<String>,
184 #[serde(skip_serializing_if = "Option::is_none", rename = "hide_email")]
185 pub hide_email: Option<bool>,
186 #[serde(skip_serializing_if = "Option::is_none", rename = "hide_activity")]
187 pub hide_activity: Option<bool>,
188}
189
190#[derive(Debug, Clone, Default)]
191pub struct ListUserBlocksOptions {
193 pub list_options: ListOptions,
194}
195
196impl QueryEncode for ListUserBlocksOptions {
197 fn query_encode(&self) -> String {
198 self.list_options.query_encode()
199 }
200}
201
202#[derive(Debug, Clone, Default)]
203pub struct SearchUsersOption {
205 pub list_options: ListOptions,
206 pub key_word: String,
207 pub uid: i64,
208}
209
210impl QueryEncode for SearchUsersOption {
211 fn query_encode(&self) -> String {
212 let mut out = String::new();
213 let defaulted = self.list_options.with_defaults();
214 if defaulted.page == Some(0) {
215 out.push_str("page=0&limit=0");
216 } else if let Some(page) = defaulted.page {
217 out.push_str(&format!("page={page}"));
218 if let Some(size) = defaulted.page_size {
219 out.push_str(&format!("&limit={size}"));
220 }
221 }
222 if !self.key_word.is_empty() {
223 out.push_str(&format!("&q={}", urlencoding(&self.key_word)));
224 }
225 if self.uid > 0 {
226 out.push_str(&format!("&uid={}", self.uid));
227 }
228 out
229 }
230}
231
232#[derive(Debug, Clone, Default)]
233pub struct ListUserActivityFeedsOptions {
235 pub list_options: ListOptions,
236 pub only_performed_by: bool,
237 pub date: String,
238}
239
240impl QueryEncode for ListUserActivityFeedsOptions {
241 fn query_encode(&self) -> String {
242 let mut query = self.list_options.query_encode();
243 if self.only_performed_by {
244 query.push_str("&only-performed-by=true");
245 }
246 if !self.date.is_empty() {
247 query.push_str("&date=");
248 query.push_str(&urlencoding(&self.date));
249 }
250 query
251 }
252}
253
254#[derive(Debug, Clone, Default)]
255pub struct ListGPGKeysOptions {
257 pub list_options: ListOptions,
258}
259
260impl QueryEncode for ListGPGKeysOptions {
261 fn query_encode(&self) -> String {
262 self.list_options.query_encode()
263 }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct CreateGPGKeyOption {
269 #[serde(rename = "armored_public_key")]
271 pub armored_key: String,
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub signature: Option<String>,
275}
276
277impl CreateGPGKeyOption {
278 pub fn validate(&self) -> crate::Result<()> {
280 if self.armored_key.is_empty() {
281 return Err(crate::Error::Validation(
282 "armored_public_key is required".to_string(),
283 ));
284 }
285 Ok(())
286 }
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct VerifyGPGKeyOption {
292 #[serde(rename = "key_id")]
293 pub key_id: String,
294 #[serde(rename = "armored_signature")]
295 pub signature: String,
296}
297
298impl VerifyGPGKeyOption {
299 pub fn validate(&self) -> crate::Result<()> {
301 if self.key_id.is_empty() {
302 return Err(crate::Error::Validation("key_id is required".to_string()));
303 }
304 if self.signature.is_empty() {
305 return Err(crate::Error::Validation(
306 "armored_signature is required".to_string(),
307 ));
308 }
309 Ok(())
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct UpdateUserAvatarOption {
316 pub image: String,
318}
319
320impl UpdateUserAvatarOption {
321 pub fn validate(&self) -> crate::Result<()> {
323 if self.image.is_empty() {
324 return Err(crate::Error::Validation("image is required".to_string()));
325 }
326 Ok(())
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_create_email_option_validate_success() {
336 let opt = CreateEmailOption {
337 emails: vec!["user@example.com".to_string()],
338 };
339 assert!(opt.validate().is_ok());
340 }
341
342 #[test]
343 fn test_create_email_option_validate_empty_list() {
344 let opt = CreateEmailOption { emails: vec![] };
345 assert!(opt.validate().is_err());
346 }
347
348 #[test]
349 fn test_create_email_option_validate_empty_email() {
350 let opt = CreateEmailOption {
351 emails: vec![String::new()],
352 };
353 assert!(opt.validate().is_err());
354 }
355
356 #[test]
357 fn test_delete_email_option_validate_success() {
358 let opt = DeleteEmailOption {
359 emails: vec!["user@example.com".to_string()],
360 };
361 assert!(opt.validate().is_ok());
362 }
363
364 #[test]
365 fn test_delete_email_option_validate_empty_list() {
366 let opt = DeleteEmailOption { emails: vec![] };
367 assert!(opt.validate().is_err());
368 }
369
370 #[test]
371 fn test_delete_email_option_validate_empty_email() {
372 let opt = DeleteEmailOption {
373 emails: vec![String::new()],
374 };
375 assert!(opt.validate().is_err());
376 }
377
378 #[test]
379 fn test_create_key_option_validate_success() {
380 let opt = CreateKeyOption {
381 title: "my-key".to_string(),
382 key: "ssh-rsa AAAAB3...".to_string(),
383 read_only: false,
384 };
385 assert!(opt.validate().is_ok());
386 }
387
388 #[test]
389 fn test_create_key_option_validate_empty_key() {
390 let opt = CreateKeyOption {
391 title: "my-key".to_string(),
392 key: String::new(),
393 read_only: false,
394 };
395 assert!(opt.validate().is_err());
396 }
397
398 #[test]
399 fn test_create_key_option_validate_empty_title() {
400 let opt = CreateKeyOption {
401 title: String::new(),
402 key: "ssh-rsa AAAAB3...".to_string(),
403 read_only: false,
404 };
405 assert!(opt.validate().is_err());
406 }
407
408 #[test]
409 fn test_create_access_token_option_validate_success() {
410 let opt = CreateAccessTokenOption {
411 name: "my-token".to_string(),
412 scopes: Vec::new(),
413 };
414 assert!(opt.validate().is_ok());
415 }
416
417 #[test]
418 fn test_create_access_token_option_validate_empty_name() {
419 let opt = CreateAccessTokenOption {
420 name: String::new(),
421 scopes: Vec::new(),
422 };
423 assert!(opt.validate().is_err());
424 }
425
426 #[test]
427 fn test_create_gpg_key_option_validate_success() {
428 let opt = CreateGPGKeyOption {
429 armored_key: "-----BEGIN PGP PUBLIC KEY BLOCK-----".to_string(),
430 signature: None,
431 };
432 assert!(opt.validate().is_ok());
433 }
434
435 #[test]
436 fn test_create_gpg_key_option_validate_empty_key() {
437 let opt = CreateGPGKeyOption {
438 armored_key: String::new(),
439 signature: None,
440 };
441 assert!(opt.validate().is_err());
442 }
443
444 #[test]
445 fn test_verify_gpg_key_option_validate_success() {
446 let opt = VerifyGPGKeyOption {
447 key_id: "ABCDEF".to_string(),
448 signature: "-----BEGIN PGP SIGNATURE-----".to_string(),
449 };
450 assert!(opt.validate().is_ok());
451 }
452
453 #[test]
454 fn test_verify_gpg_key_option_validate_empty_key_id() {
455 let opt = VerifyGPGKeyOption {
456 key_id: String::new(),
457 signature: "sig".to_string(),
458 };
459 assert!(opt.validate().is_err());
460 }
461
462 #[test]
463 fn test_verify_gpg_key_option_validate_empty_signature() {
464 let opt = VerifyGPGKeyOption {
465 key_id: "ABCDEF".to_string(),
466 signature: String::new(),
467 };
468 assert!(opt.validate().is_err());
469 }
470
471 #[test]
472 fn test_update_user_avatar_option_validate_success() {
473 let opt = UpdateUserAvatarOption {
474 image: "base64image".to_string(),
475 };
476 assert!(opt.validate().is_ok());
477 }
478
479 #[test]
480 fn test_update_user_avatar_option_validate_empty_image() {
481 let opt = UpdateUserAvatarOption {
482 image: String::new(),
483 };
484 assert!(opt.validate().is_err());
485 }
486}