Files
escarmouche/src/main.rs
T

558 lines
16 KiB
Rust

use std::path::PathBuf;
use clap::parser::ValuesRef;
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use log::{debug, error, info};
use rusqlite::Connection;
use serde::Serialize;
use simple_logger::SimpleLogger;
use db::securities::list_securities;
use crate::db::users::{create_user, list_users};
use crate::migration::apply_migrations;
use crate::util::{CustomResult, Error};
use crate::webui::web_ui_main;
mod db;
mod event_processor;
mod migration;
mod services;
mod util;
mod webui;
mod events;
#[derive(Debug, Serialize)]
struct Output<T> {
status: String,
result: T,
}
impl<T> Output<T> {
fn success(shit: T) -> Output<T> {
return Output {
status: "success".to_string(),
result: shit,
};
}
}
trait CommandLineUserLogic {
fn create_user(
&mut self,
conn: &mut Connection,
name: &String,
public_key: &String,
) -> CustomResult<()>;
fn list_user(&mut self, conn: &Connection) -> CustomResult<()>;
fn list_securities(&mut self, conn: &Connection) -> CustomResult<()>;
fn process_events(
&mut self,
conn: &mut Connection,
files: ValuesRef<PathBuf>,
) -> CustomResult<()>;
fn run_webui(
&mut self,
conn: &mut Connection,
port: u16,
enable_template_autoreload: bool,
) -> CustomResult<()>;
}
fn verbosity_arg() -> Arg {
return Arg::new("verbosity")
.short('v')
.action(ArgAction::Count)
.default_value("0");
}
fn get_verbosity(m: &ArgMatches) -> log::LevelFilter {
match m.get_one::<u8>("verbosity") {
None => log::LevelFilter::Error,
Some(1) => log::LevelFilter::Warn,
Some(2) => log::LevelFilter::Info,
Some(3) => log::LevelFilter::Debug,
Some(4) => log::LevelFilter::Trace,
_ => log::LevelFilter::Error,
}
}
fn db_path_arg() -> Arg {
return Arg::new("db-path")
.long("db-path")
.value_name("FILE")
.value_parser(value_parser!(PathBuf))
.default_value("ledger.sqlite");
}
fn get_db_path(m: &ArgMatches) -> &PathBuf {
return m.get_one::<PathBuf>("db-path").unwrap();
}
fn new_user_command_line_parser() -> Command {
return Command::new("new")
.long_flag("new")
.about("Create new user.")
.arg(
Arg::new("name")
.long("name")
.required(true)
.help("name of new user.")
.action(ArgAction::Set),
)
.arg(
Arg::new("public_key")
.required(true)
.long("public-key")
.help("public key of users.")
.action(ArgAction::Set),
);
}
fn ls_user_command_line_parser() -> Command {
return Command::new("ls").long_flag("ls").about("List all users.");
}
fn process_events_command_line_parser() -> Command {
Command::new("process")
.about("Process events one after the other. In the order received")
.arg_required_else_help(true)
.arg(clap::arg!(<PATH> ... "Stuff to add").value_parser(value_parser!(PathBuf)))
}
fn user_command_line_parser() -> Command {
return Command::new("users")
.about("Handy command about users.")
.subcommand(new_user_command_line_parser())
.subcommand(ls_user_command_line_parser())
.arg_required_else_help(true);
}
fn event_command_line_parser() -> Command {
return Command::new("events")
.about("Process and query events files")
.subcommand(process_events_command_line_parser())
.arg_required_else_help(true);
}
fn ls_securities_command_line_parser() -> Command {
return Command::new("ls")
.long_flag("ls")
.about("List all securities.");
}
fn run_web_ui_command_line_parser() -> Command {
return Command::new("run")
.arg(
Arg::new("port")
.long("port")
.value_parser(clap::value_parser!(u16).range(2000..))
.action(ArgAction::Set)
.default_value("2222"),
)
.arg(
Arg::new("enable-template-autoreload")
.long("enable-template-autoreload")
.required(false)
.num_args(0)
.help("Reload underlying template (for development)"),
)
.about("Run web user interface.");
}
fn securities_command_line_parser() -> Command {
return Command::new("securities")
.about("Handy command about securities.")
.subcommand(ls_securities_command_line_parser())
.arg_required_else_help(true);
}
fn web_ui_command_line_parser() -> Command {
return Command::new("wui")
.about("Web user interface.")
.subcommand(run_web_ui_command_line_parser())
.arg_required_else_help(true);
}
fn command_line_parser() -> Command {
return Command::new("prog")
.arg(verbosity_arg())
.arg(db_path_arg())
.subcommand(event_command_line_parser())
.subcommand(user_command_line_parser())
.subcommand(securities_command_line_parser())
.subcommand(web_ui_command_line_parser())
.arg_required_else_help(true);
}
fn process_new_user_command_line(
conn: &mut Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing new user command line {:?}.", m);
let name = m.get_one::<String>("name").unwrap();
let pk = m.get_one::<String>("public_key").unwrap();
logic.create_user(conn, name, pk)
}
fn process_list_user_command_line(
conn: &Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing list users command line {:?}.", m);
logic.list_user(conn)
}
fn process_user_command_line(
conn: &mut Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing user command line {:?}.", m);
match m.subcommand() {
Some(("new", new_user_match)) => process_new_user_command_line(conn, new_user_match, logic),
Some(("ls", list_user_match)) => {
process_list_user_command_line(conn, list_user_match, logic)
}
_ => unreachable!(),
}
}
fn process_list_securities_command_line(
conn: &Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing list securities command line {:?}.", m);
logic.list_securities(conn)
}
fn process_process_events_command_line(
conn: &mut Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing process event command line {:?}.", m);
let mut files = m.get_many::<PathBuf>("PATH").unwrap();
if files.any(|x| !x.exists()) {
error!("Some passed files do not exists. Existing right now.");
return Err(Error::UserInputError(
"Some passed files do not exists. Existing right now.".to_string(),
));
}
logic.process_events(conn, files)
}
fn process_run_wui_command_line(
conn: &mut Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing run wui command line {:?}.", m);
let port = m.get_one::<u16>("port").unwrap_or(&2222);
let enable_template_autoreload = m.get_flag("enable-template-autoreload");
logic.run_webui(conn, *port, enable_template_autoreload)
}
fn process_securities_command_line(
conn: &Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing securities command line {:?}.", m);
match m.subcommand() {
Some(("ls", list_securities_match)) => {
process_list_securities_command_line(conn, list_securities_match, logic)
}
_ => unreachable!(),
}
}
fn process_events_command_line(
conn: &mut Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing events command line {:?}.", m);
match m.subcommand() {
Some(("process", process_events_match)) => {
process_process_events_command_line(conn, process_events_match, logic)
}
_ => unreachable!(),
}
}
fn process_wui_command_line(
conn: &mut Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing wui command line {:?}.", m);
match m.subcommand() {
Some(("run", run_wui_match)) => process_run_wui_command_line(conn, run_wui_match, logic),
_ => unreachable!(),
}
}
fn process_command_line(
conn: &mut Connection,
m: &ArgMatches,
logic: &mut dyn CommandLineUserLogic,
) -> CustomResult<()> {
debug!("Processing command line {:?}.", m);
match m.subcommand() {
Some(("users", user_match)) => process_user_command_line(conn, user_match, logic),
Some(("securities", securities_match)) => {
process_securities_command_line(conn, securities_match, logic)
}
Some(("events", events_match)) => process_events_command_line(conn, events_match, logic),
Some(("wui", wui_match)) => process_wui_command_line(conn, wui_match, logic),
_ => {
println!("Received un parsable command. {:?}", m.subcommand());
unreachable!();
}
}
}
#[cfg(test)]
mod cli_test {
use super::*;
struct TestCommandLineLogic {
list_user_count: usize,
create_user_count: usize,
list_securities_count: usize,
process_events_count: usize,
run_webui_count: usize,
}
impl TestCommandLineLogic {
fn new() -> TestCommandLineLogic {
return TestCommandLineLogic {
list_user_count: 0,
create_user_count: 0,
list_securities_count: 0,
process_events_count: 0,
run_webui_count: 0,
};
}
}
impl CommandLineUserLogic for TestCommandLineLogic {
fn create_user(
&mut self,
_conn: &mut Connection,
_name: &String,
_public_key: &String,
) -> CustomResult<()> {
self.create_user_count += 1;
Ok(())
}
fn list_user(&mut self, _conn: &Connection) -> CustomResult<()> {
self.list_user_count += 1;
Ok(())
}
fn list_securities(&mut self, _conn: &Connection) -> CustomResult<()> {
self.list_securities_count += 1;
Ok(())
}
fn process_events(
&mut self,
_conn: &mut Connection,
_files: ValuesRef<PathBuf>,
) -> CustomResult<()> {
self.process_events_count += 1;
Ok(())
}
fn run_webui(
&mut self,
_conn: &mut Connection,
_port: u16,
_enable_template_autoreload: bool,
) -> CustomResult<()> {
self.run_webui_count += 1;
Ok(())
}
}
#[test]
fn verbosity() {
{
let cmd = Command::new("test").arg(verbosity_arg());
let m = cmd.get_matches_from(vec!["test", "-vv"]);
assert_eq!(get_verbosity(&m), log::LevelFilter::Info);
}
{
let cmd = Command::new("test").arg(verbosity_arg());
let m = cmd.get_matches_from(vec!["test"]);
assert_eq!(get_verbosity(&m), log::LevelFilter::Error);
}
}
#[test]
fn db_path() {
{
let cmd = Command::new("test").arg(db_path_arg());
let m = cmd.get_matches_from(vec!["test", "--db-path", "/foo/bar"]);
let expt = PathBuf::from("/foo/bar");
assert_eq!(get_db_path(&m), &expt);
}
{
let cmd = Command::new("test").arg(db_path_arg());
let m = cmd.get_matches_from(vec!["test"]);
let expt = PathBuf::from("ledger.sqlite");
assert_eq!(get_db_path(&m), &expt);
}
}
#[test]
fn create_user() {
let cmd = command_line_parser();
let m = cmd.get_matches_from(vec![
"test",
"users",
"new",
"--name",
"paul",
"--public-key",
"123abc",
]);
let mut logic = TestCommandLineLogic::new();
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.create_user_count, 1);
}
#[test]
fn list_users() {
let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "users", "ls"]);
let mut logic = TestCommandLineLogic::new();
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.list_user_count, 1);
}
#[test]
fn list_securities() {
let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "securities", "ls"]);
let mut logic = TestCommandLineLogic::new();
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.list_securities_count, 1);
}
#[test]
fn process_events() {
let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "events", "process", "/dev/null"]);
let mut logic = TestCommandLineLogic::new();
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.process_events_count, 1);
}
#[test]
fn run_web_ui() {
let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "wui", "run"]);
let mut logic = TestCommandLineLogic::new();
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.run_webui_count, 1);
}
}
struct ProCommandLineLogic {}
impl ProCommandLineLogic {
fn new() -> ProCommandLineLogic {
return ProCommandLineLogic {};
}
}
impl CommandLineUserLogic for ProCommandLineLogic {
fn create_user(
&mut self,
conn: &mut Connection,
name: &String,
public_key: &String,
) -> CustomResult<()> {
let tx = conn.transaction().map_err(Error::SqlError)?;
create_user(&tx, name, public_key)?;
tx.commit().map_err(Error::SqlError)
}
fn list_user(&mut self, conn: &Connection) -> CustomResult<()> {
let users = list_users(conn)?;
let out = Output::success(users);
println!("{}", serde_json::to_string(&out).unwrap());
Ok(())
}
fn list_securities(&mut self, conn: &Connection) -> CustomResult<()> {
let securities = list_securities(conn)?;
let out = Output::success(securities);
println!("{}", serde_json::to_string(&out).unwrap());
Ok(())
}
fn process_events(
&mut self,
conn: &mut Connection,
files: ValuesRef<PathBuf>,
) -> CustomResult<()> {
// process_event_files(conn, files)
Ok(())
}
fn run_webui(
&mut self,
conn: &mut Connection,
port: u16,
enable_template_autoreload: bool,
) -> CustomResult<()> {
info!("Serving web-ui on port {:?}", port);
web_ui_main(conn, port, enable_template_autoreload)
}
}
fn get_connection(m: &ArgMatches) -> Connection {
let path = get_db_path(&m);
info!("Sqlite database location is {:?}.", path);
return Connection::open(path).unwrap();
}
fn set_logger(m: &ArgMatches) {
SimpleLogger::new()
.with_level(get_verbosity(&m))
.init()
.unwrap();
}
fn main() {
let command = command_line_parser();
let m = command.get_matches();
set_logger(&m);
let mut conn = get_connection(&m);
apply_migrations(&mut conn).unwrap();
let mut logic = ProCommandLineLogic::new();
process_command_line(&mut conn, &m, &mut logic);
}