use derive_builder::Builder;
use reqwest::Method;
use std::borrow::Cow;
use crate::api::custom_fields::CustomFieldEssentialsWithValue;
use crate::api::{Endpoint, NoPagination, ReturnsJsonResponse};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MyAccount {
pub id: u64,
pub login: String,
pub admin: bool,
pub firstname: String,
pub lastname: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mail: Option<String>,
#[serde(
serialize_with = "crate::api::serialize_rfc3339",
deserialize_with = "crate::api::deserialize_rfc3339"
)]
pub created_on: time::OffsetDateTime,
#[serde(
serialize_with = "crate::api::serialize_optional_rfc3339",
deserialize_with = "crate::api::deserialize_optional_rfc3339"
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub last_login_on: Option<time::OffsetDateTime>,
pub api_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub twofa_scheme: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auth_source_id: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub must_change_passwd: Option<bool>,
#[serde(
default,
serialize_with = "crate::api::serialize_optional_rfc3339",
deserialize_with = "crate::api::deserialize_optional_rfc3339"
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub passwd_changed_on: Option<time::OffsetDateTime>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_fields: Option<Vec<CustomFieldEssentialsWithValue>>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MailNotificationOption {
All,
Selected,
OnlyMyEvents,
OnlyAssigned,
OnlyOwner,
None,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommentsSorting {
Asc,
Desc,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TextareaFont {
Monospace,
Proportional,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolbarLanguage {
C,
Cpp,
Csharp,
Css,
Diff,
Go,
Groovy,
Html,
Java,
Javascript,
Objc,
Perl,
Php,
Python,
R,
Ruby,
Sass,
Scala,
Shell,
Sql,
Swift,
Xml,
Yaml,
}
impl std::fmt::Display for ToolbarLanguage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ToolbarLanguage::C => write!(f, "c"),
ToolbarLanguage::Cpp => write!(f, "cpp"),
ToolbarLanguage::Csharp => write!(f, "csharp"),
ToolbarLanguage::Css => write!(f, "css"),
ToolbarLanguage::Diff => write!(f, "diff"),
ToolbarLanguage::Go => write!(f, "go"),
ToolbarLanguage::Groovy => write!(f, "groovy"),
ToolbarLanguage::Html => write!(f, "html"),
ToolbarLanguage::Java => write!(f, "java"),
ToolbarLanguage::Javascript => write!(f, "javascript"),
ToolbarLanguage::Objc => write!(f, "objc"),
ToolbarLanguage::Perl => write!(f, "perl"),
ToolbarLanguage::Php => write!(f, "php"),
ToolbarLanguage::Python => write!(f, "python"),
ToolbarLanguage::R => write!(f, "r"),
ToolbarLanguage::Ruby => write!(f, "ruby"),
ToolbarLanguage::Sass => write!(f, "sass"),
ToolbarLanguage::Scala => write!(f, "scala"),
ToolbarLanguage::Shell => write!(f, "shell"),
ToolbarLanguage::Sql => write!(f, "sql"),
ToolbarLanguage::Swift => write!(f, "swift"),
ToolbarLanguage::Xml => write!(f, "xml"),
ToolbarLanguage::Yaml => write!(f, "yaml"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AutoWatchAction {
IssueCreated,
IssueContributedTo,
}
impl std::fmt::Display for AutoWatchAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AutoWatchAction::IssueCreated => write!(f, "issue_created"),
AutoWatchAction::IssueContributedTo => write!(f, "issue_contributed_to"),
}
}
}
#[derive(Debug, Clone, Builder)]
#[builder(setter(strip_option))]
pub struct GetMyAccount {}
impl ReturnsJsonResponse for GetMyAccount {}
impl NoPagination for GetMyAccount {}
impl GetMyAccount {
#[must_use]
pub fn builder() -> GetMyAccountBuilder {
GetMyAccountBuilder::default()
}
}
impl Endpoint for GetMyAccount {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
"my/account.json".into()
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Builder, serde::Serialize)]
#[builder(setter(strip_option))]
pub struct UpdateMyAccount<'a> {
#[builder(setter(into), default)]
firstname: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
lastname: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
mail: Option<Cow<'a, str>>,
#[builder(default)]
mail_notification: Option<MailNotificationOption>,
#[builder(default)]
notified_project_ids: Option<Vec<u64>>,
#[builder(setter(into), default)]
language: Option<Cow<'a, str>>,
#[builder(default)]
hide_mail: Option<bool>,
#[builder(setter(into), default)]
time_zone: Option<Cow<'a, str>>,
#[builder(default)]
comments_sorting: Option<CommentsSorting>,
#[builder(default)]
warn_on_leaving_unsaved: Option<bool>,
#[builder(default)]
no_self_notified: Option<bool>,
#[builder(default)]
notify_about_high_priority_issues: Option<bool>,
#[builder(default)]
textarea_font: Option<TextareaFont>,
#[builder(default)]
recently_used_projects: Option<u64>,
#[builder(setter(into), default)]
history_default_tab: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
default_issue_query: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
default_project_query: Option<Cow<'a, str>>,
#[builder(default)]
toolbar_language_options: Option<Vec<ToolbarLanguage>>,
#[builder(default)]
auto_watch_on: Option<Vec<AutoWatchAction>>,
}
impl<'a> UpdateMyAccount<'a> {
#[must_use]
pub fn builder() -> UpdateMyAccountBuilder<'a> {
UpdateMyAccountBuilder::default()
}
}
impl Endpoint for UpdateMyAccount<'_> {
fn method(&self) -> Method {
Method::PUT
}
fn endpoint(&self) -> Cow<'static, str> {
"my/account.json".into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
use serde_json::json;
let mut user_params = serde_json::Map::new();
if let Some(ref firstname) = self.firstname {
user_params.insert("firstname".to_string(), json!(firstname));
}
if let Some(ref lastname) = self.lastname {
user_params.insert("lastname".to_string(), json!(lastname));
}
if let Some(ref mail) = self.mail {
user_params.insert("mail".to_string(), json!(mail));
}
if let Some(ref mail_notification) = self.mail_notification {
user_params.insert("mail_notification".to_string(), json!(mail_notification));
}
if let Some(ref notified_project_ids) = self.notified_project_ids {
user_params.insert(
"notified_project_ids".to_string(),
json!(notified_project_ids),
);
}
if let Some(ref language) = self.language {
user_params.insert("language".to_string(), json!(language));
}
let mut pref_params = serde_json::Map::new();
if let Some(hide_mail) = self.hide_mail {
pref_params.insert("hide_mail".to_string(), json!(hide_mail));
}
if let Some(ref time_zone) = self.time_zone {
pref_params.insert("time_zone".to_string(), json!(time_zone));
}
if let Some(ref comments_sorting) = self.comments_sorting {
pref_params.insert("comments_sorting".to_string(), json!(comments_sorting));
}
if let Some(warn_on_leaving_unsaved) = self.warn_on_leaving_unsaved {
pref_params.insert(
"warn_on_leaving_unsaved".to_string(),
json!(warn_on_leaving_unsaved),
);
}
if let Some(no_self_notified) = self.no_self_notified {
pref_params.insert("no_self_notified".to_string(), json!(no_self_notified));
}
if let Some(notify_about_high_priority_issues) = self.notify_about_high_priority_issues {
pref_params.insert(
"notify_about_high_priority_issues".to_string(),
json!(notify_about_high_priority_issues),
);
}
if let Some(ref textarea_font) = self.textarea_font {
pref_params.insert("textarea_font".to_string(), json!(textarea_font));
}
if let Some(recently_used_projects) = self.recently_used_projects {
pref_params.insert(
"recently_used_projects".to_string(),
json!(recently_used_projects),
);
}
if let Some(ref history_default_tab) = self.history_default_tab {
pref_params.insert(
"history_default_tab".to_string(),
json!(history_default_tab),
);
}
if let Some(ref default_issue_query) = self.default_issue_query {
pref_params.insert(
"default_issue_query".to_string(),
json!(default_issue_query),
);
}
if let Some(ref default_project_query) = self.default_project_query {
pref_params.insert(
"default_project_query".to_string(),
json!(default_project_query),
);
}
if let Some(ref toolbar_language_options) = self.toolbar_language_options {
pref_params.insert(
"toolbar_language_options".to_string(),
json!(
toolbar_language_options
.iter()
.map(|e| e.to_string())
.collect::<Vec<String>>()
.join(",")
),
);
}
if let Some(ref auto_watch_on) = self.auto_watch_on {
pref_params.insert(
"auto_watch_on".to_string(),
json!(
auto_watch_on
.iter()
.map(|e| e.to_string())
.collect::<Vec<String>>()
.join(",")
),
);
}
let mut root_map = serde_json::Map::new();
if !user_params.is_empty() {
root_map.insert("user".to_string(), serde_json::Value::Object(user_params));
}
if !pref_params.is_empty() {
root_map.insert("pref".to_string(), serde_json::Value::Object(pref_params));
}
if root_map.is_empty() {
Ok(None)
} else {
Ok(Some((
"application/json",
serde_json::to_vec(&serde_json::Value::Object(root_map))?,
)))
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::api::users::UserWrapper;
use pretty_assertions::assert_eq;
use std::error::Error;
use tracing_test::traced_test;
#[traced_test]
#[test]
fn test_get_my_account() -> Result<(), Box<dyn Error>> {
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env(
reqwest::blocking::Client::builder()
.tls_backend_rustls()
.build()?,
)?;
let endpoint = GetMyAccount::builder().build()?;
redmine.json_response_body::<_, UserWrapper<MyAccount>>(&endpoint)?;
Ok(())
}
#[traced_test]
#[test]
fn test_update_my_account() -> Result<(), Box<dyn Error>> {
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env(
reqwest::blocking::Client::builder()
.tls_backend_rustls()
.build()?,
)?;
let get_endpoint = GetMyAccount::builder().build()?;
let original_account: UserWrapper<MyAccount> = redmine.json_response_body(&get_endpoint)?;
let update_endpoint = UpdateMyAccount::builder()
.firstname("NewFirstName")
.build()?;
redmine.ignore_response_body(&update_endpoint)?;
let updated_account: UserWrapper<MyAccount> = redmine.json_response_body(&get_endpoint)?;
assert_eq!(updated_account.user.firstname, "NewFirstName");
let restore_endpoint = UpdateMyAccount::builder()
.firstname(original_account.user.firstname.as_str())
.build()?;
redmine.ignore_response_body(&restore_endpoint)?;
let restored_account: UserWrapper<MyAccount> = redmine.json_response_body(&get_endpoint)?;
assert_eq!(
restored_account.user.firstname,
original_account.user.firstname
);
Ok(())
}
#[traced_test]
#[test]
fn test_completeness_my_account_type() -> Result<(), Box<dyn Error>> {
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env(
reqwest::blocking::Client::builder()
.tls_backend_rustls()
.build()?,
)?;
let endpoint = GetMyAccount::builder().build()?;
let UserWrapper { user: value } =
redmine.json_response_body::<_, UserWrapper<serde_json::Value>>(&endpoint)?;
let o: MyAccount = serde_json::from_value(value.clone())?;
let reserialized = serde_json::to_value(o)?;
assert_eq!(value, reserialized);
Ok(())
}
}