mithril_aggregator/commands/
mod.rs

1mod config_association;
2mod database_command;
3mod era_command;
4mod genesis_command;
5mod serve_command;
6mod tools_command;
7
8use anyhow::anyhow;
9use clap::{CommandFactory, Parser, Subcommand};
10use config::{builder::DefaultState, ConfigBuilder, Map, Source, Value};
11use mithril_cli_helper::{register_config_value, register_config_value_option};
12use mithril_common::StdResult;
13use mithril_doc::{Documenter, GenerateDocCommands, StructDoc};
14use slog::{debug, Level, Logger};
15use std::{collections::HashMap, path::PathBuf};
16
17use crate::{extract_all, DefaultConfiguration};
18
19/// Main command selector
20#[derive(Debug, Clone, Subcommand)]
21pub enum MainCommand {
22    Genesis(genesis_command::GenesisCommand),
23    Era(era_command::EraCommand),
24    Serve(serve_command::ServeCommand),
25    Tools(tools_command::ToolsCommand),
26    Database(database_command::DatabaseCommand),
27    #[clap(alias("doc"), hide(true))]
28    GenerateDoc(GenerateDocCommands),
29}
30/// Identifies the type of command
31pub enum CommandType {
32    /// Command that runs a server
33    Server,
34
35    /// Command that outputs some result after execution
36    CommandLine,
37}
38
39impl MainCommand {
40    pub async fn execute(
41        &self,
42        root_logger: Logger,
43        config_builder: ConfigBuilder<DefaultState>,
44    ) -> StdResult<()> {
45        match self {
46            Self::Genesis(cmd) => cmd.execute(root_logger, config_builder).await,
47            Self::Era(cmd) => cmd.execute(root_logger).await,
48            Self::Serve(cmd) => cmd.execute(root_logger, config_builder).await,
49            Self::Tools(cmd) => cmd.execute(root_logger, config_builder).await,
50            Self::Database(cmd) => cmd.execute(root_logger, config_builder).await,
51            Self::GenerateDoc(cmd) => {
52                let commands_configs =
53                    Self::extract_config(Self::format_crate_name_to_config_key());
54
55                cmd.execute_with_configurations(&mut MainOpts::command(), commands_configs)
56                    .map_err(|message| anyhow!(message))
57            }
58        }
59    }
60
61    pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
62        extract_all!(
63            command_path,
64            MainCommand,
65            Database = { database_command::DatabaseCommand },
66            Era = { era_command::EraCommand },
67            Genesis = { genesis_command::GenesisCommand },
68            Serve = { serve_command::ServeCommand },
69            Tools = { tools_command::ToolsCommand },
70            GenerateDoc = {},
71        )
72    }
73
74    fn format_crate_name_to_config_key() -> String {
75        env!("CARGO_PKG_NAME").replace("-", "")
76    }
77
78    pub fn command_type(&self) -> CommandType {
79        match self {
80            MainCommand::Serve(_) => CommandType::Server,
81            MainCommand::Genesis(_) => CommandType::CommandLine,
82            MainCommand::Era(_) => CommandType::CommandLine,
83            MainCommand::Tools(_) => CommandType::CommandLine,
84            MainCommand::Database(_) => CommandType::CommandLine,
85            MainCommand::GenerateDoc(_) => CommandType::CommandLine,
86        }
87    }
88}
89
90/// Mithril aggregator node
91#[derive(Documenter, Parser, Debug, Clone)]
92#[command(version)]
93pub struct MainOpts {
94    /// application main command
95    #[clap(subcommand)]
96    pub command: MainCommand,
97
98    /// Run Mode
99    #[clap(short, long, default_value = "dev")]
100    pub run_mode: String,
101
102    /// Verbosity level
103    #[clap(short, long, action = clap::ArgAction::Count)]
104    #[example = "Parsed from the number of occurrences: `-v` for `Warning`, `-vv` for `Info`, `-vvv` for `Debug` and `-vvvv` for `Trace`"]
105    pub verbose: u8,
106
107    /// Directory of the Cardano node files
108    #[clap(long)]
109    pub db_directory: Option<PathBuf>,
110
111    /// Directory where configuration file is located
112    #[clap(long, default_value = "./config")]
113    pub config_directory: PathBuf,
114}
115
116impl Source for MainOpts {
117    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
118        Box::new(self.clone())
119    }
120
121    fn collect(&self) -> Result<Map<String, Value>, config::ConfigError> {
122        let mut result = Map::new();
123        let namespace = "clap arguments".to_string();
124
125        register_config_value_option!(result, &namespace, self.db_directory, |v: PathBuf| format!(
126            "{}",
127            v.to_string_lossy()
128        ));
129
130        Ok(result)
131    }
132}
133
134impl MainOpts {
135    /// execute command
136    pub async fn execute(&self, root_logger: Logger) -> StdResult<()> {
137        let config_file_path = self
138            .config_directory
139            .join(format!("{}.json", self.run_mode));
140        let config_builder = config::Config::builder()
141            .add_source(DefaultConfiguration::default())
142            .add_source(
143                config::File::with_name(&config_file_path.to_string_lossy()).required(false),
144            )
145            .add_source(config::Environment::default().separator("__"))
146            .add_source(self.clone());
147        debug!(root_logger, "Started"; "run_mode" => &self.run_mode, "node_version" => env!("CARGO_PKG_VERSION"));
148
149        self.command.execute(root_logger, config_builder).await
150    }
151
152    /// get log level from parameters
153    pub fn log_level(&self) -> Level {
154        match self.verbose {
155            0 => Level::Error,
156            1 => Level::Warning,
157            2 => Level::Info,
158            3 => Level::Debug,
159            _ => Level::Trace,
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_format_crate_name_as_config_key() {
170        let crate_name = MainCommand::format_crate_name_to_config_key();
171
172        assert_eq!(crate_name, "mithrilaggregator");
173    }
174}