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. /// A hashmap containing all available mail backends.
/// An abstraction over any available backends. /// An abstraction over any available backends.
pub struct 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 { impl Default for Backends {
@ -76,28 +81,40 @@ impl Backends {
{ {
b.register( b.register(
"maildir".to_string(), "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")] #[cfg(feature = "mbox_backend")]
{ {
b.register( b.register(
"mbox".to_string(), "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")] #[cfg(feature = "imap_backend")]
{ {
b.register( b.register(
"imap".to_string(), "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")] #[cfg(feature = "notmuch_backend")]
{ {
b.register( b.register(
"notmuch".to_string(), "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 b
@ -107,15 +124,23 @@ impl Backends {
if !self.map.contains_key(key) { if !self.map.contains_key(key) {
panic!("{} is not a valid mail backend", 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) { if self.map.contains_key(&key) {
panic!("{} is an already registered backend", key); panic!("{} is an already registered backend", key);
} }
self.map.insert(key, backend); 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)] #[derive(Debug)]

View File

@ -84,6 +84,34 @@ impl std::ops::Deref for IsSubscribedFn {
} }
type Capabilities = FnvHashSet<Vec<u8>>; 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)] #[derive(Debug)]
pub struct UIDStore { pub struct UIDStore {
uidvalidity: Arc<Mutex<FnvHashMap<FolderHash, UID>>>, uidvalidity: Arc<Mutex<FnvHashMap<FolderHash, UID>>>,
@ -313,7 +341,9 @@ impl MailBackend for ImapType {
let path = { let path = {
let folders = self.folders.read().unwrap(); 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 if f_result
.map(|f| !f.permissions.lock().unwrap().create_messages) .map(|f| !f.permissions.lock().unwrap().create_messages)
.unwrap_or(false) .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 { impl ImapType {
pub fn new( pub fn new(
s: &AccountSettings, s: &AccountSettings,
@ -609,4 +611,14 @@ impl ImapType {
} }
Err(MeliError::new(response)) 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(); writer.write_all(bytes).unwrap();
return Ok(()); 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( fn add_path_to_index(

View File

@ -432,9 +432,14 @@ impl MailBackend for MboxType {
work_context: WorkContext, work_context: WorkContext,
) -> Result<std::thread::ThreadId> { ) -> Result<std::thread::ThreadId> {
let (tx, rx) = channel(); 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() { 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()); debug!("watching {:?}", f.path.as_path());
} }
let index = self.index.clone(); let index = self.index.clone();
@ -646,4 +651,16 @@ impl MboxType {
*/ */
Ok(Box::new(ret)) 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 mut database: *mut notmuch_database_t = std::ptr::null_mut();
let path = Path::new(s.root_folder.as_str()).expand().to_path_buf(); let path = Path::new(s.root_folder.as_str()).expand().to_path_buf();
if !path.exists() { if !path.exists() {
panic!( return Err(MeliError::new(format!(
"\"root_folder\" {} for account {} is not a valid path.", "\"root_folder\" {} for account {} is not a valid path.",
s.root_folder.as_str(), s.root_folder.as_str(),
s.name() s.name()
); )));
} }
let path_c = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let path_c = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
@ -170,6 +170,26 @@ impl NotmuchDb {
save_messages_to: None, 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 { impl MailBackend for NotmuchDb {

View File

@ -328,12 +328,40 @@ impl FileSettings {
let mut file = File::open(path)?; let mut file = File::open(path)?;
let mut contents = String::new(); let mut contents = String::new();
file.read_to_string(&mut contents)?; file.read_to_string(&mut contents)?;
let s: std::result::Result<FileSettings, toml::de::Error> = toml::from_str(&contents); let s: FileSettings = toml::from_str(&contents).map_err(|e| {
if let Err(e) = s { MeliError::new(format!("Config file contains errors: {}", e.to_string()))
return Err(MeliError::new(format!( })?;
"Config file contains errors: {}", let backends = melib::backends::Backends::new();
e.to_string() 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(()) Ok(())

View File

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