Skip to content

Commit

Permalink
Refactor CLI parsing: clap derive + improved errors.
Browse files Browse the repository at this point in the history
**Big refactoring!**

As suggested, more declarative code is used to define CLI.

NOTE: Errors like 'missing argument' or 'u64 parse error'
are handled by `clap` natively now.
  • Loading branch information
koxu1996 committed Feb 12, 2024
1 parent fd4476e commit f543666
Show file tree
Hide file tree
Showing 17 changed files with 206 additions and 215 deletions.
32 changes: 32 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion kairos-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ edition = "2021"

[dependencies]
casper-types = { version = "4.0.1", features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version.
clap = "4.4.18"
clap = { version = "4.4.18", features = ["derive"] }
hex = "0.4.3"
thiserror = "1.0.56"

[dev-dependencies]
assert_cmd = "2.0.13"
predicates = "3.1.0"
37 changes: 15 additions & 22 deletions kairos-cli/bin/commands/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
use crate::common::{amount, private_key};
use crate::common::args::{AmountArg, PrivateKeyPathArg};
use crate::crypto::signer::CasperSigner;
use crate::error::CliError;
use clap::{ArgMatches, Command};

pub struct Deposit;
use clap::Parser;

impl Deposit {
pub const NAME: &'static str = "deposit";
pub const ABOUT: &'static str = "Deposits funds into your account";

pub fn new_cmd() -> Command {
Command::new(Self::NAME)
.about(Self::ABOUT)
.arg(amount::arg())
.arg(private_key::arg())
}

pub fn run(matches: &ArgMatches) -> Result<String, CliError> {
let _amount = amount::get(matches)?;
let private_key = private_key::get(matches)?;
#[derive(Parser, Debug)]
pub struct Args {
#[clap(flatten)]
amount: AmountArg,
#[clap(flatten)]
private_key_path: PrivateKeyPathArg,
}

let _signer = CasperSigner::from_key(private_key);
pub fn run(args: Args) -> Result<String, CliError> {
let _amount: u64 = args.amount.field;
let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?;

// TODO: Create transaction and sign it with `signer`.
// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.
// TODO: Send transaction to the network, using Rust SDK.

Ok("ok".to_string())
}
Ok("ok".to_string())
}
20 changes: 14 additions & 6 deletions kairos-cli/bin/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
mod deposit;
mod transfer;
mod withdraw;
pub mod deposit;
pub mod transfer;
pub mod withdraw;

pub use deposit::Deposit;
pub use transfer::Transfer;
pub use withdraw::Withdraw;
use clap::Subcommand;

#[derive(Subcommand)]
pub enum Command {
#[command(about = "Deposits funds into your account")]
Deposit(deposit::Args),
#[command(about = "Transfers funds to another account")]
Transfer(transfer::Args),
#[command(about = "Withdraws funds from your account")]
Withdraw(withdraw::Args),
}
66 changes: 19 additions & 47 deletions kairos-cli/bin/commands/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,31 @@
use crate::common::{amount, private_key};
use crate::common::args::{AmountArg, PrivateKeyPathArg};
use crate::crypto::public_key::CasperPublicKey;
use crate::crypto::signer::CasperSigner;
use crate::error::CliError;
use clap::{Arg, ArgMatches, Command};
use crate::utils::parse_hex_string;

pub struct Transfer;
use clap::Parser;

const ARG_NAME: &str = "recipient";
const ARG_SHORT: char = 'r';
const ARG_VALUE_NAME: &str = "PUBLIC_KEY";
type StdVec<E> = Vec<E>;

pub mod recipient {
use super::*;

pub fn arg() -> Arg {
Arg::new(ARG_NAME)
.long(ARG_NAME)
.short(ARG_SHORT)
.required(true)
.value_name(ARG_VALUE_NAME)
}

pub fn get(matches: &ArgMatches) -> Result<CasperPublicKey, CliError> {
let value = matches
.get_one::<String>("recipient")
.map(String::as_str)
.unwrap();

CasperPublicKey::from_hex(value).map_err(|error| CliError::CryptoError { error })
}
#[derive(Parser)]
pub struct Args {
#[arg(long, short, value_name = "PUBLIC_KEY", value_parser = parse_hex_string)]
recipient: StdVec<u8>,
#[clap(flatten)]
amount: AmountArg,
#[clap(flatten)]
private_key_path: PrivateKeyPathArg,
}

impl Transfer {
pub const NAME: &'static str = "transfer";
pub const ABOUT: &'static str = "Transfers funds to another account";

pub fn new_cmd() -> Command {
Command::new(Self::NAME)
.about(Self::ABOUT)
.arg(recipient::arg())
.arg(amount::arg())
.arg(private_key::arg())
}

pub fn run(matches: &ArgMatches) -> Result<String, CliError> {
let _recipient = recipient::get(matches)?;
let _amount = amount::get(matches)?;
let private_key = private_key::get(matches)?;

let _signer = CasperSigner::from_key(private_key);
pub fn run(args: Args) -> Result<String, CliError> {
let _recipient = CasperPublicKey::from_bytes(args.recipient.as_ref())?;
let _amount: u64 = args.amount.field;
let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?;

// TODO: Create transaction and sign it with `signer`.
// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.
// TODO: Send transaction to the network, using Rust SDK.

Ok("ok".to_string())
}
Ok("ok".to_string())
}
37 changes: 15 additions & 22 deletions kairos-cli/bin/commands/withdraw.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
use crate::common::{amount, private_key};
use crate::common::args::{AmountArg, PrivateKeyPathArg};
use crate::crypto::signer::CasperSigner;
use crate::error::CliError;
use clap::{ArgMatches, Command};

pub struct Withdraw;
use clap::Parser;

impl Withdraw {
pub const NAME: &'static str = "withdraw";
pub const ABOUT: &'static str = "Withdraws funds from your account";

pub fn new_cmd() -> Command {
Command::new(Self::NAME)
.about(Self::ABOUT)
.arg(amount::arg())
.arg(private_key::arg())
}

pub fn run(matches: &ArgMatches) -> Result<String, CliError> {
let _amount = amount::get(matches)?;
let private_key = private_key::get(matches)?;
#[derive(Parser)]
pub struct Args {
#[clap(flatten)]
amount: AmountArg,
#[clap(flatten)]
private_key_path: PrivateKeyPathArg,
}

let _signer = CasperSigner::from_key(private_key);
pub fn run(args: Args) -> Result<String, CliError> {
let _amount: u64 = args.amount.field;
let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?;

// TODO: Create transaction and sign it with `signer`.
// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.
// TODO: Send transaction to the network, using Rust SDK.

Ok("ok".to_string())
}
Ok("ok".to_string())
}
66 changes: 10 additions & 56 deletions kairos-cli/bin/common/args.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,15 @@
use crate::error::CliError;
use clap::{Arg, ArgMatches};
use std::path::PathBuf;

pub mod amount {
use super::*;
use clap::Args;

const ARG_NAME: &str = "amount";
const ARG_SHORT: char = 'a';
const ARG_VALUE_NAME: &str = "NUM_MOTES";

pub fn arg() -> Arg {
Arg::new(ARG_NAME)
.long(ARG_NAME)
.short(ARG_SHORT)
.required(true)
.value_name(ARG_VALUE_NAME)
}

pub fn get(matches: &ArgMatches) -> Result<u64, CliError> {
let value = matches
.get_one::<String>(ARG_NAME)
.map(String::as_str)
.ok_or(CliError::MissingArgument { context: ARG_NAME })?;

let amount = value
.parse::<u64>()
.map_err(|_| CliError::FailedToParseU64 { context: "amount" })?;

Ok(amount)
}
#[derive(Args, Debug)]
pub struct AmountArg {
#[arg(name = "amount", long, short, value_name = "NUM_MOTES")]
pub field: u64,
}

pub mod private_key {
use crate::crypto::private_key::CasperPrivateKey;

use super::*;

const ARG_NAME: &str = "private-key";
const ARG_SHORT: char = 'k';
const ARG_VALUE_NAME: &str = "FILE_PATH";

pub fn arg() -> Arg {
Arg::new(ARG_NAME)
.long(ARG_NAME)
.short(ARG_SHORT)
.required(true)
.value_name(ARG_VALUE_NAME)
}

pub fn get(matches: &ArgMatches) -> Result<CasperPrivateKey, CliError> {
let value = matches
.get_one::<String>(ARG_NAME)
.map(String::as_str)
.ok_or(CliError::MissingArgument { context: ARG_NAME })?;

let private_key =
CasperPrivateKey::from_file(value).map_err(|error| CliError::CryptoError { error })?;

Ok(private_key)
}
#[derive(Args, Debug)]
pub struct PrivateKeyPathArg {
#[arg(name = "private-key", long, short = 'k', value_name = "FILE_PATH")]
pub field: PathBuf,
}
5 changes: 1 addition & 4 deletions kairos-cli/bin/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
mod args;

pub use args::amount;
pub use args::private_key;
pub mod args;
12 changes: 8 additions & 4 deletions kairos-cli/bin/crypto/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ use thiserror::Error;

#[derive(Error, Debug)]
pub enum CryptoError {
/// Failed to parse a public key from a formatted string.
/// Unable to load a file from the given path.
#[error("failed to load key from file")]
KeyLoad,
/// Failed to parse a public key from a raw data.
#[error("failed to parse private key")]
FailedToParseKey {},
#[error("failed to serialize signature/key")]
Serialization {},
FailedToParseKey,
/// Invalid public key (hexdigest) or other encoding related error.
#[error("failed to serialize/deserialize '{context}'")]
Serialization { context: &'static str },
}
11 changes: 9 additions & 2 deletions kairos-cli/bin/crypto/private_key.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
use casper_types::file_utils::read_file;

use crate::crypto::error::CryptoError;

pub struct CasperPrivateKey(pub casper_types::SecretKey);

impl CasperPrivateKey {
pub fn from_file(file_path: &str) -> Result<Self, CryptoError> {
let secret_key = casper_types::SecretKey::from_file(file_path)
.map_err(|_e| CryptoError::FailedToParseKey {})?;
let data = read_file(file_path).map_err(|_e| CryptoError::KeyLoad)?;
let secret_key =
casper_types::SecretKey::from_pem(data).map_err(|_e| CryptoError::FailedToParseKey)?;
Ok(Self(secret_key))
}
}

pub fn parse_private_key(file_path: &str) -> Result<CasperPrivateKey, CryptoError> {
CasperPrivateKey::from_file(file_path)
}
Loading

0 comments on commit f543666

Please sign in to comment.