fix config deserialization issues

This commit is contained in:
Clément DOUIN 2023-02-18 21:12:47 +01:00
parent efd24251e0
commit bfb572acbd
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
8 changed files with 148 additions and 85 deletions

View file

@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Fixed
- Fixed config deserialization issue with `email-hooks` and
`email-reading-format`.
## [0.7.1] - 2023-02-14 ## [0.7.1] - 2023-02-14
### Added ### Added

57
Cargo.lock generated
View file

@ -803,7 +803,7 @@ dependencies = [
[[package]] [[package]]
name = "himalaya-lib" name = "himalaya-lib"
version = "0.6.0" version = "0.6.0"
source = "git+https://git.sr.ht/~soywod/himalaya-lib?branch=develop#dff1c2354bd480b5997c947c38601b40796f683e" source = "git+https://git.sr.ht/~soywod/himalaya-lib?branch=develop#b1a60353ea0577cfeb886433c7a9065c93ad534d"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"chrono", "chrono",
@ -1291,6 +1291,15 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr 2.5.0",
]
[[package]] [[package]]
name = "notmuch" name = "notmuch"
version = "0.8.0" version = "0.8.0"
@ -1853,18 +1862,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.148" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.148" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1882,6 +1891,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "shellexpand" name = "shellexpand"
version = "2.1.2" version = "2.1.2"
@ -2099,11 +2117,36 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.9" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
] ]
[[package]] [[package]]

View file

@ -45,7 +45,7 @@ serde_json = "1.0"
shellexpand = "2.1" shellexpand = "2.1"
termcolor = "1.1" termcolor = "1.1"
terminal_size = "0.1" terminal_size = "0.1"
toml = "0.5" toml = "0.7.2"
unicode-width = "0.1" unicode-width = "0.1"
url = "2.2" url = "2.2"
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }

51
config.sample.toml Normal file
View file

@ -0,0 +1,51 @@
display-name = "Display NAME"
signature-delim = "~~"
signature = "~/.signature"
downloads-dir = "~/downloads"
folder-listing-page-size = 12
email-listing-page-size = 12
email-reading-headers = ["From", "To"]
email-reading-verify-cmd = "gpg --verify -q"
email-reading-decrypt-cmd = "gpg -dq"
email-writing-sign-cmd = "gpg -o - -saq"
email-writing-encrypt-cmd = "gpg -o - -eqar <recipient>"
[example]
default = false
display-name = "Display NAME (gmail)"
email = "display.name@gmail.local"
backend = "imap"
imap-host = "imap.gmail.com"
imap-login = "display.name@gmail.local"
imap-passwd-cmd = "pass show gmail"
imap-port = 993
imap-ssl = true
imap-starttls = false
imap-notify-cmd = """📫 "<sender>" "<subject>""""
imap-notify-query = "NOT SEEN"
imap-watch-cmds = ["echo \"received server changes!\""]
sender = "smtp"
smtp-host = "smtp.gmail.com"
smtp-login = "display.name@gmail.local"
smtp-passwd-cmd = "pass show piana/gmail"
smtp-port = 465
smtp-ssl = true
smtp-starttls = false
sync = true
sync-dir = "/tmp/sync/gmail"
[example.folder-aliases]
inbox = "INBOX"
drafts = "[Gmail]/Drafts"
sent = "[Gmail]/Sent Mail"
trash = "[Gmail]/Trash"
[example.email-hooks]
pre-send = "echo $1"
[example.email-reading-format]
type = "fixed"
width = 64

View file

@ -17,7 +17,7 @@ use crate::{
}; };
/// Represents the user config file. /// Represents the user config file.
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct DeserializedConfig { pub struct DeserializedConfig {
#[serde(alias = "name")] #[serde(alias = "name")]
@ -31,12 +31,8 @@ pub struct DeserializedConfig {
pub email_listing_page_size: Option<usize>, pub email_listing_page_size: Option<usize>,
pub email_reading_headers: Option<Vec<String>>, pub email_reading_headers: Option<Vec<String>>,
#[serde( #[serde(default, with = "EmailTextPlainFormatDef")]
default, pub email_reading_format: EmailTextPlainFormat,
with = "EmailTextPlainFormatOptionDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_format: Option<EmailTextPlainFormat>,
pub email_reading_verify_cmd: Option<String>, pub email_reading_verify_cmd: Option<String>,
pub email_reading_decrypt_cmd: Option<String>, pub email_reading_decrypt_cmd: Option<String>,
pub email_writing_headers: Option<Vec<String>>, pub email_writing_headers: Option<Vec<String>>,
@ -44,10 +40,10 @@ pub struct DeserializedConfig {
pub email_writing_encrypt_cmd: Option<String>, pub email_writing_encrypt_cmd: Option<String>,
#[serde( #[serde(
default, default,
with = "EmailHooksOptionDef", with = "EmailHooksDef",
skip_serializing_if = "Option::is_none" skip_serializing_if = "EmailHooks::is_empty"
)] )]
pub email_hooks: Option<EmailHooks>, pub email_hooks: EmailHooks,
#[serde(flatten)] #[serde(flatten)]
pub accounts: HashMap<String, DeserializedAccountConfig>, pub accounts: HashMap<String, DeserializedAccountConfig>,

