bin: add backend specific validation functions for --test-config flag

jmap
Manos Pitsidianakis 2019-11-27 14:22:53 +02:00
parent 4677f9c6bb
commit ba52c59859
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
7 changed files with 171 additions and 50 deletions

View File

@ -58,7 +58,12 @@ pub type BackendCreator = Box<
/// A hashmap containing all available mail backends.
/// An abstraction over any available backends.
pub struct Backends {
map: FnvHashMap<std::string::String, Box<dyn Fn() -> BackendCreator>>,
map: FnvHashMap<std::string::String, Backend>,
}
pub struct Backend {
pub create_fn: Box<dyn Fn() -> BackendCreator>,
pub validate_conf_fn: Box<dyn Fn(&AccountSettings) -> Result<()>>,
}
impl Default for Backends {
@ -76,28 +81,40 @@ impl Backends {
{
b.register(
"maildir".to_string(),
Box::new(|| Box::new(|f, i| MaildirType::new(f, i))),
Backend {
create_fn: Box::new(|| Box::new(|f, i| MaildirType::new(f, i))),
validate_conf_fn: Box::new(MaildirType::validate_config),
},
);
}
#[cfg(feature = "mbox_backend")]
{
b.register(
"mbox".to_string(),
Box::new(|| Box::new(|f, i| MboxType::new(f, i))),
Backend {
create_fn: Box::new(|| Box::new(|f, i| MboxType::new(f, i))),
validate_conf_fn: Box::new(MboxType::validate_config),
},
);
}
#[cfg(feature = "imap_backend")]
{
b.register(
"imap".to_string(),
Box::new(|| Box::new(|f, i| ImapType::new(f, i))),
Backend {
create_fn: Box::new(|| Box::new(|f, i| ImapType::new(f, i))),
validate_conf_fn: Box::new(ImapType::validate_config),
},
);
}
#[cfg(feature = "notmuch_backend")]
{
b.register(
"notmuch".to_string(),
Box::new(|| Box::new(|f, i| NotmuchDb::new(f, i))),
Backend {
create_fn: Box::new(|| Box::new(|f, i| NotmuchDb::new(f, i))),
validate_conf_fn: Box::new(NotmuchDb::validate_config),
},
);
}
b
@ -107,15 +124,23 @@ impl Backends {
if !self.map.contains_key(key) {
panic!("{} is not a valid mail backend", key);
}
self.map[key]()
(self.map[key].create_fn)()
}
pub fn register(&mut self, key: String, backend: Box<dyn Fn() -> BackendCreator>) {
pub fn register(&mut self, key: String, backend: Backend) {
if self.map.contains_key(&key) {
panic!("{} is an already registered backend", key);
}
self.map.insert(key, backend);
}
pub fn validate_config(&self, key: &str, s: &AccountSettings) -> Result<()> {
(self
.map
.get(key)
.ok_or_else(|| MeliError::new(format!("{} is not a valid mail backend", key)))?
.validate_conf_fn)(s)
}
}
#[derive(Debug)]

View File

@ -84,6 +84,34 @@ impl std::ops::Deref for IsSubscribedFn {
}
type Capabilities = FnvHashSet<Vec<u8>>;
macro_rules! get_conf_val {
($s:ident[$var:literal]) => {
$s.extra.get($var).ok_or_else(|| {
MeliError::new(format!(
"Configuration error ({}): IMAP connection requires the field `{}` set",
$s.name.as_str(),
$var
))
})
};
($s:ident[$var:literal], $default:expr) => {
$s.extra
.get($var)
.map(|v| {
<_>::from_str(v).map_err(|e| {
MeliError::new(format!(
"Configuration error ({}): Invalid value for field `{}`: {}\n{}",
$s.name.as_str(),
$var,
v,
e
))
})
})
.unwrap_or_else(|| Ok($default))
};
}
#[derive(Debug)]
pub struct UIDStore {
uidvalidity: Arc<Mutex<FnvHashMap<FolderHash, UID>>>,
@ -313,7 +341,9 @@ impl MailBackend for ImapType {
let path = {
let folders = self.folders.read().unwrap();
let f_result = folders.values().find(|v| v.name == folder);
let f_result = folders
.values()
.find(|v| v.path == folder || v.name == folder);
if f_result
.map(|f| !f.permissions.lock().unwrap().create_messages)
.unwrap_or(false)
@ -417,34 +447,6 @@ impl MailBackend for ImapType {
}
}
macro_rules! get_conf_val {
($s:ident[$var:literal]) => {
$s.extra.get($var).ok_or_else(|| {
MeliError::new(format!(
"Configuration error ({}): IMAP connection requires the field `{}` set",
$s.name.as_str(),
$var
))
})
};
($s:ident[$var:literal], $default:expr) => {
$s.extra
.get($var)
.map(|v| {
<_>::from_str(v).map_err(|e| {
MeliError::new(format!(
"Configuration error ({}): Invalid value for field `{}`: {}\n{}",
$s.name.as_str(),
$var,
v,
e
))
})
})
.unwrap_or_else(|| Ok($default))
};
}
impl ImapType {
pub fn new(
s: &AccountSettings,
@ -609,4 +611,14 @@ impl ImapType {
}
Err(MeliError::new(response))
}
pub fn validate_config(s: &AccountSettings) -> Result<()> {
get_conf_val!(s["server_hostname"])?;
get_conf_val!(s["server_username"])?;
get_conf_val!(s["server_password"])?;
get_conf_val!(s["server_port"], 143)?;
get_conf_val!(s["use_starttls"], false)?;
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
Ok(())
}
}

View File

@ -877,6 +877,25 @@ impl MaildirType {
writer.write_all(bytes).unwrap();
return Ok(());
}
pub fn validate_config(s: &AccountSettings) -> Result<()> {
let root_path = PathBuf::from(s.root_folder()).expand();
if !root_path.exists() {
return Err(MeliError::new(format!(
"Configuration error ({}): root_path `{}` is not a valid directory.",
s.name(),
s.root_folder.as_str()
)));
} else if !root_path.is_dir() {
return Err(MeliError::new(format!(
"Configuration error ({}): root_path `{}` is not a directory.",
s.name(),
s.root_folder.as_str()
)));
}
Ok(())
}
}
fn add_path_to_index(

View File

@ -432,9 +432,14 @@ impl MailBackend for MboxType {
work_context: WorkContext,
) -> Result<std::thread::ThreadId> {
let (tx, rx) = channel();
let mut watcher = watcher(tx, std::time::Duration::from_secs(10)).unwrap();
let mut watcher = watcher(tx, std::time::Duration::from_secs(10))
.map_err(|e| e.to_string())
.map_err(MeliError::new)?;
for f in self.folders.lock().unwrap().values() {
watcher.watch(&f.path, RecursiveMode::Recursive).unwrap();
watcher
.watch(&f.path, RecursiveMode::Recursive)
.map_err(|e| e.to_string())
.map_err(MeliError::new)?;
debug!("watching {:?}", f.path.as_path());
}
let index = self.index.clone();
@ -646,4 +651,16 @@ impl MboxType {
*/
Ok(Box::new(ret))
}
pub fn validate_config(s: &AccountSettings) -> Result<()> {
let path = Path::new(s.root_folder.as_str()).expand();
if !path.exists() {
return Err(MeliError::new(format!(
"\"root_folder\" {} for account {} is not a valid path.",
s.root_folder.as_str(),
s.name()
)));
}
Ok(())
}
}

View File

@ -111,11 +111,11 @@ impl NotmuchDb {
let mut database: *mut notmuch_database_t = std::ptr::null_mut();
let path = Path::new(s.root_folder.as_str()).expand().to_path_buf();
if !path.exists() {
panic!(
return Err(MeliError::new(format!(
"\"root_folder\" {} for account {} is not a valid path.",
s.root_folder.as_str(),
s.name()
);
)));
}
let path_c = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
@ -170,6 +170,26 @@ impl NotmuchDb {
save_messages_to: None,
}))
}
pub fn validate_config( s: &AccountSettings) -> Result<()> {
let path = Path::new(s.root_folder.as_str()).expand().to_path_buf();
if !path.exists() {
return Err(MeliError::new(format!(
"\"root_folder\" {} for account {} is not a valid path.",
s.root_folder.as_str(),
s.name()
)));
}
for (k, f) in s.folders.iter() {
if f.extra.get("query").is_none() {
return Err(MeliError::new(format!(
"notmuch folder configuration entry \"{}\" should have a \"query\" value set.",
k
)));
}
}
Ok(())
}
}
impl MailBackend for NotmuchDb {

View File

@ -328,12 +328,40 @@ impl FileSettings {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let s: std::result::Result<FileSettings, toml::de::Error> = toml::from_str(&contents);
if let Err(e) = s {
return Err(MeliError::new(format!(
"Config file contains errors: {}",
e.to_string()
)));
let s: FileSettings = toml::from_str(&contents).map_err(|e| {
MeliError::new(format!("Config file contains errors: {}", e.to_string()))
})?;
let backends = melib::backends::Backends::new();
for (name, acc) in s.accounts {
let FileAccount {
root_folder,
format,
identity,
read_only,
display_name,
subscribed_folders,
folders,
extra,
index_style: _,
cache_type: _,
} = acc;
let lowercase_format = format.to_lowercase();
let s = AccountSettings {
name,
root_folder,
format: format.clone(),
identity,
read_only,
display_name,
subscribed_folders,
folders: folders
.into_iter()
.map(|(k, v)| (k, v.folder_conf))
.collect(),
extra,
};
backends.validate_config(&lowercase_format, &s)?;
}
Ok(())

View File

@ -10,13 +10,13 @@ pub struct Shortcuts {
pub listing: ListingShortcuts,
#[serde(default)]
pub composing: ComposingShortcuts,
#[serde(default)]
#[serde(default, alias = "compact-listing")]
pub compact_listing: CompactListingShortcuts,
#[serde(default)]
#[serde(default, alias = "contact-list")]
pub contact_list: ContactListShortcuts,
#[serde(default)]
#[serde(default, alias = "envelope-view")]
pub envelope_view: EnvelopeViewShortcuts,
#[serde(default)]
#[serde(default, alias = "thread-view")]
pub thread_view: ThreadViewShortcuts,
#[serde(default)]
pub pager: PagerShortcuts,