meli: make config error more user-friendly
If `send_mail` is incorrect, display a long-ish list of valid examples. Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>pull/335/head
parent
f63774fa6d
commit
f0866a3965
|
@ -616,6 +616,12 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.2"
|
||||
|
@ -1002,6 +1008,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.7.1"
|
||||
|
@ -1271,7 +1287,7 @@ dependencies = [
|
|||
"crossbeam",
|
||||
"flate2",
|
||||
"futures",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"linkify",
|
||||
|
@ -1312,7 +1328,7 @@ dependencies = [
|
|||
"flate2",
|
||||
"futures",
|
||||
"imap-codec",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"isahc",
|
||||
"libc",
|
||||
"libloading",
|
||||
|
@ -2021,6 +2037,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.0"
|
||||
|
@ -2307,12 +2332,37 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.0.1",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
||||
dependencies = [
|
||||
"indexmap 2.0.1",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2688,6 +2738,15 @@ version = "0.48.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
|
|
|
@ -44,7 +44,7 @@ smallvec = { version = "^1.5.0", features = ["serde"] }
|
|||
structopt = { version = "0.3.14", default-features = false }
|
||||
svg_crate = { version = "^0.13", optional = true, package = "svg" }
|
||||
termion = { version = "1.5.1", default-features = false }
|
||||
toml = { version = "0.5.6", default-features = false, features = ["preserve_order"] }
|
||||
toml = { version = "0.8", default-features = false, features = ["display","preserve_order","parse"] }
|
||||
xdg = "2.1.0"
|
||||
|
||||
[dependencies.pcre2]
|
||||
|
|
|
@ -439,18 +439,16 @@ impl FileSettings {
|
|||
|
||||
pub fn validate(path: PathBuf, interactive: bool, clear_extras: bool) -> Result<Self> {
|
||||
let s = pp::pp(&path)?;
|
||||
let map: toml::map::Map<String, toml::value::Value> =
|
||||
toml::from_str(&s).map_err(|err| {
|
||||
Error::new(format!(
|
||||
"{}:\nConfig file is invalid TOML: {}",
|
||||
path.display(),
|
||||
err
|
||||
))
|
||||
})?;
|
||||
/*
|
||||
* Check that a global composing option is set and return a user-friendly
|
||||
* error message because the default serde one is confusing.
|
||||
*/
|
||||
let map: toml::value::Table = toml::from_str(&s).map_err(|err| {
|
||||
Error::new(format!(
|
||||
"{}: Config file is invalid TOML; {}",
|
||||
path.display(),
|
||||
err
|
||||
))
|
||||
})?;
|
||||
|
||||
// Check that a global composing option is set and return a user-friendly
|
||||
// error message because the default serde one is confusing.
|
||||
if !map.contains_key("composing") {
|
||||
let err_msg = r#"You must set a global `composing` option. If you override `composing` in each account, you can use a dummy global like follows:
|
||||
|
||||
|
@ -484,7 +482,7 @@ This is required so that you don't accidentally start meli and find out later th
|
|||
}
|
||||
let mut s: FileSettings = toml::from_str(&s).map_err(|err| {
|
||||
Error::new(format!(
|
||||
"{}:\nConfig file contains errors: {}",
|
||||
"{}: Config file contains errors; {}",
|
||||
path.display(),
|
||||
err
|
||||
))
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use melib::{email::HeaderName, ToggleFlag};
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
use super::{
|
||||
default_vals::{ask, false_val, none, true_val},
|
||||
|
@ -176,7 +177,7 @@ pub mod strings {
|
|||
named_unit_variant!(server_submission);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SendMail {
|
||||
#[cfg(feature = "smtp")]
|
||||
|
@ -202,3 +203,103 @@ impl From<ComposeHook> for crate::mail::hooks::Hook {
|
|||
Self::new_shell_command(c.name.into(), c.command)
|
||||
}
|
||||
}
|
||||
const SENDMAIL_ERR_HELP: &str = r#"Invalid `send_mail` value.
|
||||
|
||||
Here are some valid examples:
|
||||
|
||||
Use server submission in protocols that support it (JMAP, NNTP)
|
||||
===============================================================
|
||||
|
||||
send_mail = "server_submission"
|
||||
|
||||
Using a shell script
|
||||
====================
|
||||
|
||||
send_mail = "msmtp --read-recipients --read-envelope-from"
|
||||
|
||||
Direct SMTP connection
|
||||
======================
|
||||
|
||||
send_mail = { hostname = "mail.example.com", port = 587, auth = { type = "auto", password = { type = "raw", value = "hunter2" } }, security = { type = "STARTTLS" } }
|
||||
|
||||
[composing.send_mail]
|
||||
hostname = "mail.example.com"
|
||||
port = 587
|
||||
auth = { type = "auto", password = { type = "command_eval", value = "/path/to/password_script.sh" } }
|
||||
security = { type = "TLS", danger_accept_invalid_certs = true } }
|
||||
|
||||
|
||||
`send_mail` direct SMTP connection fields:
|
||||
- hostname: text
|
||||
- port: valid port number
|
||||
- envelope_from: text (optional, default is empty),
|
||||
- auth: ...
|
||||
- security: ... (optional, default is "auto")
|
||||
- extensions: ... (optional, default is PIPELINING, CHUNKING, PRDR, 8BITMIME, BINARYMIME, SMTPUTF8, AUTH and DSN_NOTIFY)
|
||||
|
||||
Possible values for `send_mail.auth`:
|
||||
|
||||
No authentication:
|
||||
|
||||
auth = { type = "none" }
|
||||
|
||||
Regular authentication:
|
||||
Note: `require_auth` and `auth_type` are optional and can be skipped.
|
||||
|
||||
auth = { type = "auto", username = "...", password = "...", require_auth = true, auth_type = ... }
|
||||
|
||||
password can be:
|
||||
password = { type = "raw", value = "..." }
|
||||
password = { type = "command_eval", value = "/path/to/password_script.sh" }
|
||||
|
||||
XOAuth2 authentication:
|
||||
Note: `require_auth` is optional and can be skipped.
|
||||
auth = { type = "xoauth2", token_command = "...", require_auth = true }
|
||||
|
||||
Possible values for `send_mail.auth.auth_type` when `auth.type` is "auto":
|
||||
|
||||
auth_type = { plain = false, login = true }
|
||||
|
||||
Possible values for `send_mail.security`:
|
||||
Note that in all cases field `danger_accept_invalid_certs` is optional and its default value is false.
|
||||
|
||||
security = "none"
|
||||
security = { type = "auto", danger_accept_invalid_certs = false }
|
||||
security = { type = "STARTTLS", danger_accept_invalid_certs = false }
|
||||
security = { type = "TLS", danger_accept_invalid_certs = false }
|
||||
|
||||
Possible values for `send_mail.extensions` (All optional and have default values `true`:
|
||||
pipelining
|
||||
chunking
|
||||
8bitmime
|
||||
prdr
|
||||
binarymime
|
||||
smtputf8
|
||||
auth
|
||||
dsn_notify: Array of options e.g. ["FAILURE"]
|
||||
"#;
|
||||
|
||||
impl<'de> Deserialize<'de> for SendMail {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum SendMailInner {
|
||||
#[cfg(feature = "smtp")]
|
||||
Smtp(melib::smtp::SmtpServerConf),
|
||||
#[serde(with = "strings::server_submission")]
|
||||
ServerSubmission,
|
||||
ShellCommand(String),
|
||||
}
|
||||
|
||||
match <SendMailInner>::deserialize(deserializer) {
|
||||
#[cfg(feature = "smtp")]
|
||||
Ok(SendMailInner::Smtp(v)) => Ok(SendMail::Smtp(v)),
|
||||
Ok(SendMailInner::ServerSubmission) => Ok(SendMail::ServerSubmission),
|
||||
Ok(SendMailInner::ShellCommand(v)) => Ok(SendMail::ShellCommand(v)),
|
||||
Err(_err) => Err(de::Error::custom(SENDMAIL_ERR_HELP)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -460,20 +460,32 @@ fn test_color_de() {
|
|||
);
|
||||
};
|
||||
($s:literal, err $v:literal) => {
|
||||
test_color!($s, err $v, "")
|
||||
};
|
||||
($s:literal, err $v:literal, $extra:literal) => {
|
||||
assert_eq!(
|
||||
toml::from_str::<V>(std::concat!("k = \"", $s, "\""))
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
$v.to_string()
|
||||
std::concat!(
|
||||
"TOML parse error at line 1, column 5\n |\n1 | k = \"",
|
||||
$s,
|
||||
"\"\n | ",
|
||||
$extra,
|
||||
"^^^^^^\n",
|
||||
$v,
|
||||
'\n',
|
||||
)
|
||||
.to_string()
|
||||
);
|
||||
};
|
||||
}
|
||||
test_color!("#Ff6600", ok Color::Rgb(255, 102, 0));
|
||||
test_color!("#2E3440", ok Color::Rgb(46, 52, 64));
|
||||
test_color!("#f60", ok Color::Rgb(255, 102, 0));
|
||||
test_color!("#gb0", err "invalid `color` value for key `k` at line 1 column 1");
|
||||
test_color!("#gb0", err "invalid `color` value");
|
||||
test_color!("Olive", ok Color::Byte(3));
|
||||
test_color!("Oafahifdave", err "invalid `color` value for key `k` at line 1 column 1");
|
||||
test_color!("Oafahifdave", err "invalid `color` value", "^^^^^^^");
|
||||
}
|
||||
|
||||
impl Serialize for Color {
|
||||
|
|
|
@ -439,11 +439,23 @@ fn test_key_serde() {
|
|||
);
|
||||
};
|
||||
($s:literal, err $v:literal) => {
|
||||
test_key!($s, err $v, "^")
|
||||
};
|
||||
($s:literal, err $v:literal, $extra:literal) => {
|
||||
assert_eq!(
|
||||
toml::from_str::<V>(std::concat!("k = \"", $s, "\""))
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
$v.to_string()
|
||||
std::concat!(
|
||||
"TOML parse error at line 1, column 5\n |\n1 | k = \"",
|
||||
$s,
|
||||
"\"\n | ",
|
||||
$extra,
|
||||
"^^^^\n",
|
||||
$v,
|
||||
'\n',
|
||||
)
|
||||
.to_string()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -468,9 +480,9 @@ fn test_key_serde() {
|
|||
test_key!("M-a", ok Key::Alt('a') );
|
||||
test_key!("F1", ok Key::F(1) );
|
||||
test_key!("F12", ok Key::F(12) );
|
||||
test_key!("C-V", err "`V` should be a lowercase and alphanumeric character instead. for key `k` at line 1 column 5");
|
||||
test_key!("M-V", err "`V` should be a lowercase and alphanumeric character instead. for key `k` at line 1 column 5");
|
||||
test_key!("F13", err "`13` should be a number 1 <= n <= 12 instead. for key `k` at line 1 column 5");
|
||||
test_key!("Fc", err "`c` should be a number 1 <= n <= 12 instead. for key `k` at line 1 column 5");
|
||||
test_key!("adsfsf", err "Cannot derive shortcut from `adsfsf`. Please consult the manual for valid key inputs. for key `k` at line 1 column 5");
|
||||
test_key!("C-V", err "`V` should be a lowercase and alphanumeric character instead.");
|
||||
test_key!("M-V", err "`V` should be a lowercase and alphanumeric character instead.");
|
||||
test_key!("F13", err "`13` should be a number 1 <= n <= 12 instead.");
|
||||
test_key!("Fc", err "`c` should be a number 1 <= n <= 12 instead.", "");
|
||||
test_key!("adsfsf", err "Cannot derive shortcut from `adsfsf`. Please consult the manual for valid key inputs.", "^^^^");
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ pub extern crate futures;
|
|||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
pub extern crate indexmap;
|
||||
pub extern crate serde_path_to_error;
|
||||
pub extern crate smallvec;
|
||||
pub extern crate smol;
|
||||
pub extern crate uuid;
|
||||
|
|
|
@ -191,7 +191,7 @@ pub struct SmtpExtensionSupport {
|
|||
#[serde(default = "crate::conf::true_val")]
|
||||
chunking: bool,
|
||||
/// [RFC 6152: SMTP Service Extension for 8-bit MIME Transport](https://www.rfc-editor.org/rfc/rfc6152)
|
||||
#[serde(default = "crate::conf::true_val")]
|
||||
#[serde(alias = "8bitmime", default = "crate::conf::true_val")]
|
||||
_8bitmime: bool,
|
||||
/// Essentially, the PRDR extension to SMTP allows (but does not require) an
|
||||
/// SMTP server to issue multiple responses after a message has been
|
||||
|
|
Loading…
Reference in New Issue