View file

@ -11,7 +11,7 @@ use himalaya_lib::ImapConfig;
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
use himalaya_lib::NotmuchConfig; use himalaya_lib::NotmuchConfig;
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SmtpConfig")] #[serde(remote = "SmtpConfig")]
struct SmtpConfigDef { struct SmtpConfigDef {
#[serde(rename = "smtp-host")] #[serde(rename = "smtp-host")]
@ -31,7 +31,7 @@ struct SmtpConfigDef {
} }
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "ImapConfig")] #[serde(remote = "ImapConfig")]
pub struct ImapConfigDef { pub struct ImapConfigDef {
#[serde(rename = "imap-host")] #[serde(rename = "imap-host")]
@ -56,41 +56,39 @@ pub struct ImapConfigDef {
pub watch_cmds: Option<Vec<String>>, pub watch_cmds: Option<Vec<String>>,
} }
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "MaildirConfig")] #[serde(remote = "MaildirConfig", rename_all = "kebab-case")]
pub struct MaildirConfigDef { pub struct MaildirConfigDef {
#[serde(rename = "maildir-root-dir")] #[serde(rename = "maildir-root-dir")]
pub root_dir: PathBuf, pub root_dir: PathBuf,
} }
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "NotmuchConfig")] #[serde(remote = "NotmuchConfig", rename_all = "kebab-case")]
pub struct NotmuchConfigDef { pub struct NotmuchConfigDef {
#[serde(rename = "notmuch-db-path")] #[serde(rename = "notmuch-db-path")]
pub db_path: PathBuf, pub db_path: PathBuf,
} }
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Option<EmailTextPlainFormat>")] #[serde(
pub enum EmailTextPlainFormatOptionDef { remote = "EmailTextPlainFormat",
#[serde(with = "EmailTextPlainFormatDef")] tag = "type",
Some(EmailTextPlainFormat), content = "width",
#[default] rename_all = "kebab-case"
None, )]
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "EmailTextPlainFormat", rename_all = "snake_case")]
pub enum EmailTextPlainFormatDef { pub enum EmailTextPlainFormatDef {
#[default]
Auto, Auto,
Flowed, Flowed,
Fixed(usize), Fixed(usize),
} }
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "EmailSender", tag = "sender", rename_all = "snake_case")] #[serde(remote = "EmailSender", tag = "sender", rename_all = "kebab-case")]
pub enum EmailSenderDef { pub enum EmailSenderDef {
#[default]
None, None,
#[serde(with = "SmtpConfigDef")] #[serde(with = "SmtpConfigDef")]
Smtp(SmtpConfig), Smtp(SmtpConfig),
@ -98,26 +96,17 @@ pub enum EmailSenderDef {
Sendmail(SendmailConfig), Sendmail(SendmailConfig),
} }
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SendmailConfig")] #[serde(remote = "SendmailConfig", rename_all = "kebab-case")]
pub struct SendmailConfigDef { pub struct SendmailConfigDef {
#[serde(rename = "sendmail-cmd")] #[serde(rename = "sendmail-cmd")]
cmd: String, cmd: String,
} }
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Option<EmailHooks>")]
pub enum EmailHooksOptionDef {
#[serde(with = "EmailHooksDef")]
Some(EmailHooks),
#[default]
None,
}
/// Represents the email hooks. Useful for doing extra email /// Represents the email hooks. Useful for doing extra email
/// processing before or after sending it. /// processing before or after sending it.
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "EmailHooks")] #[serde(remote = "EmailHooks", rename_all = "kebab-case")]
pub struct EmailHooksDef { pub struct EmailHooksDef {
/// Represents the hook called just before sending an email. /// Represents the hook called just before sending an email.
pub pre_send: Option<String>, pub pre_send: Option<String>,

View file

@ -32,7 +32,6 @@ const SECURITY_PROTOCOLS: &[&str] = &["SSL/TLS", "STARTTLS", "None"];
static THEME: Lazy<ColorfulTheme> = Lazy::new(ColorfulTheme::default); static THEME: Lazy<ColorfulTheme> = Lazy::new(ColorfulTheme::default);
pub(crate) fn wizard() -> Result<DeserializedConfig> { pub(crate) fn wizard() -> Result<DeserializedConfig> {
trace!(">> wizard");
println!("Himalaya couldn't find an already existing configuration file."); println!("Himalaya couldn't find an already existing configuration file.");
match Confirm::new() match Confirm::new()
@ -111,7 +110,7 @@ pub(crate) fn wizard() -> Result<DeserializedConfig> {
// Serialize config to file // Serialize config to file
println!("\nWriting the configuration to {path:?}..."); println!("\nWriting the configuration to {path:?}...");
fs::create_dir_all(path.parent().unwrap())?; fs::create_dir_all(path.parent().unwrap())?;
fs::write(path, toml::to_vec(&config)?)?; fs::write(path, toml::to_string(&config)?)?;
trace!("<< wizard"); trace!("<< wizard");
Ok(config) Ok(config)

View file

@ -19,8 +19,8 @@ use std::{collections::HashMap, path::PathBuf};
use crate::config::{prelude::*, DeserializedConfig}; use crate::config::{prelude::*, DeserializedConfig};
/// Represents all existing kind of account config. /// Represents all existing kind of account config.
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(tag = "backend", rename_all = "snake_case")] #[serde(tag = "backend", rename_all = "kebab-case")]
pub enum DeserializedAccountConfig { pub enum DeserializedAccountConfig {
None(DeserializedBaseAccountConfig), None(DeserializedBaseAccountConfig),
Maildir(DeserializedMaildirAccountConfig), Maildir(DeserializedMaildirAccountConfig),
@ -70,7 +70,7 @@ impl DeserializedAccountConfig {
} }
} }
#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct DeserializedBaseAccountConfig { pub struct DeserializedBaseAccountConfig {
pub email: String, pub email: String,
@ -85,12 +85,8 @@ pub struct DeserializedBaseAccountConfig {
pub email_listing_page_size: Option<usize>, pub email_listing_page_size: Option<usize>,
pub email_reading_headers: Option<Vec<String>>, pub email_reading_headers: Option<Vec<String>>,
#[serde( #[serde(default, with = "EmailTextPlainFormatDef")]
default, pub email_reading_format: EmailTextPlainFormat,
with = "EmailTextPlainFormatOptionDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_format: Option<EmailTextPlainFormat>,
pub email_reading_verify_cmd: Option<String>, pub email_reading_verify_cmd: Option<String>,
pub email_reading_decrypt_cmd: Option<String>, pub email_reading_decrypt_cmd: Option<String>,
pub email_writing_headers: Option<Vec<String>>, pub email_writing_headers: Option<Vec<String>>,
@ -100,10 +96,10 @@ pub struct DeserializedBaseAccountConfig {
pub email_sender: EmailSender, pub email_sender: EmailSender,
#[serde( #[serde(
default, default,
with = "EmailHooksOptionDef", with = "EmailHooksDef",
skip_serializing_if = "Option::is_none" skip_serializing_if = "EmailHooks::is_empty"
)] )]
pub email_hooks: Option<EmailHooks>, pub email_hooks: EmailHooks,
#[serde(default)] #[serde(default)]
pub sync: bool, pub sync: bool,
@ -159,12 +155,7 @@ impl DeserializedBaseAccountConfig {
.as_ref() .as_ref()
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.or_else(|| config.email_reading_headers.as_ref().map(ToOwned::to_owned)), .or_else(|| config.email_reading_headers.as_ref().map(ToOwned::to_owned)),
email_reading_format: self email_reading_format: self.email_reading_format.clone(),
.email_reading_format
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.email_reading_format.as_ref().map(ToOwned::to_owned))
.unwrap_or_default(),
email_reading_verify_cmd: self email_reading_verify_cmd: self
.email_reading_verify_cmd .email_reading_verify_cmd
.as_ref() .as_ref()
@ -212,18 +203,7 @@ impl DeserializedBaseAccountConfig {
.or_else(|| config.email_writing_headers.as_ref().map(ToOwned::to_owned)), .or_else(|| config.email_writing_headers.as_ref().map(ToOwned::to_owned)),
email_sender: self.email_sender.to_owned(), email_sender: self.email_sender.to_owned(),
email_hooks: EmailHooks { email_hooks: EmailHooks {
pre_send: self pre_send: self.email_hooks.pre_send.clone(),
.email_hooks
.as_ref()
.map(ToOwned::to_owned)
.map(|hook| hook.pre_send)
.or_else(|| {
config
.email_hooks
.as_ref()
.map(|hook| hook.pre_send.as_ref().map(ToOwned::to_owned))
})
.unwrap_or_default(),
}, },
sync: self.sync, sync: self.sync,
sync_dir: self.sync_dir.clone(), sync_dir: self.sync_dir.clone(),
@ -231,7 +211,7 @@ impl DeserializedBaseAccountConfig {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
pub struct DeserializedImapAccountConfig { pub struct DeserializedImapAccountConfig {
#[serde(flatten)] #[serde(flatten)]
@ -240,7 +220,7 @@ pub struct DeserializedImapAccountConfig {
pub backend: ImapConfig, pub backend: ImapConfig,
} }
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct DeserializedMaildirAccountConfig { pub struct DeserializedMaildirAccountConfig {
#[serde(flatten)] #[serde(flatten)]
pub base: DeserializedBaseAccountConfig, pub base: DeserializedBaseAccountConfig,
@ -248,7 +228,7 @@ pub struct DeserializedMaildirAccountConfig {
pub backend: MaildirConfig, pub backend: MaildirConfig,
} }
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
pub struct DeserializedNotmuchAccountConfig { pub struct DeserializedNotmuchAccountConfig {
#[serde(flatten)] #[serde(flatten)]