core: split commands in their own module

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
add-rfc9500-test-keys
Manos Pitsidianakis 2023-10-31 00:36:42 +02:00
parent 250eb0a2ab
commit 6f13cd1e31
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
10 changed files with 1357 additions and 1025 deletions

View File

@ -102,12 +102,7 @@ pub enum Command {
#[arg(long)]
dry_run: bool,
},
/// Mail that has not been handled properly end up in the error queue.
ErrorQueue {
#[command(subcommand)]
cmd: QueueCommand,
},
/// Mail that has not been handled properly end up in the error queue.
/// Processed mail is stored in queues.
Queue {
#[arg(long, value_parser = QueueValueParser)]
queue: mailpot::queue::Queue,
@ -275,9 +270,6 @@ pub enum QueueCommand {
/// index of entry.
#[arg(long)]
index: Vec<i64>,
/// Do not print in stdout.
#[arg(long)]
quiet: bool,
},
}

1083
cli/src/commands.rs 100644

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,8 @@ extern crate ureq;
pub use std::path::PathBuf;
mod args;
pub mod commands;
pub mod import;
pub mod lints;
pub use args::*;
pub use clap::{Args, CommandFactory, Parser, Subcommand};

View File

@ -17,7 +17,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use super::*;
use mailpot::{
chrono,
melib::{self, Envelope},
models::{Account, DbVal, ListSubscription, MailingList},
rusqlite, Connection, Result,
};
pub fn datetime_header_value_lint(db: &mut Connection, dry_run: bool) -> Result<()> {
let mut col = vec![];

File diff suppressed because it is too large Load Diff

View File

@ -104,6 +104,31 @@ For more information, try '--help'."#,
let config_str = config.to_toml();
fn config_not_exists(conf: &Path) {
let mut cmd = Command::cargo_bin("mpot").unwrap();
let output = cmd
.arg("-c")
.arg(conf)
.arg("list-lists")
.output()
.unwrap()
.assert();
output.code(255).stderr(predicates::str::is_empty()).stdout(
predicate::eq(
format!(
"[1] Could not read configuration file from path: {} Caused by:\n[2] Error \
returned from internal I/O operation: No such file or directory (os error 2)",
conf.display()
)
.as_str(),
)
.trim()
.normalize(),
);
}
config_not_exists(&conf_path);
std::fs::write(&conf_path, config_str.as_bytes()).unwrap();
fn list_lists(conf: &Path, eq: &str) {
@ -178,4 +203,65 @@ For more information, try '--help'."#,
\"twobar-chat@example.com\", topics: [], description: None, archive_url: None }, \
2)\n\tList owners: None\n\tPost policy: None\n\tSubscription policy: None",
);
fn add_list_owner(conf: &Path) {
let mut cmd = Command::cargo_bin("mpot").unwrap();
let output = cmd
.arg("-c")
.arg(conf)
.arg("list")
.arg("twobar-chat")
.arg("add-list-owner")
.arg("--address")
.arg("list-owner@example.com")
.output()
.unwrap()
.assert();
output.code(0).stderr(predicates::str::is_empty()).stdout(
predicate::eq("Added new list owner [#1 2] list-owner@example.com")
.trim()
.normalize(),
);
}
add_list_owner(&conf_path);
list_lists(
&conf_path,
"- foo-chat DbVal(MailingList { pk: 1, name: \"foobar chat\", id: \"foo-chat\", address: \
\"foo-chat@example.com\", topics: [], description: None, archive_url: None }, 1)\n\tList \
owners: None\n\tPost policy: None\n\tSubscription policy: None\n\n- twobar-chat \
DbVal(MailingList { pk: 2, name: \"twobar\", id: \"twobar-chat\", address: \
\"twobar-chat@example.com\", topics: [], description: None, archive_url: None }, \
2)\n\tList owners:\n\t- [#1 2] list-owner@example.com\n\tPost policy: \
None\n\tSubscription policy: None",
);
fn remove_list_owner(conf: &Path) {
let mut cmd = Command::cargo_bin("mpot").unwrap();
let output = cmd
.arg("-c")
.arg(conf)
.arg("list")
.arg("twobar-chat")
.arg("remove-list-owner")
.arg("--pk")
.arg("1")
.output()
.unwrap()
.assert();
output.code(0).stderr(predicates::str::is_empty()).stdout(
predicate::eq("Removed list owner with pk = 1")
.trim()
.normalize(),
);
}
remove_list_owner(&conf_path);
list_lists(
&conf_path,
"- foo-chat DbVal(MailingList { pk: 1, name: \"foobar chat\", id: \"foo-chat\", address: \
\"foo-chat@example.com\", topics: [], description: None, archive_url: None }, 1)\n\tList \
owners: None\n\tPost policy: None\n\tSubscription policy: None\n\n- twobar-chat \
DbVal(MailingList { pk: 2, name: \"twobar\", id: \"twobar-chat\", address: \
\"twobar-chat@example.com\", topics: [], description: None, archive_url: None }, \
2)\n\tList owners: None\n\tPost policy: None\n\tSubscription policy: None",
);
}

View File

@ -78,10 +78,12 @@ impl Configuration {
let mut s = String::new();
let mut file = std::fs::File::open(path)?;
file.read_to_string(&mut s)?;
let config: Self = toml::from_str(&s).context(format!(
"Could not parse configuration file `{}` succesfully: ",
path.display()
))?;
let config: Self = toml::from_str(&s)
.map_err(anyhow::Error::from)
.context(format!(
"Could not parse configuration file `{}` successfully: ",
path.display()
))?;
Ok(config)
}
@ -127,3 +129,29 @@ impl Configuration {
.to_string()
}
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
use super::*;
#[test]
fn test_config_parse_error() {
let tmp_dir = TempDir::new().unwrap();
let conf_path = tmp_dir.path().join("conf.toml");
std::fs::write(&conf_path, b"afjsad skas as a as\n\n\n\n\t\x11\n").unwrap();
assert_eq!(
Configuration::from_file(&conf_path)
.unwrap_err()
.display_chain()
.to_string(),
format!(
"[1] Could not parse configuration file `{}` successfully: Caused by:\n[2] \
Error: expected an equals, found an identifier at line 1 column 8\n",
conf_path.display()
),
);
}
}

