Skip to main content

gitea_sdk_rs/options/
user.rs

1// Copyright 2026 infinitete. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5//! Request option types for user API endpoints.
6
7use crate::internal::request::urlencoding;
8use crate::pagination::{ListOptions, QueryEncode};
9use crate::types::enums::AccessTokenScope;
10use crate::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Default)]
13/// Options for List Emails Option.
14pub 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)]
25/// Options for Create Email Option.
26pub struct CreateEmailOption {
27    /// email addresses to add
28    pub emails: Vec<String>,
29}
30
31impl CreateEmailOption {
32    /// Validate this `CreateEmailOption` payload.
33    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)]
51/// Options for Delete Email Option.
52pub struct DeleteEmailOption {
53    /// email addresses to delete
54    pub emails: Vec<String>,
55}
56
57impl DeleteEmailOption {
58    /// Validate this `DeleteEmailOption` payload.
59    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)]
77/// Options for List Public Keys Option.
78pub 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)]
89/// Options for Create Key Option.
90pub struct CreateKeyOption {
91    /// Title of the key to add
92    pub title: String,
93    /// An armored SSH key to add
94    pub key: String,
95    /// Describe if the key has only read access or read/write
96    #[serde(default)]
97    pub read_only: bool,
98}
99
100impl CreateKeyOption {
101    /// Validate this `CreateKeyOption` payload.
102    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)]
114/// Options for List Followers Option.
115pub 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)]
126/// Options for List Following Option.
127pub 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)]
138/// Options for List Access Tokens Option.
139pub 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)]
150/// Options for Create Access Token Option.
151pub struct CreateAccessTokenOption {
152    pub name: String,
153    #[serde(default)]
154    pub scopes: Vec<AccessTokenScope>,
155}
156
157impl CreateAccessTokenOption {
158    /// Validate this `CreateAccessTokenOption` payload.
159    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)]
168/// Options for User Settings Option.
169pub 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)]
191/// Options for List User Blocks Option.
192pub 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)]
203/// Options for Search Users Option.
204pub 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)]
233/// Options for List User Activity Feeds Option.
234pub 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)]
255/// Options for List GPGKeys Option.
256pub 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)]
267/// Options for Create GPGKey Option.
268pub struct CreateGPGKeyOption {
269    /// An armored GPG key to add
270    #[serde(rename = "armored_public_key")]
271    pub armored_key: String,
272    /// An optional armored signature for the GPG key
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub signature: Option<String>,
275}
276
277impl CreateGPGKeyOption {
278    /// Validate this `CreateGPGKeyOption` payload.
279    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)]
290/// Options for Verify GPGKey Option.
291pub 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    /// Validate this `VerifyGPGKeyOption` payload.
300    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)]
314/// Options for Update User Avatar Option.
315pub struct UpdateUserAvatarOption {
316    /// base64 encoded image
317    pub image: String,
318}
319
320impl UpdateUserAvatarOption {
321    /// Validate this `UpdateUserAvatarOption` payload.
322    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}