Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use derive apis to parse command line arguments #1326

Merged
merged 1 commit into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions common/config-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = "4.3"
reqwest = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
96 changes: 92 additions & 4 deletions common/config-parser/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::collections::HashMap;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::{
collections::HashMap, ffi::OsStr, io, marker::PhantomData, net::SocketAddr, path::PathBuf,
};

use serde::Deserialize;
use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
use serde::{de, Deserialize};
use tentacle_multiaddr::MultiAddr;

use protocol::types::{Hex, H160, U256};

use crate::parse_file;

pub const DEFAULT_BROADCAST_TXS_SIZE: usize = 200;
pub const DEFAULT_BROADCAST_TXS_INTERVAL: u64 = 200; // milliseconds
pub const DEFAULT_SYNC_TXS_CHUNK_SIZE: usize = 5000;
Expand Down Expand Up @@ -80,6 +83,91 @@ impl Config {
}
}

impl ValueParserFactory for Config {
type Parser = ConfigValueParser;

fn value_parser() -> Self::Parser {
ConfigValueParser
}
}

#[derive(Clone, Debug)]
pub struct ConfigValueParser;

impl TypedValueParser for ConfigValueParser {
type Value = Config;

fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &OsStr,
) -> Result<Self::Value, clap::Error> {
let file_path = StringValueParser::new()
.parse_ref(cmd, arg, value)
.map(PathBuf::from)?;
let dir_path = file_path.parent().ok_or_else(|| {
let err = {
let kind = io::ErrorKind::Other;
let msg = format!("no parent directory of {}", file_path.display());
io::Error::new(kind, msg)
};
let kind = clap::error::ErrorKind::InvalidValue;
clap::Error::raw(kind, err)
})?;
parse_file(&file_path, false)
.map(|mut config: Self::Value| {
if let Some(ref mut f) = config.rocksdb.options_file {
*f = dir_path.join(&f)
}
config
})
.map_err(|err| {
let kind = clap::error::ErrorKind::InvalidValue;
let msg = format!("failed to parse file {} since {err}", file_path.display());
clap::Error::raw(kind, msg)
})
}
}

#[derive(Clone, Debug)]
pub struct JsonValueParser<T: de::DeserializeOwned + 'static + Clone + Send + Sync>(PhantomData<T>);

impl<T> Default for JsonValueParser<T>
where
T: de::DeserializeOwned + 'static + Clone + Send + Sync,
{
fn default() -> Self {
Self(PhantomData)
}
}

impl<T> TypedValueParser for JsonValueParser<T>
where
T: de::DeserializeOwned + 'static + Clone + Send + Sync,
{
type Value = T;

fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &OsStr,
) -> Result<Self::Value, clap::Error> {
let file_path = StringValueParser::new()
.parse_ref(cmd, arg, value)
.map(PathBuf::from)?;
parse_file(&file_path, true).map_err(|err| {
let kind = clap::error::ErrorKind::InvalidValue;
let msg = format!(
"failed to parse JSON file {} since {err}",
file_path.display()
);
clap::Error::raw(kind, msg)
})
}
}

#[derive(Clone, Debug, Deserialize)]
pub struct ConfigApi {
pub http_listening_address: Option<SocketAddr>,
Expand Down
2 changes: 1 addition & 1 deletion core/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.3", features = ["cargo", "string"] }
clap = { version = "4.3", features = ["cargo", "string", "derive"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
1 change: 1 addition & 0 deletions core/cli/src/args/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod run;
59 changes: 59 additions & 0 deletions core/cli/src/args/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::ffi::OsStr;

use clap::{builder::TypedValueParser, Parser};

use common_config_parser::types::{Config, JsonValueParser};
use common_version::Version;
use core_run::{Axon, KeyProvider};
use protocol::types::RichBlock;

use crate::{
error::{Error, Result},
utils,
};

#[derive(Parser, Debug)]
#[command(about = "Run axon process")]
pub struct RunArgs {
#[arg(short = 'c', long = "config", help = "Axon config path")]
pub config: Config,
#[arg(short = 'g', long = "genesis", help = "Axon genesis path")]
#[arg(value_parser=RichBlockValueParser)]
pub genesis: RichBlock,
}

#[derive(Clone, Debug)]
struct RichBlockValueParser;

impl TypedValueParser for RichBlockValueParser {
type Value = RichBlock;

fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &OsStr,
) -> Result<Self::Value, clap::Error> {
JsonValueParser::<RichBlock>::default().parse_ref(cmd, arg, value)
}
}

impl RunArgs {
pub(crate) fn execute<K: KeyProvider>(
self,
application_version: Version,
kernel_version: Version,
key_provider: Option<K>,
) -> Result<()> {
let Self { config, genesis } = self;
utils::check_version(
&config.data_path_for_version(),
&kernel_version,
utils::latest_compatible_version(),
)?;
utils::register_log(&config);
Axon::new(application_version.to_string(), config, genesis)
.run(key_provider)
.map_err(Error::Running)
}
}
33 changes: 33 additions & 0 deletions core/cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::io;

use common_version::Version;
use thiserror::Error;

use protocol::ProtocolError;

#[non_exhaustive]
#[derive(Error, Debug)]
pub enum Error {
// Boxing so the error type isn't too large (clippy::result-large-err).
#[error(transparent)]
CheckingVersion(Box<CheckingVersionError>),
#[error("reading data version: {0}")]
ReadingVersion(#[source] io::Error),
#[error("writing data version: {0}")]
WritingVersion(#[source] io::Error),

#[error(transparent)]
Running(ProtocolError),
}

#[non_exhaustive]
#[derive(Error, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[error("data version({data}) is not compatible with the current axon version({current}), version >= {least_compatible} is supported")]
pub struct CheckingVersionError {
pub current: Version,
pub data: Version,
pub least_compatible: Version,
}

pub type Result<T, E = Error> = std::result::Result<T, E>;
Loading