Skip to content

Commit

Permalink
refactor: use derive apis to parse command line arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
yangby-cryptape committed Aug 16, 2023
1 parent f29d8b0 commit 5d084ea
Show file tree
Hide file tree
Showing 9 changed files with 336 additions and 222 deletions.
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

0 comments on commit 5d084ea

Please sign in to comment.