| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- use {
- crate::{
- input_parsers::parse_cpu_ranges,
- keypair::{parse_signer_source, SignerSourceKind, ASK_KEYWORD},
- },
- chrono::DateTime,
- solana_clock::{Epoch, Slot},
- solana_hash::Hash,
- solana_keypair::read_keypair_file,
- solana_pubkey::{Pubkey, MAX_SEED_LEN},
- solana_signature::Signature,
- std::{fmt::Display, ops::RangeBounds, str::FromStr},
- };
- fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- U: FromStr,
- U::Err: Display,
- {
- string
- .as_ref()
- .parse::<U>()
- .map(|_| ())
- .map_err(|err| format!("error parsing '{string}': {err}"))
- }
- // 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
- pub fn is_parsable<T>(string: String) -> Result<(), String>
- where
- T: FromStr,
- T::Err: Display,
- {
- is_parsable_generic::<T, String>(string)
- }
- // Return an error if string cannot be parsed as numeric type T, and value not within specified
- // range
- pub fn is_within_range<T, R>(string: String, range: R) -> Result<(), String>
- where
- T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add<Output = T> + From<usize>,
- T::Err: Display,
- R: RangeBounds<T> + std::fmt::Debug,
- {
- match string.parse::<T>() {
- Ok(input) => {
- if !range.contains(&input) {
- Err(format!("input '{input:?}' out of range {range:?}"))
- } else {
- Ok(())
- }
- }
- Err(err) => Err(format!("error parsing '{string}': {err}")),
- }
- }
- // Return an error if a pubkey cannot be parsed.
- pub fn is_pubkey<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- is_parsable_generic::<Pubkey, _>(string)
- }
- // Return an error if a hash cannot be parsed.
- pub fn is_hash<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- is_parsable_generic::<Hash, _>(string)
- }
- // Return an error if a keypair file cannot be parsed.
- pub fn is_keypair<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- read_keypair_file(string.as_ref())
- .map(|_| ())
- .map_err(|err| format!("{err}"))
- }
- // Return an error if a keypair file cannot be parsed
- pub fn is_keypair_or_ask_keyword<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- if string.as_ref() == ASK_KEYWORD {
- return Ok(());
- }
- read_keypair_file(string.as_ref())
- .map(|_| ())
- .map_err(|err| format!("{err}"))
- }
- // Return an error if a `SignerSourceKind::Prompt` cannot be parsed
- pub fn is_prompt_signer_source<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- if string.as_ref() == ASK_KEYWORD {
- return Ok(());
- }
- match parse_signer_source(string.as_ref())
- .map_err(|err| format!("{err}"))?
- .kind
- {
- SignerSourceKind::Prompt => Ok(()),
- _ => Err(format!(
- "Unable to parse input as `prompt:` URI scheme or `ASK` keyword: {string}"
- )),
- }
- }
- // Return an error if string cannot be parsed as pubkey string or keypair file location
- pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- is_pubkey(string.as_ref()).or_else(|_| is_keypair(string))
- }
- // Return an error if string cannot be parsed as a pubkey string, or a valid Signer that can
- // produce a pubkey()
- pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- match parse_signer_source(string.as_ref())
- .map_err(|err| format!("{err}"))?
- .kind
- {
- SignerSourceKind::Filepath(path) => is_keypair(path),
- _ => Ok(()),
- }
- }
- // Return an error if string cannot be parsed as a valid Signer. This is an alias of
- // `is_valid_pubkey`, and does accept pubkey strings, even though a Pubkey is not by itself
- // sufficient to sign a transaction.
- //
- // In the current offline-signing implementation, a pubkey is the valid input for a signer field
- // when paired with an offline `--signer` argument to provide a Presigner (pubkey + signature).
- // Clap validators can't check multiple fields at once, so the verification that a `--signer` is
- // also provided and correct happens in parsing, not in validation.
- pub fn is_valid_signer<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- is_valid_pubkey(string)
- }
- // Return an error if string cannot be parsed as pubkey=signature string
- pub fn is_pubkey_sig<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- let mut signer = string.as_ref().split('=');
- match Pubkey::from_str(
- signer
- .next()
- .ok_or_else(|| "Malformed signer string".to_string())?,
- ) {
- Ok(_) => {
- match Signature::from_str(
- signer
- .next()
- .ok_or_else(|| "Malformed signer string".to_string())?,
- ) {
- Ok(_) => Ok(()),
- Err(err) => Err(format!("{err}")),
- }
- }
- Err(err) => Err(format!("{err}")),
- }
- }
- // Return an error if a url cannot be parsed.
- pub fn is_url<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- match url::Url::parse(string.as_ref()) {
- Ok(url) => {
- if url.has_host() {
- Ok(())
- } else {
- Err("no host provided".to_string())
- }
- }
- Err(err) => Err(format!("{err}")),
- }
- }
- pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
- Ok(url) => {
- if url.has_host() {
- Ok(())
- } else {
- Err("no host provided".to_string())
- }
- }
- Err(err) => Err(format!("{err}")),
- }
- }
- pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
- match url_or_moniker.as_ref() {
- "m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com",
- "t" | "testnet" => "https://api.testnet.solana.com",
- "d" | "devnet" => "https://api.devnet.solana.com",
- "l" | "localhost" => "http://localhost:8899",
- url => url,
- }
- .to_string()
- }
- pub fn is_epoch<T>(epoch: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- is_parsable_generic::<Epoch, _>(epoch)
- }
- pub fn is_slot<T>(slot: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- is_parsable_generic::<Slot, _>(slot)
- }
- pub fn is_pow2<T>(bins: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- bins.as_ref()
- .parse::<usize>()
- .map_err(|e| format!("Unable to parse, provided: {bins}, err: {e}"))
- .and_then(|v| {
- if !v.is_power_of_two() {
- Err(format!("Must be a power of 2: {v}"))
- } else {
- Ok(())
- }
- })
- }
- pub fn is_port<T>(port: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- is_parsable_generic::<u16, _>(port)
- }
- pub fn is_valid_percentage<T>(percentage: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- percentage
- .as_ref()
- .parse::<u8>()
- .map_err(|e| format!("Unable to parse input percentage, provided: {percentage}, err: {e}"))
- .and_then(|v| {
- if v > 100 {
- Err(format!(
- "Percentage must be in range of 0 to 100, provided: {v}"
- ))
- } else {
- Ok(())
- }
- })
- }
- pub fn is_amount<T>(amount: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- if amount.as_ref().parse::<u64>().is_ok() || amount.as_ref().parse::<f64>().is_ok() {
- Ok(())
- } else {
- Err(format!(
- "Unable to parse input amount as integer or float, provided: {amount}"
- ))
- }
- }
- pub fn is_amount_or_all<T>(amount: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- if amount.as_ref().parse::<u64>().is_ok()
- || amount.as_ref().parse::<f64>().is_ok()
- || amount.as_ref() == "ALL"
- {
- Ok(())
- } else {
- Err(format!(
- "Unable to parse input amount as integer or float, provided: {amount}"
- ))
- }
- }
- pub fn is_amount_or_all_or_available<T>(amount: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- if amount.as_ref().parse::<u64>().is_ok()
- || amount.as_ref().parse::<f64>().is_ok()
- || amount.as_ref() == "ALL"
- || amount.as_ref() == "AVAILABLE"
- {
- Ok(())
- } else {
- Err(format!(
- "Unable to parse input amount as integer or float, provided: {amount}"
- ))
- }
- }
- pub fn is_rfc3339_datetime<T>(value: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- DateTime::parse_from_rfc3339(value.as_ref())
- .map(|_| ())
- .map_err(|e| format!("{e}"))
- }
- pub fn is_derivation<T>(value: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- let value = value.as_ref().replace('\'', "");
- let mut parts = value.split('/');
- let account = parts.next().unwrap();
- account
- .parse::<u32>()
- .map_err(|e| format!("Unable to parse derivation, provided: {account}, err: {e}"))
- .and_then(|_| {
- if let Some(change) = parts.next() {
- change.parse::<u32>().map_err(|e| {
- format!("Unable to parse derivation, provided: {change}, err: {e}")
- })
- } else {
- Ok(0)
- }
- })
- .map(|_| ())
- }
- pub fn is_structured_seed<T>(value: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- let (prefix, value) = value
- .as_ref()
- .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(()),
- _ => {
- 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(())
- }
- }
- }
- }
- }
- }
- pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- let value = value.as_ref();
- if value.len() > MAX_SEED_LEN {
- Err(format!(
- "Address seed must not be longer than {MAX_SEED_LEN} bytes"
- ))
- } else {
- Ok(())
- }
- }
- pub fn validate_maximum_full_snapshot_archives_to_retain<T>(value: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- let value = value.as_ref();
- if value.eq("0") {
- Err(String::from(
- "--maximum-full-snapshot-archives-to-retain cannot be zero",
- ))
- } else {
- Ok(())
- }
- }
- pub fn validate_maximum_incremental_snapshot_archives_to_retain<T>(value: T) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- let value = value.as_ref();
- if value.eq("0") {
- Err(String::from(
- "--maximum-incremental-snapshot-archives-to-retain cannot be zero",
- ))
- } else {
- Ok(())
- }
- }
- pub fn validate_cpu_ranges<T>(value: T, err_prefix: &str) -> Result<(), String>
- where
- T: AsRef<str> + Display,
- {
- parse_cpu_ranges(value.as_ref())
- .map(|_| ())
- .map_err(|e| format!("{err_prefix} {e}"))
- }
- pub fn is_non_zero(value: impl AsRef<str>) -> Result<(), String> {
- let value = value.as_ref();
- if value.eq("0") {
- Err(String::from("cannot be zero"))
- } else {
- 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());
- }
- }
|