View File

@ -617,7 +617,7 @@ impl Connection {
if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
Error::from(err).chain_err(|| NotFound("list or list owner not found!"))
} else {
err.into()
Error::from(err)
}
})?;
Ok(())

View File

@ -23,8 +23,6 @@ use std::sync::Arc;
use thiserror::Error;
pub use crate::anyhow::Context;
/// Mailpot library error.
#[derive(Error, Debug)]
pub struct Error {
@ -53,39 +51,36 @@ pub enum ErrorKind {
/// Error returned from an external user initiated operation such as
/// deserialization or I/O.
#[error(
"Error returned from an external user initiated operation such as deserialization or I/O. \
{0}"
)]
#[error("Error: {0}")]
External(#[from] anyhow::Error),
/// Generic
#[error("{0}")]
Generic(anyhow::Error),
/// Error returned from sqlite3.
#[error("Error returned from sqlite3 {0}.")]
#[error("Error returned from sqlite3: {0}.")]
Sql(
#[from]
#[source]
rusqlite::Error,
),
/// Error returned from sqlite3.
#[error("Error returned from sqlite3. {0}")]
#[error("Error returned from sqlite3: {0}")]
SqlLib(
#[from]
#[source]
rusqlite::ffi::Error,
),
/// Error returned from internal I/O operations.
#[error("Error returned from internal I/O operations. {0}")]
#[error("Error returned from internal I/O operation: {0}")]
Io(#[from] ::std::io::Error),
/// Error returned from e-mail protocol operations from `melib` crate.
#[error("Error returned from e-mail protocol operations from `melib` crate. {0}")]
#[error("Error returned from e-mail protocol operations from `melib` crate: {0}")]
Melib(#[from] melib::error::Error),
/// Error from deserializing JSON values.
#[error("Error from deserializing JSON values. {0}")]
#[error("Error from deserializing JSON values: {0}")]
SerdeJson(#[from] serde_json::Error),
/// Error returned from minijinja template engine.
#[error("Error returned from minijinja template engine. {0}")]
#[error("Error returned from minijinja template engine: {0}")]
Template(#[from] minijinja::Error),
}
@ -188,7 +183,7 @@ struct ErrorChainDisplay<'e> {
impl std::fmt::Display for ErrorChainDisplay<'_> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(ref source) = self.current.source {
writeln!(fmt, "[{}] {}, caused by:", self.counter, self.current.kind)?;
writeln!(fmt, "[{}] {} Caused by:", self.counter, self.current.kind)?;
Self {
current: source,
counter: self.counter + 1,
@ -200,3 +195,38 @@ impl std::fmt::Display for ErrorChainDisplay<'_> {
}
}
}
/// adfsa
pub trait Context<T> {
/// Wrap the error value with additional context.
fn context<C>(self, context: C) -> Result<T>
where
C: Into<Error>;
/// Wrap the error value with additional context that is evaluated lazily
/// only once an error does occur.
fn with_context<C, F>(self, f: F) -> Result<T>
where
C: Into<Error>,
F: FnOnce() -> C;
}
impl<T, E> Context<T> for std::result::Result<T, E>
where
Error: From<E>,
{
fn context<C>(self, context: C) -> Result<T>
where
C: Into<Error>,
{
self.map_err(|err| Error::from(err).chain_err(|| context.into()))
}
fn with_context<C, F>(self, f: F) -> Result<T>
where
C: Into<Error>,
F: FnOnce() -> C,
{
self.map_err(|err| Error::from(err).chain_err(|| f().into()))
}
}

View File

@ -705,61 +705,6 @@ Show e\-mail processing result without actually consuming it.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\fB
.SS mpot error-queue
.\fR
.br
.br
Mail that has not been handled properly end up in the error queue.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\fB
.SS mpot error-queue list
.\fR
.br
.br
List.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\fB
.SS mpot error-queue print
.\fR
.br
.br
mpot error\-queue print [\-\-index \fIINDEX\fR]
.br
Print entry in RFC5322 or JSON format.
.TP
\-\-index \fIINDEX\fR
index of entry.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\fB
.SS mpot error-queue delete
.\fR
.br
.br
mpot error\-queue delete [\-\-index \fIINDEX\fR] [\-\-quiet \fIQUIET\fR]
.br
Delete entry and print it in stdout.
.TP
\-\-index \fIINDEX\fR
index of entry.
.TP
\-\-quiet
Do not print in stdout.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\fB
.SS mpot queue
.\fR
.br
@ -769,7 +714,7 @@ Do not print in stdout.
mpot queue \-\-queue \fIQUEUE\fR
.br
Mail that has not been handled properly end up in the error queue.
Processed mail is stored in queues.
.TP
\-\-queue \fIQUEUE\fR
@ -810,16 +755,13 @@ index of entry.
.br
mpot queue delete [\-\-index \fIINDEX\fR] [\-\-quiet \fIQUIET\fR]
mpot queue delete [\-\-index \fIINDEX\fR]
.br
Delete entry and print it in stdout.
.TP
\-\-index \fIINDEX\fR
index of entry.
.TP
\-\-quiet
Do not print in stdout.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\fB