bin: add backend specific validation functions for --test-config flag
parent
4677f9c6bb
commit
ba52c59859
|
@ -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)]
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue