1use derive_builder::Builder;
8use reqwest::Method;
9use std::borrow::Cow;
10
11use crate::api::custom_fields::CustomFieldEssentialsWithValue;
12use crate::api::{Endpoint, NoPagination, ReturnsJsonResponse};
13use serde_json::json;
14
15#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19pub struct MyAccount {
20 pub id: u64,
22 pub login: String,
24 pub admin: bool,
26 pub firstname: String,
28 pub lastname: String,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub mail: Option<String>,
33 #[serde(
35 serialize_with = "crate::api::serialize_rfc3339",
36 deserialize_with = "crate::api::deserialize_rfc3339"
37 )]
38 pub created_on: time::OffsetDateTime,
39 #[serde(
41 serialize_with = "crate::api::serialize_optional_rfc3339",
42 deserialize_with = "crate::api::deserialize_optional_rfc3339"
43 )]
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub last_login_on: Option<time::OffsetDateTime>,
46 pub api_key: String,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub twofa_scheme: Option<String>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub auth_source_id: Option<u64>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub must_change_passwd: Option<bool>,
57 #[serde(
59 default,
60 serialize_with = "crate::api::serialize_optional_rfc3339",
61 deserialize_with = "crate::api::deserialize_optional_rfc3339"
62 )]
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub passwd_changed_on: Option<time::OffsetDateTime>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub custom_fields: Option<Vec<CustomFieldEssentialsWithValue>>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
72#[serde(rename_all = "snake_case")]
73pub enum MailNotificationOption {
74 All,
76 Selected,
78 OnlyMyEvents,
80 OnlyAssigned,
82 OnlyOwner,
84 None,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum CommentsSorting {
92 Asc,
94 Desc,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
100#[serde(rename_all = "snake_case")]
101pub enum TextareaFont {
102 Monospace,
104 Proportional,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
110#[serde(rename_all = "snake_case")]
111pub enum ToolbarLanguage {
112 C,
114 Cpp,
116 Csharp,
118 Css,
120 Diff,
122 Go,
124 Groovy,
126 Html,
128 Java,
130 Javascript,
132 Objc,
134 Perl,
136 Php,
138 Python,
140 R,
142 Ruby,
144 Sass,
146 Scala,
148 Shell,
150 Sql,
152 Swift,
154 Xml,
156 Yaml,
158}
159
160impl std::fmt::Display for ToolbarLanguage {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 ToolbarLanguage::C => write!(f, "c"),
164 ToolbarLanguage::Cpp => write!(f, "cpp"),
165 ToolbarLanguage::Csharp => write!(f, "csharp"),
166 ToolbarLanguage::Css => write!(f, "css"),
167 ToolbarLanguage::Diff => write!(f, "diff"),
168 ToolbarLanguage::Go => write!(f, "go"),
169 ToolbarLanguage::Groovy => write!(f, "groovy"),
170 ToolbarLanguage::Html => write!(f, "html"),
171 ToolbarLanguage::Java => write!(f, "java"),
172 ToolbarLanguage::Javascript => write!(f, "javascript"),
173 ToolbarLanguage::Objc => write!(f, "objc"),
174 ToolbarLanguage::Perl => write!(f, "perl"),
175 ToolbarLanguage::Php => write!(f, "php"),
176 ToolbarLanguage::Python => write!(f, "python"),
177 ToolbarLanguage::R => write!(f, "r"),
178 ToolbarLanguage::Ruby => write!(f, "ruby"),
179 ToolbarLanguage::Sass => write!(f, "sass"),
180 ToolbarLanguage::Scala => write!(f, "scala"),
181 ToolbarLanguage::Shell => write!(f, "shell"),
182 ToolbarLanguage::Sql => write!(f, "sql"),
183 ToolbarLanguage::Swift => write!(f, "swift"),
184 ToolbarLanguage::Xml => write!(f, "xml"),
185 ToolbarLanguage::Yaml => write!(f, "yaml"),
186 }
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
192#[serde(rename_all = "snake_case")]
193pub enum AutoWatchAction {
194 IssueCreated,
196 IssueContributedTo,
198}
199
200impl std::fmt::Display for AutoWatchAction {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 match self {
203 AutoWatchAction::IssueCreated => write!(f, "issue_created"),
204 AutoWatchAction::IssueContributedTo => write!(f, "issue_contributed_to"),
205 }
206 }
207}
208
209#[derive(Debug, Clone, Builder)]
211#[builder(setter(strip_option))]
212pub struct GetMyAccount {}
213
214impl ReturnsJsonResponse for GetMyAccount {}
215impl NoPagination for GetMyAccount {}
216
217impl GetMyAccount {
218 #[must_use]
220 pub fn builder() -> GetMyAccountBuilder {
221 GetMyAccountBuilder::default()
222 }
223}
224
225impl Endpoint for GetMyAccount {
226 fn method(&self) -> Method {
227 Method::GET
228 }
229
230 fn endpoint(&self) -> Cow<'static, str> {
231 "my/account.json".into()
232 }
233}
234
235#[serde_with::skip_serializing_none]
237#[derive(Debug, Clone, Builder, serde::Serialize)]
238#[builder(setter(strip_option))]
239pub struct UpdateMyAccount<'a> {
240 #[builder(setter(into), default)]
242 firstname: Option<Cow<'a, str>>,
243 #[builder(setter(into), default)]
245 lastname: Option<Cow<'a, str>>,
246 #[builder(setter(into), default)]
248 mail: Option<Cow<'a, str>>,
249 #[builder(default)]
251 mail_notification: Option<MailNotificationOption>,
252 #[builder(default)]
254 notified_project_ids: Option<Vec<u64>>,
255 #[builder(setter(into), default)]
257 language: Option<Cow<'a, str>>,
258 #[builder(default)]
260 hide_mail: Option<bool>,
261 #[builder(setter(into), default)]
263 time_zone: Option<Cow<'a, str>>,
264 #[builder(default)]
266 comments_sorting: Option<CommentsSorting>,
267 #[builder(default)]
269 warn_on_leaving_unsaved: Option<bool>,
270 #[builder(default)]
272 no_self_notified: Option<bool>,
273 #[builder(default)]
275 notify_about_high_priority_issues: Option<bool>,
276 #[builder(default)]
278 textarea_font: Option<TextareaFont>,
279 #[builder(default)]
281 recently_used_projects: Option<u64>,
282 #[builder(setter(into), default)]
284 history_default_tab: Option<Cow<'a, str>>,
285 #[builder(setter(into), default)]
287 default_issue_query: Option<Cow<'a, str>>,
288 #[builder(setter(into), default)]
290 default_project_query: Option<Cow<'a, str>>,
291 #[builder(default)]
293 toolbar_language_options: Option<Vec<ToolbarLanguage>>,
294 #[builder(default)]
296 auto_watch_on: Option<Vec<AutoWatchAction>>,
297}
298
299impl<'a> UpdateMyAccount<'a> {
300 #[must_use]
302 pub fn builder() -> UpdateMyAccountBuilder<'a> {
303 UpdateMyAccountBuilder::default()
304 }
305}
306
307impl Endpoint for UpdateMyAccount<'_> {
308 fn method(&self) -> Method {
309 Method::PUT
310 }
311
312 fn endpoint(&self) -> Cow<'static, str> {
313 "my/account.json".into()
314 }
315
316 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
317 use serde_json::json;
318 let mut user_params = serde_json::Map::new();
319 if let Some(ref firstname) = self.firstname {
320 user_params.insert("firstname".to_string(), json!(firstname));
321 }
322 if let Some(ref lastname) = self.lastname {
323 user_params.insert("lastname".to_string(), json!(lastname));
324 }
325 if let Some(ref mail) = self.mail {
326 user_params.insert("mail".to_string(), json!(mail));
327 }
328 if let Some(ref mail_notification) = self.mail_notification {
329 user_params.insert("mail_notification".to_string(), json!(mail_notification));
330 }
331 if let Some(ref notified_project_ids) = self.notified_project_ids {
332 user_params.insert(
333 "notified_project_ids".to_string(),
334 json!(notified_project_ids),
335 );
336 }
337 if let Some(ref language) = self.language {
338 user_params.insert("language".to_string(), json!(language));
339 }
340
341 let mut pref_params = serde_json::Map::new();
342 if let Some(hide_mail) = self.hide_mail {
343 pref_params.insert("hide_mail".to_string(), json!(hide_mail));
344 }
345 if let Some(ref time_zone) = self.time_zone {
346 pref_params.insert("time_zone".to_string(), json!(time_zone));
347 }
348 if let Some(ref comments_sorting) = self.comments_sorting {
349 pref_params.insert("comments_sorting".to_string(), json!(comments_sorting));
350 }
351 if let Some(warn_on_leaving_unsaved) = self.warn_on_leaving_unsaved {
352 pref_params.insert(
353 "warn_on_leaving_unsaved".to_string(),
354 json!(warn_on_leaving_unsaved),
355 );
356 }
357 if let Some(no_self_notified) = self.no_self_notified {
358 pref_params.insert("no_self_notified".to_string(), json!(no_self_notified));
359 }
360 if let Some(notify_about_high_priority_issues) = self.notify_about_high_priority_issues {
361 pref_params.insert(
362 "notify_about_high_priority_issues".to_string(),
363 json!(notify_about_high_priority_issues),
364 );
365 }
366 if let Some(ref textarea_font) = self.textarea_font {
367 pref_params.insert("textarea_font".to_string(), json!(textarea_font));
368 }
369 if let Some(recently_used_projects) = self.recently_used_projects {
370 pref_params.insert(
371 "recently_used_projects".to_string(),
372 json!(recently_used_projects),
373 );
374 }
375 if let Some(ref history_default_tab) = self.history_default_tab {
376 pref_params.insert(
377 "history_default_tab".to_string(),
378 json!(history_default_tab),
379 );
380 }
381 if let Some(ref default_issue_query) = self.default_issue_query {
382 pref_params.insert(
383 "default_issue_query".to_string(),
384 json!(default_issue_query),
385 );
386 }
387 if let Some(ref default_project_query) = self.default_project_query {
388 pref_params.insert(
389 "default_project_query".to_string(),
390 json!(default_project_query),
391 );
392 }
393 if let Some(ref toolbar_language_options) = self.toolbar_language_options {
394 pref_params.insert(
395 "toolbar_language_options".to_string(),
396 json!(
397 toolbar_language_options
398 .iter()
399 .map(|e| e.to_string())
400 .collect::<Vec<String>>()
401 .join(",")
402 ),
403 );
404 }
405 if let Some(ref auto_watch_on) = self.auto_watch_on {
406 pref_params.insert(
407 "auto_watch_on".to_string(),
408 json!(
409 auto_watch_on
410 .iter()
411 .map(|e| e.to_string())
412 .collect::<Vec<String>>()
413 .join(",")
414 ),
415 );
416 }
417
418 let mut root_map = serde_json::Map::new();
419 if !user_params.is_empty() {
420 root_map.insert("user".to_string(), serde_json::Value::Object(user_params));
421 }
422 if !pref_params.is_empty() {
423 root_map.insert("pref".to_string(), serde_json::Value::Object(pref_params));
424 }
425
426 if root_map.is_empty() {
427 Ok(None)
428 } else {
429 Ok(Some((
430 "application/json",
431 serde_json::to_vec(&serde_json::Value::Object(root_map))?,
432 )))
433 }
434 }
435}
436
437#[cfg(test)]
438mod test {
439 use super::*;
440 use crate::api::users::UserWrapper;
441 use pretty_assertions::assert_eq;
442 use std::error::Error;
443 use tracing_test::traced_test;
444
445 #[traced_test]
446 #[test]
447 fn test_get_my_account() -> Result<(), Box<dyn Error>> {
448 dotenvy::dotenv()?;
449 let redmine = crate::api::Redmine::from_env(
450 reqwest::blocking::Client::builder()
451 .use_rustls_tls()
452 .build()?,
453 )?;
454 let endpoint = GetMyAccount::builder().build()?;
455 redmine.json_response_body::<_, UserWrapper<MyAccount>>(&endpoint)?;
456 Ok(())
457 }
458
459 #[traced_test]
460 #[test]
461 fn test_update_my_account() -> Result<(), Box<dyn Error>> {
462 dotenvy::dotenv()?;
463 let redmine = crate::api::Redmine::from_env(
464 reqwest::blocking::Client::builder()
465 .use_rustls_tls()
466 .build()?,
467 )?;
468 let get_endpoint = GetMyAccount::builder().build()?;
469 let original_account: UserWrapper<MyAccount> = redmine.json_response_body(&get_endpoint)?;
470 let update_endpoint = UpdateMyAccount::builder()
471 .firstname("NewFirstName")
472 .build()?;
473 redmine.ignore_response_body(&update_endpoint)?;
474 let updated_account: UserWrapper<MyAccount> = redmine.json_response_body(&get_endpoint)?;
475 assert_eq!(updated_account.user.firstname, "NewFirstName");
476 let restore_endpoint = UpdateMyAccount::builder()
477 .firstname(original_account.user.firstname.as_str())
478 .build()?;
479 redmine.ignore_response_body(&restore_endpoint)?;
480 let restored_account: UserWrapper<MyAccount> = redmine.json_response_body(&get_endpoint)?;
481 assert_eq!(
482 restored_account.user.firstname,
483 original_account.user.firstname
484 );
485 Ok(())
486 }
487
488 #[traced_test]
493 #[test]
494 fn test_completeness_my_account_type() -> Result<(), Box<dyn Error>> {
495 dotenvy::dotenv()?;
496 let redmine = crate::api::Redmine::from_env(
497 reqwest::blocking::Client::builder()
498 .use_rustls_tls()
499 .build()?,
500 )?;
501 let endpoint = GetMyAccount::builder().build()?;
502 let UserWrapper { user: value } =
503 redmine.json_response_body::<_, UserWrapper<serde_json::Value>>(&endpoint)?;
504 let o: MyAccount = serde_json::from_value(value.clone())?;
505 let reserialized = serde_json::to_value(o)?;
506 assert_eq!(value, reserialized);
507 Ok(())
508 }
509}