diff --git a/clap-v3-utils/src/input_parsers/mod.rs b/clap-v3-utils/src/input_parsers/mod.rs new file mode 100644 index 0000000000..d96af9516b --- /dev/null +++ b/clap-v3-utils/src/input_parsers/mod.rs @@ -0,0 +1,548 @@ +use { + crate::{ + input_validators::normalize_to_url_if_moniker, + keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG}, + }, + chrono::DateTime, + clap::ArgMatches, + solana_sdk::{ + clock::UnixTimestamp, + commitment_config::CommitmentConfig, + genesis_config::ClusterType, + native_token::sol_to_lamports, + pubkey::{Pubkey, MAX_SEED_LEN}, + signature::{read_keypair_file, Keypair, Signer}, + }, + std::str::FromStr, +}; + +pub mod signer; +#[deprecated( + since = "1.17.0", + note = "Please use the functions in `solana_clap_v3_utils::input_parsers::signer` directly instead" +)] +pub use signer::{ + pubkey_of_signer, pubkeys_of_multiple_signers, pubkeys_sigs_of, resolve_signer, signer_of, + STDOUT_OUTFILE_TOKEN, +}; + +// Return parsed values from matches at `name` +pub fn values_of(matches: &ArgMatches, name: &str) -> Option> +where + T: std::str::FromStr, + ::Err: std::fmt::Debug, +{ + matches + .values_of(name) + .map(|xs| xs.map(|x| x.parse::().unwrap()).collect()) +} + +// Return a parsed value from matches at `name` +pub fn value_of(matches: &ArgMatches, name: &str) -> Option +where + T: std::str::FromStr, + ::Err: std::fmt::Debug, +{ + matches + .value_of(name) + .and_then(|value| value.parse::().ok()) +} + +pub fn unix_timestamp_from_rfc3339_datetime( + matches: &ArgMatches, + name: &str, +) -> Option { + matches.value_of(name).and_then(|value| { + DateTime::parse_from_rfc3339(value) + .ok() + .map(|date_time| date_time.timestamp()) + }) +} + +#[deprecated( + since = "1.17.0", + note = "please use `Amount::parse_decimal` and `Amount::sol_to_lamport` instead" +)] +pub fn lamports_of_sol(matches: &ArgMatches, name: &str) -> Option { + value_of(matches, name).map(sol_to_lamports) +} + +pub fn cluster_type_of(matches: &ArgMatches, name: &str) -> Option { + value_of(matches, name) +} + +pub fn commitment_of(matches: &ArgMatches, name: &str) -> Option { + matches + .value_of(name) + .map(|value| CommitmentConfig::from_str(value).unwrap_or_default()) +} + +pub fn parse_url(arg: &str) -> Result { + url::Url::parse(arg) + .map_err(|err| err.to_string()) + .and_then(|url| { + url.has_host() + .then_some(arg.to_string()) + .ok_or("no host provided".to_string()) + }) +} + +pub fn parse_url_or_moniker(arg: &str) -> Result { + parse_url(&normalize_to_url_if_moniker(arg)) +} + +pub fn parse_pow2(arg: &str) -> Result { + arg.parse::() + .map_err(|e| format!("Unable to parse, provided: {arg}, err: {e}")) + .and_then(|v| { + v.is_power_of_two() + .then_some(v) + .ok_or(format!("Must be a power of 2: {v}")) + }) +} + +pub fn parse_percentage(arg: &str) -> Result { + arg.parse::() + .map_err(|e| format!("Unable to parse input percentage, provided: {arg}, err: {e}")) + .and_then(|v| { + (v <= 100).then_some(v).ok_or(format!( + "Percentage must be in range of 0 to 100, provided: {v}" + )) + }) +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Amount { + Decimal(f64), + Raw(u64), + All, +} +impl Amount { + pub fn parse(arg: &str) -> Result { + if arg == "ALL" { + Ok(Amount::All) + } else { + Self::parse_decimal(arg).or(Self::parse_raw(arg) + .map_err(|_| format!("Unable to parse input amount, provided: {arg}"))) + } + } + + pub fn parse_decimal(arg: &str) -> Result { + arg.parse::() + .map(Amount::Decimal) + .map_err(|_| format!("Unable to parse input amount, provided: {arg}")) + } + + pub fn parse_raw(arg: &str) -> Result { + arg.parse::() + .map(Amount::Raw) + .map_err(|_| format!("Unable to parse input amount, provided: {arg}")) + } + + pub fn parse_decimal_or_all(arg: &str) -> Result { + if arg == "ALL" { + Ok(Amount::All) + } else { + Self::parse_decimal(arg).map_err(|_| { + format!("Unable to parse input amount as float or 'ALL' keyword, provided: {arg}") + }) + } + } + + pub fn to_raw_amount(&self, decimals: u8) -> Self { + match self { + Amount::Decimal(amount) => { + Amount::Raw((amount * 10_usize.pow(decimals as u32) as f64) as u64) + } + Amount::Raw(amount) => Amount::Raw(*amount), + Amount::All => Amount::All, + } + } + + pub fn sol_to_lamport(&self) -> Amount { + const NATIVE_SOL_DECIMALS: u8 = 9; + self.to_raw_amount(NATIVE_SOL_DECIMALS) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RawTokenAmount { + Amount(u64), + All, +} + +pub fn parse_rfc3339_datetime(arg: &str) -> Result { + DateTime::parse_from_rfc3339(arg) + .map(|_| arg.to_string()) + .map_err(|e| format!("{e}")) +} + +pub fn parse_derivation(arg: &str) -> Result { + let value = arg.replace('\'', ""); + let mut parts = value.split('/'); + let account = parts.next().unwrap(); + account + .parse::() + .map_err(|e| format!("Unable to parse derivation, provided: {account}, err: {e}")) + .and_then(|_| { + if let Some(change) = parts.next() { + change.parse::().map_err(|e| { + format!("Unable to parse derivation, provided: {change}, err: {e}") + }) + } else { + Ok(0) + } + })?; + Ok(arg.to_string()) +} + +pub fn parse_structured_seed(arg: &str) -> Result { + let (prefix, value) = arg + .split_once(':') + .ok_or("Seed must contain ':' as delimiter") + .unwrap(); + if prefix.is_empty() || value.is_empty() { + Err(String::from("Seed prefix or value is empty")) + } else { + match prefix { + "string" | "pubkey" | "hex" | "u8" => Ok(arg.to_string()), + _ => { + let len = prefix.len(); + if len != 5 && len != 6 { + Err(format!("Wrong prefix length {len} {prefix}:{value}")) + } else { + let sign = &prefix[0..1]; + let type_size = &prefix[1..len.saturating_sub(2)]; + let byte_order = &prefix[len.saturating_sub(2)..len]; + if sign != "u" && sign != "i" { + Err(format!("Wrong prefix sign {sign} {prefix}:{value}")) + } else if type_size != "16" + && type_size != "32" + && type_size != "64" + && type_size != "128" + { + Err(format!( + "Wrong prefix type size {type_size} {prefix}:{value}" + )) + } else if byte_order != "le" && byte_order != "be" { + Err(format!( + "Wrong prefix byte order {byte_order} {prefix}:{value}" + )) + } else { + Ok(arg.to_string()) + } + } + } + } + } +} + +pub fn parse_derived_address_seed(arg: &str) -> Result { + (arg.len() <= MAX_SEED_LEN) + .then_some(arg.to_string()) + .ok_or(format!( + "Address seed must not be longer than {MAX_SEED_LEN} bytes" + )) +} + +// Return the keypair for an argument with filename `name` or None if not present. +pub fn keypair_of(matches: &ArgMatches, name: &str) -> Option { + if let Some(value) = matches.value_of(name) { + if value == ASK_KEYWORD { + let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); + keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() + } else { + read_keypair_file(value).ok() + } + } else { + None + } +} + +pub fn keypairs_of(matches: &ArgMatches, name: &str) -> Option> { + matches.values_of(name).map(|values| { + values + .filter_map(|value| { + if value == ASK_KEYWORD { + let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); + keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() + } else { + read_keypair_file(value).ok() + } + }) + .collect() + }) +} + +// Return a pubkey for an argument that can itself be parsed into a pubkey, +// or is a filename that can be read as a keypair +pub fn pubkey_of(matches: &ArgMatches, name: &str) -> Option { + value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey())) +} + +pub fn pubkeys_of(matches: &ArgMatches, name: &str) -> Option> { + matches.values_of(name).map(|values| { + values + .map(|value| { + value.parse::().unwrap_or_else(|_| { + read_keypair_file(value) + .expect("read_keypair_file failed") + .pubkey() + }) + }) + .collect() + }) +} + +#[cfg(test)] +mod tests { + use { + super::*, + clap::{Arg, Command}, + solana_sdk::{hash::Hash, pubkey::Pubkey}, + }; + + fn app<'ab>() -> Command<'ab> { + Command::new("test") + .arg( + Arg::new("multiple") + .long("multiple") + .takes_value(true) + .multiple_occurrences(true) + .multiple_values(true), + ) + .arg(Arg::new("single").takes_value(true).long("single")) + .arg(Arg::new("unit").takes_value(true).long("unit")) + } + + #[test] + fn test_values_of() { + let matches = app().get_matches_from(vec!["test", "--multiple", "50", "--multiple", "39"]); + assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39])); + assert_eq!(values_of::(&matches, "single"), None); + + let pubkey0 = solana_sdk::pubkey::new_rand(); + let pubkey1 = solana_sdk::pubkey::new_rand(); + let matches = app().get_matches_from(vec![ + "test", + "--multiple", + &pubkey0.to_string(), + "--multiple", + &pubkey1.to_string(), + ]); + assert_eq!( + values_of(&matches, "multiple"), + Some(vec![pubkey0, pubkey1]) + ); + } + + #[test] + fn test_value_of() { + let matches = app().get_matches_from(vec!["test", "--single", "50"]); + assert_eq!(value_of(&matches, "single"), Some(50)); + assert_eq!(value_of::(&matches, "multiple"), None); + + let pubkey = solana_sdk::pubkey::new_rand(); + let matches = app().get_matches_from(vec!["test", "--single", &pubkey.to_string()]); + assert_eq!(value_of(&matches, "single"), Some(pubkey)); + } + + #[test] + fn test_parse_pubkey() { + let command = Command::new("test").arg( + Arg::new("pubkey") + .long("pubkey") + .takes_value(true) + .value_parser(clap::value_parser!(Pubkey)), + ); + + // success case + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--pubkey", "11111111111111111111111111111111"]) + .unwrap(); + assert_eq!( + *matches.get_one::("pubkey").unwrap(), + Pubkey::from_str("11111111111111111111111111111111").unwrap(), + ); + + // validation fails + let matches_error = command + .clone() + .try_get_matches_from(vec!["test", "--pubkey", "this_is_an_invalid_arg"]) + .unwrap_err(); + assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation); + } + + #[test] + fn test_parse_hash() { + let command = Command::new("test").arg( + Arg::new("hash") + .long("hash") + .takes_value(true) + .value_parser(clap::value_parser!(Hash)), + ); + + // success case + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--hash", "11111111111111111111111111111111"]) + .unwrap(); + assert_eq!( + *matches.get_one::("hash").unwrap(), + Hash::from_str("11111111111111111111111111111111").unwrap(), + ); + + // validation fails + let matches_error = command + .clone() + .try_get_matches_from(vec!["test", "--hash", "this_is_an_invalid_arg"]) + .unwrap_err(); + assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation); + } + + #[test] + fn test_parse_token_decimal() { + let command = Command::new("test").arg( + Arg::new("amount") + .long("amount") + .takes_value(true) + .value_parser(Amount::parse_decimal), + ); + + // success cases + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "11223344"]) + .unwrap(); + assert_eq!( + *matches.get_one::("amount").unwrap(), + Amount::Decimal(11223344_f64), + ); + + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "0.11223344"]) + .unwrap(); + assert_eq!( + *matches.get_one::("amount").unwrap(), + Amount::Decimal(0.11223344), + ); + + // validation fail cases + let matches_error = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "this_is_an_invalid_arg"]) + .unwrap_err(); + assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation); + + let matches_error = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "all"]) + .unwrap_err(); + assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation); + } + + #[test] + fn test_parse_token_decimal_or_all() { + let command = Command::new("test").arg( + Arg::new("amount") + .long("amount") + .takes_value(true) + .value_parser(Amount::parse_decimal_or_all), + ); + + // success cases + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "11223344"]) + .unwrap(); + assert_eq!( + *matches.get_one::("amount").unwrap(), + Amount::Decimal(11223344_f64), + ); + + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "0.11223344"]) + .unwrap(); + assert_eq!( + *matches.get_one::("amount").unwrap(), + Amount::Decimal(0.11223344), + ); + + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "ALL"]) + .unwrap(); + assert_eq!(*matches.get_one::("amount").unwrap(), Amount::All,); + + // validation fail cases + let matches_error = command + .clone() + .try_get_matches_from(vec!["test", "--amount", "this_is_an_invalid_arg"]) + .unwrap_err(); + assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation); + } + + #[test] + fn test_sol_to_lamports() { + let command = Command::new("test").arg( + Arg::new("amount") + .long("amount") + .takes_value(true) + .value_parser(Amount::parse_decimal_or_all), + ); + + let test_cases = vec![ + ("50", 50_000_000_000), + ("1.5", 1_500_000_000), + ("0.03", 30_000_000), + ]; + + for (arg, expected_lamport) in test_cases { + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--amount", arg]) + .unwrap(); + assert_eq!( + matches + .get_one::("amount") + .unwrap() + .sol_to_lamport(), + Amount::Raw(expected_lamport), + ); + } + } + + #[test] + fn test_derivation() { + let command = Command::new("test").arg( + Arg::new("derivation") + .long("derivation") + .takes_value(true) + .value_parser(parse_derivation), + ); + + let test_arguments = vec![ + ("2", true), + ("0", true), + ("65537", true), + ("0/2", true), + ("a", false), + ("4294967296", false), + ("a/b", false), + ("0/4294967296", false), + ]; + + for (arg, should_accept) in test_arguments { + if should_accept { + let matches = command + .clone() + .try_get_matches_from(vec!["test", "--derivation", arg]) + .unwrap(); + assert_eq!(matches.get_one::("derivation").unwrap(), arg); + } + } + } +} diff --git a/clap-v3-utils/src/input_parsers.rs b/clap-v3-utils/src/input_parsers/signer.rs similarity index 59% rename from clap-v3-utils/src/input_parsers.rs rename to clap-v3-utils/src/input_parsers/signer.rs index 03b3ba3be1..28425a95a0 100644 --- a/clap-v3-utils/src/input_parsers.rs +++ b/clap-v3-utils/src/input_parsers/signer.rs @@ -1,18 +1,13 @@ use { - crate::keypair::{ - keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path, - ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG, + crate::{ + input_parsers::{keypair_of, keypairs_of, pubkey_of, pubkeys_of}, + keypair::{pubkey_from_path, resolve_signer_from_path, signer_from_path}, }, - chrono::DateTime, clap::ArgMatches, solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_sdk::{ - clock::UnixTimestamp, - commitment_config::CommitmentConfig, - genesis_config::ClusterType, - native_token::sol_to_lamports, pubkey::Pubkey, - signature::{read_keypair_file, Keypair, Signature, Signer}, + signature::{Keypair, Signature, Signer}, }, std::{error, rc::Rc, str::FromStr}, }; @@ -20,55 +15,6 @@ use { // Sentinel value used to indicate to write to screen instead of file pub const STDOUT_OUTFILE_TOKEN: &str = "-"; -// Return parsed values from matches at `name` -pub fn values_of(matches: &ArgMatches, name: &str) -> Option> -where - T: std::str::FromStr, - ::Err: std::fmt::Debug, -{ - matches - .values_of(name) - .map(|xs| xs.map(|x| x.parse::().unwrap()).collect()) -} - -// Return a parsed value from matches at `name` -pub fn value_of(matches: &ArgMatches, name: &str) -> Option -where - T: std::str::FromStr, - ::Err: std::fmt::Debug, -{ - if let Some(value) = matches.value_of(name) { - value.parse::().ok() - } else { - None - } -} - -pub fn unix_timestamp_from_rfc3339_datetime( - matches: &ArgMatches, - name: &str, -) -> Option { - matches.value_of(name).and_then(|value| { - DateTime::parse_from_rfc3339(value) - .ok() - .map(|date_time| date_time.timestamp()) - }) -} - -// Return the keypair for an argument with filename `name` or None if not present. -pub fn keypair_of(matches: &ArgMatches, name: &str) -> Option { - if let Some(value) = matches.value_of(name) { - if value == ASK_KEYWORD { - let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); - keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() - } else { - read_keypair_file(value).ok() - } - } else { - None - } -} - // Return the keypair for an argument with filename `name` or `None` if not present wrapped inside `Result`. pub fn try_keypair_of( matches: &ArgMatches, @@ -78,21 +24,6 @@ pub fn try_keypair_of( Ok(keypair_of(matches, name)) } -pub fn keypairs_of(matches: &ArgMatches, name: &str) -> Option> { - matches.values_of(name).map(|values| { - values - .filter_map(|value| { - if value == ASK_KEYWORD { - let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); - keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() - } else { - read_keypair_file(value).ok() - } - }) - .collect() - }) -} - pub fn try_keypairs_of( matches: &ArgMatches, name: &str, @@ -101,12 +32,6 @@ pub fn try_keypairs_of( Ok(keypairs_of(matches, name)) } -// Return a pubkey for an argument that can itself be parsed into a pubkey, -// or is a filename that can be read as a keypair -pub fn pubkey_of(matches: &ArgMatches, name: &str) -> Option { - value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey())) -} - // Return a `Result` wrapped pubkey for an argument that can itself be parsed into a pubkey, // or is a filename that can be read as a keypair pub fn try_pubkey_of( @@ -117,20 +42,6 @@ pub fn try_pubkey_of( Ok(pubkey_of(matches, name)) } -pub fn pubkeys_of(matches: &ArgMatches, name: &str) -> Option> { - matches.values_of(name).map(|values| { - values - .map(|value| { - value.parse::().unwrap_or_else(|_| { - read_keypair_file(value) - .expect("read_keypair_file failed") - .pubkey() - }) - }) - .collect() - }) -} - pub fn try_pubkeys_of( matches: &ArgMatches, name: &str, @@ -225,18 +136,28 @@ pub fn resolve_signer( ) } -pub fn lamports_of_sol(matches: &ArgMatches, name: &str) -> Option { - value_of(matches, name).map(sol_to_lamports) +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PubkeySignature { + pubkey: Pubkey, + signature: Signature, } +impl FromStr for PubkeySignature { + type Err = String; -pub fn cluster_type_of(matches: &ArgMatches, name: &str) -> Option { - value_of(matches, name) -} + fn from_str(s: &str) -> Result { + let mut signer = s.split('='); + let pubkey = signer + .next() + .ok_or_else(|| String::from("Malformed signer string"))?; + let pubkey = Pubkey::from_str(pubkey).map_err(|err| format!("{err}"))?; -pub fn commitment_of(matches: &ArgMatches, name: &str) -> Option { - matches - .value_of(name) - .map(|value| CommitmentConfig::from_str(value).unwrap_or_default()) + let signature = signer + .next() + .ok_or_else(|| String::from("Malformed signer string"))?; + let signature = Signature::from_str(signature).map_err(|err| format!("{err}"))?; + + Ok(Self { pubkey, signature }) + } } #[cfg(test)] @@ -268,38 +189,6 @@ mod tests { format!("{out_dir}/tmp/{name}-{pubkey}") } - #[test] - fn test_values_of() { - let matches = app().get_matches_from(vec!["test", "--multiple", "50", "--multiple", "39"]); - assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39])); - assert_eq!(values_of::(&matches, "single"), None); - - let pubkey0 = solana_sdk::pubkey::new_rand(); - let pubkey1 = solana_sdk::pubkey::new_rand(); - let matches = app().get_matches_from(vec![ - "test", - "--multiple", - &pubkey0.to_string(), - "--multiple", - &pubkey1.to_string(), - ]); - assert_eq!( - values_of(&matches, "multiple"), - Some(vec![pubkey0, pubkey1]) - ); - } - - #[test] - fn test_value_of() { - let matches = app().get_matches_from(vec!["test", "--single", "50"]); - assert_eq!(value_of(&matches, "single"), Some(50)); - assert_eq!(value_of::(&matches, "multiple"), None); - - let pubkey = solana_sdk::pubkey::new_rand(); - let matches = app().get_matches_from(vec!["test", "--single", &pubkey.to_string()]); - assert_eq!(value_of(&matches, "single"), Some(pubkey)); - } - #[test] fn test_keypair_of() { let keypair = Keypair::new(); @@ -376,14 +265,40 @@ mod tests { } #[test] - fn test_lamports_of_sol() { - let matches = app().get_matches_from(vec!["test", "--single", "50"]); - assert_eq!(lamports_of_sol(&matches, "single"), Some(50_000_000_000)); - assert_eq!(lamports_of_sol(&matches, "multiple"), None); - let matches = app().get_matches_from(vec!["test", "--single", "1.5"]); - assert_eq!(lamports_of_sol(&matches, "single"), Some(1_500_000_000)); - assert_eq!(lamports_of_sol(&matches, "multiple"), None); - let matches = app().get_matches_from(vec!["test", "--single", "0.03"]); - assert_eq!(lamports_of_sol(&matches, "single"), Some(30_000_000)); + fn test_parse_pubkey_signature() { + let command = Command::new("test").arg( + Arg::new("pubkeysig") + .long("pubkeysig") + .takes_value(true) + .value_parser(clap::value_parser!(PubkeySignature)), + ); + + // success case + let matches = command + .clone() + .try_get_matches_from(vec![ + "test", + "--pubkeysig", + "11111111111111111111111111111111=4TpFuec1u4BZfxgHg2VQXwvBHANZuNSJHmgrU34GViLAM5uYZ8t7uuhWMHN4k9r41B2p9mwnHjPGwTmTxyvCZw63" + ] + ) + .unwrap(); + + let expected = PubkeySignature { + pubkey: Pubkey::from_str("11111111111111111111111111111111").unwrap(), + signature: Signature::from_str("4TpFuec1u4BZfxgHg2VQXwvBHANZuNSJHmgrU34GViLAM5uYZ8t7uuhWMHN4k9r41B2p9mwnHjPGwTmTxyvCZw63").unwrap(), + }; + + assert_eq!( + *matches.get_one::("pubkeysig").unwrap(), + expected, + ); + + // validation fails + let matches_error = command + .clone() + .try_get_matches_from(vec!["test", "--pubkeysig", "this_is_an_invalid_arg"]) + .unwrap_err(); + assert_eq!(matches_error.kind, clap::error::ErrorKind::ValueValidation); } } diff --git a/clap-v3-utils/src/input_validators.rs b/clap-v3-utils/src/input_validators.rs index 76c780d3f3..4bb40b0cd1 100644 --- a/clap-v3-utils/src/input_validators.rs +++ b/clap-v3-utils/src/input_validators.rs @@ -25,6 +25,7 @@ where // Return an error if string cannot be parsed as type T. // Takes a String to avoid second type parameter when used as a clap validator +#[deprecated(since = "1.17.0", note = "please use `clap::value_parser!` instead")] pub fn is_parsable(string: &str) -> Result<(), String> where T: FromStr, @@ -35,6 +36,10 @@ where // Return an error if string cannot be parsed as numeric type T, and value not within specified // range +#[deprecated( + since = "1.17.0", + note = "please use `clap::builder::RangedI64ValueParser` instead" +)] pub fn is_within_range(string: String, range: R) -> Result<(), String> where T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add + From, @@ -59,6 +64,10 @@ pub fn is_pubkey(string: &str) -> Result<(), String> { } // Return an error if a hash cannot be parsed. +#[deprecated( + since = "1.17.0", + note = "please use `clap::value_parser!(Hash)` instead" +)] pub fn is_hash(string: T) -> Result<(), String> where T: AsRef + Display, @@ -144,6 +153,10 @@ where } // Return an error if string cannot be parsed as pubkey=signature string +#[deprecated( + since = "1.17.0", + note = "please use `clap::value_parser!(PubkeySignature)` instead" +)] pub fn is_pubkey_sig(string: T) -> Result<(), String> where T: AsRef + Display, @@ -169,6 +182,7 @@ where } // Return an error if a url cannot be parsed. +#[deprecated(since = "1.17.0", note = "please use `parse_url` instead")] pub fn is_url(string: T) -> Result<(), String> where T: AsRef + Display, @@ -185,6 +199,7 @@ where } } +#[deprecated(since = "1.17.0", note = "please use `parse_url_or_moniker` instead")] pub fn is_url_or_moniker(string: T) -> Result<(), String> where T: AsRef + Display, @@ -212,6 +227,10 @@ pub fn normalize_to_url_if_moniker>(url_or_moniker: T) -> String { .to_string() } +#[deprecated( + since = "1.17.0", + note = "please use `clap::value_parser!(Epoch)` instead" +)] pub fn is_epoch(epoch: T) -> Result<(), String> where T: AsRef + Display, @@ -219,6 +238,10 @@ where is_parsable_generic::(epoch) } +#[deprecated( + since = "1.17.0", + note = "please use `clap::value_parser!(Slot)` instead" +)] pub fn is_slot(slot: T) -> Result<(), String> where T: AsRef + Display, @@ -226,6 +249,7 @@ where is_parsable_generic::(slot) } +#[deprecated(since = "1.17.0", note = "please use `parse_pow2` instead")] pub fn is_pow2(bins: T) -> Result<(), String> where T: AsRef + Display, @@ -242,6 +266,10 @@ where }) } +#[deprecated( + since = "1.17.0", + note = "please use `clap_value_parser!(u16)` instead" +)] pub fn is_port(port: T) -> Result<(), String> where T: AsRef + Display, @@ -249,6 +277,7 @@ where is_parsable_generic::(port) } +#[deprecated(since = "1.17.0", note = "please use `parse_percentage` instead")] pub fn is_valid_percentage(percentage: T) -> Result<(), String> where T: AsRef + Display, @@ -268,6 +297,7 @@ where }) } +#[deprecated(since = "1.17.0", note = "please use `Amount::parse_decimal` instead")] pub fn is_amount(amount: T) -> Result<(), String> where T: AsRef + Display, @@ -281,6 +311,10 @@ where } } +#[deprecated( + since = "1.17.0", + note = "please use `TokenAmount::parse_decimal` instead" +)] pub fn is_amount_or_all(amount: T) -> Result<(), String> where T: AsRef + Display, @@ -297,6 +331,7 @@ where } } +#[deprecated(since = "1.17.0", note = "please use `parse_rfc3339_datetime` instead")] pub fn is_rfc3339_datetime(value: T) -> Result<(), String> where T: AsRef + Display, @@ -306,6 +341,7 @@ where .map_err(|e| format!("{e}")) } +#[deprecated(since = "1.17.0", note = "please use `parse_derivation` instead")] pub fn is_derivation(value: T) -> Result<(), String> where T: AsRef + Display, @@ -328,6 +364,7 @@ where .map(|_| ()) } +#[deprecated(since = "1.17.0", note = "please use `parse_structured_seed` instead")] pub fn is_structured_seed(value: T) -> Result<(), String> where T: AsRef + Display, @@ -373,6 +410,10 @@ where } } +#[deprecated( + since = "1.17.0", + note = "please use `parse_derived_address_seed` instead" +)] pub fn is_derived_address_seed(value: T) -> Result<(), String> where T: AsRef + Display, @@ -386,21 +427,3 @@ where Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_derivation() { - assert_eq!(is_derivation("2"), Ok(())); - assert_eq!(is_derivation("0"), Ok(())); - assert_eq!(is_derivation("65537"), Ok(())); - assert_eq!(is_derivation("0/2"), Ok(())); - assert_eq!(is_derivation("0'/2'"), Ok(())); - assert!(is_derivation("a").is_err()); - assert!(is_derivation("4294967296").is_err()); - assert!(is_derivation("a/b").is_err()); - assert!(is_derivation("0/4294967296").is_err()); - } -} diff --git a/clap-v3-utils/src/offline.rs b/clap-v3-utils/src/offline.rs index 83c951dffc..b9ccd2d4ec 100644 --- a/clap-v3-utils/src/offline.rs +++ b/clap-v3-utils/src/offline.rs @@ -1,6 +1,7 @@ use { - crate::{input_validators::*, ArgConstant}, - clap::{Arg, Command}, + crate::{input_parsers::signer::PubkeySignature, ArgConstant}, + clap::{value_parser, Arg, Command}, + solana_sdk::hash::Hash, }; pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant { @@ -32,7 +33,7 @@ pub fn blockhash_arg<'a>() -> Arg<'a> { .long(BLOCKHASH_ARG.long) .takes_value(true) .value_name("BLOCKHASH") - .validator(|s| is_hash(s)) + .value_parser(value_parser!(Hash)) .help(BLOCKHASH_ARG.help) } @@ -49,7 +50,7 @@ fn signer_arg<'a>() -> Arg<'a> { .long(SIGNER_ARG.long) .takes_value(true) .value_name("PUBKEY=SIGNATURE") - .validator(|s| is_pubkey_sig(s)) + .value_parser(value_parser!(PubkeySignature)) .requires(BLOCKHASH_ARG.name) .multiple_occurrences(true) .multiple_values(true) diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index 20e218d886..e6b5289c38 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -1,10 +1,10 @@ #![allow(clippy::arithmetic_side_effects)] use { bip39::{Mnemonic, MnemonicType, Seed}, - clap::{crate_description, crate_name, Arg, ArgMatches, Command}, + clap::{crate_description, crate_name, value_parser, Arg, ArgMatches, Command}, solana_clap_v3_utils::{ input_parsers::STDOUT_OUTFILE_TOKEN, - input_validators::{is_parsable, is_prompt_signer_source}, + input_validators::is_prompt_signer_source, keygen::{ check_for_overwrite, derivation_path::{acquire_derivation_path, derivation_path_arg}, @@ -339,7 +339,7 @@ fn app<'a>(num_threads: &'a str, crate_version: &'a str) -> Command<'a> { .long("num-threads") .value_name("NUMBER") .takes_value(true) - .validator(is_parsable::) + .value_parser(value_parser!(usize)) .default_value(num_threads) .help("Specify the number of grind threads"), ) @@ -571,7 +571,7 @@ fn do_main(matches: &ArgMatches) -> Result<(), Box> { ); } - let num_threads: usize = matches.value_of_t_or_exit("num_threads"); + let num_threads = *matches.get_one::("num_threads").unwrap(); let grind_matches = grind_parse_args( ignore_case,