input_validators.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. use {
  2. crate::{
  3. input_parsers::parse_cpu_ranges,
  4. keypair::{parse_signer_source, SignerSourceKind, ASK_KEYWORD},
  5. },
  6. chrono::DateTime,
  7. solana_clock::{Epoch, Slot},
  8. solana_hash::Hash,
  9. solana_keypair::read_keypair_file,
  10. solana_pubkey::{Pubkey, MAX_SEED_LEN},
  11. solana_signature::Signature,
  12. std::{fmt::Display, ops::RangeBounds, str::FromStr},
  13. };
  14. fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
  15. where
  16. T: AsRef<str> + Display,
  17. U: FromStr,
  18. U::Err: Display,
  19. {
  20. string
  21. .as_ref()
  22. .parse::<U>()
  23. .map(|_| ())
  24. .map_err(|err| format!("error parsing '{string}': {err}"))
  25. }
  26. // Return an error if string cannot be parsed as type T.
  27. // Takes a String to avoid second type parameter when used as a clap validator
  28. pub fn is_parsable<T>(string: String) -> Result<(), String>
  29. where
  30. T: FromStr,
  31. T::Err: Display,
  32. {
  33. is_parsable_generic::<T, String>(string)
  34. }
  35. // Return an error if string cannot be parsed as numeric type T, and value not within specified
  36. // range
  37. pub fn is_within_range<T, R>(string: String, range: R) -> Result<(), String>
  38. where
  39. T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add<Output = T> + From<usize>,
  40. T::Err: Display,
  41. R: RangeBounds<T> + std::fmt::Debug,
  42. {
  43. match string.parse::<T>() {
  44. Ok(input) => {
  45. if !range.contains(&input) {
  46. Err(format!("input '{input:?}' out of range {range:?}"))
  47. } else {
  48. Ok(())
  49. }
  50. }
  51. Err(err) => Err(format!("error parsing '{string}': {err}")),
  52. }
  53. }
  54. // Return an error if a pubkey cannot be parsed.
  55. pub fn is_pubkey<T>(string: T) -> Result<(), String>
  56. where
  57. T: AsRef<str> + Display,
  58. {
  59. is_parsable_generic::<Pubkey, _>(string)
  60. }
  61. // Return an error if a hash cannot be parsed.
  62. pub fn is_hash<T>(string: T) -> Result<(), String>
  63. where
  64. T: AsRef<str> + Display,
  65. {
  66. is_parsable_generic::<Hash, _>(string)
  67. }
  68. // Return an error if a keypair file cannot be parsed.
  69. pub fn is_keypair<T>(string: T) -> Result<(), String>
  70. where
  71. T: AsRef<str> + Display,
  72. {
  73. read_keypair_file(string.as_ref())
  74. .map(|_| ())
  75. .map_err(|err| format!("{err}"))
  76. }
  77. // Return an error if a keypair file cannot be parsed
  78. pub fn is_keypair_or_ask_keyword<T>(string: T) -> Result<(), String>
  79. where
  80. T: AsRef<str> + Display,
  81. {
  82. if string.as_ref() == ASK_KEYWORD {
  83. return Ok(());
  84. }
  85. read_keypair_file(string.as_ref())
  86. .map(|_| ())
  87. .map_err(|err| format!("{err}"))
  88. }
  89. // Return an error if a `SignerSourceKind::Prompt` cannot be parsed
  90. pub fn is_prompt_signer_source<T>(string: T) -> Result<(), String>
  91. where
  92. T: AsRef<str> + Display,
  93. {
  94. if string.as_ref() == ASK_KEYWORD {
  95. return Ok(());
  96. }
  97. match parse_signer_source(string.as_ref())
  98. .map_err(|err| format!("{err}"))?
  99. .kind
  100. {
  101. SignerSourceKind::Prompt => Ok(()),
  102. _ => Err(format!(
  103. "Unable to parse input as `prompt:` URI scheme or `ASK` keyword: {string}"
  104. )),
  105. }
  106. }
  107. // Return an error if string cannot be parsed as pubkey string or keypair file location
  108. pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
  109. where
  110. T: AsRef<str> + Display,
  111. {
  112. is_pubkey(string.as_ref()).or_else(|_| is_keypair(string))
  113. }
  114. // Return an error if string cannot be parsed as a pubkey string, or a valid Signer that can
  115. // produce a pubkey()
  116. pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
  117. where
  118. T: AsRef<str> + Display,
  119. {
  120. match parse_signer_source(string.as_ref())
  121. .map_err(|err| format!("{err}"))?
  122. .kind
  123. {
  124. SignerSourceKind::Filepath(path) => is_keypair(path),
  125. _ => Ok(()),
  126. }
  127. }
  128. // Return an error if string cannot be parsed as a valid Signer. This is an alias of
  129. // `is_valid_pubkey`, and does accept pubkey strings, even though a Pubkey is not by itself
  130. // sufficient to sign a transaction.
  131. //
  132. // In the current offline-signing implementation, a pubkey is the valid input for a signer field
  133. // when paired with an offline `--signer` argument to provide a Presigner (pubkey + signature).
  134. // Clap validators can't check multiple fields at once, so the verification that a `--signer` is
  135. // also provided and correct happens in parsing, not in validation.
  136. pub fn is_valid_signer<T>(string: T) -> Result<(), String>
  137. where
  138. T: AsRef<str> + Display,
  139. {
  140. is_valid_pubkey(string)
  141. }
  142. // Return an error if string cannot be parsed as pubkey=signature string
  143. pub fn is_pubkey_sig<T>(string: T) -> Result<(), String>
  144. where
  145. T: AsRef<str> + Display,
  146. {
  147. let mut signer = string.as_ref().split('=');
  148. match Pubkey::from_str(
  149. signer
  150. .next()
  151. .ok_or_else(|| "Malformed signer string".to_string())?,
  152. ) {
  153. Ok(_) => {
  154. match Signature::from_str(
  155. signer
  156. .next()
  157. .ok_or_else(|| "Malformed signer string".to_string())?,
  158. ) {
  159. Ok(_) => Ok(()),
  160. Err(err) => Err(format!("{err}")),
  161. }
  162. }
  163. Err(err) => Err(format!("{err}")),
  164. }
  165. }
  166. // Return an error if a url cannot be parsed.
  167. pub fn is_url<T>(string: T) -> Result<(), String>
  168. where
  169. T: AsRef<str> + Display,
  170. {
  171. match url::Url::parse(string.as_ref()) {
  172. Ok(url) => {
  173. if url.has_host() {
  174. Ok(())
  175. } else {
  176. Err("no host provided".to_string())
  177. }
  178. }
  179. Err(err) => Err(format!("{err}")),
  180. }
  181. }
  182. pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
  183. where
  184. T: AsRef<str> + Display,
  185. {
  186. match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
  187. Ok(url) => {
  188. if url.has_host() {
  189. Ok(())
  190. } else {
  191. Err("no host provided".to_string())
  192. }
  193. }
  194. Err(err) => Err(format!("{err}")),
  195. }
  196. }
  197. pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
  198. match url_or_moniker.as_ref() {
  199. "m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com",
  200. "t" | "testnet" => "https://api.testnet.solana.com",
  201. "d" | "devnet" => "https://api.devnet.solana.com",
  202. "l" | "localhost" => "http://localhost:8899",
  203. url => url,
  204. }
  205. .to_string()
  206. }
  207. pub fn is_epoch<T>(epoch: T) -> Result<(), String>
  208. where
  209. T: AsRef<str> + Display,
  210. {
  211. is_parsable_generic::<Epoch, _>(epoch)
  212. }
  213. pub fn is_slot<T>(slot: T) -> Result<(), String>
  214. where
  215. T: AsRef<str> + Display,
  216. {
  217. is_parsable_generic::<Slot, _>(slot)
  218. }
  219. pub fn is_pow2<T>(bins: T) -> Result<(), String>
  220. where
  221. T: AsRef<str> + Display,
  222. {
  223. bins.as_ref()
  224. .parse::<usize>()
  225. .map_err(|e| format!("Unable to parse, provided: {bins}, err: {e}"))
  226. .and_then(|v| {
  227. if !v.is_power_of_two() {
  228. Err(format!("Must be a power of 2: {v}"))
  229. } else {
  230. Ok(())
  231. }
  232. })
  233. }
  234. pub fn is_port<T>(port: T) -> Result<(), String>
  235. where
  236. T: AsRef<str> + Display,
  237. {
  238. is_parsable_generic::<u16, _>(port)
  239. }
  240. pub fn is_valid_percentage<T>(percentage: T) -> Result<(), String>
  241. where
  242. T: AsRef<str> + Display,
  243. {
  244. percentage
  245. .as_ref()
  246. .parse::<u8>()
  247. .map_err(|e| format!("Unable to parse input percentage, provided: {percentage}, err: {e}"))
  248. .and_then(|v| {
  249. if v > 100 {
  250. Err(format!(
  251. "Percentage must be in range of 0 to 100, provided: {v}"
  252. ))
  253. } else {
  254. Ok(())
  255. }
  256. })
  257. }
  258. pub fn is_amount<T>(amount: T) -> Result<(), String>
  259. where
  260. T: AsRef<str> + Display,
  261. {
  262. if amount.as_ref().parse::<u64>().is_ok() || amount.as_ref().parse::<f64>().is_ok() {
  263. Ok(())
  264. } else {
  265. Err(format!(
  266. "Unable to parse input amount as integer or float, provided: {amount}"
  267. ))
  268. }
  269. }
  270. pub fn is_amount_or_all<T>(amount: T) -> Result<(), String>
  271. where
  272. T: AsRef<str> + Display,
  273. {
  274. if amount.as_ref().parse::<u64>().is_ok()
  275. || amount.as_ref().parse::<f64>().is_ok()
  276. || amount.as_ref() == "ALL"
  277. {
  278. Ok(())
  279. } else {
  280. Err(format!(
  281. "Unable to parse input amount as integer or float, provided: {amount}"
  282. ))
  283. }
  284. }
  285. pub fn is_amount_or_all_or_available<T>(amount: T) -> Result<(), String>
  286. where
  287. T: AsRef<str> + Display,
  288. {
  289. if amount.as_ref().parse::<u64>().is_ok()
  290. || amount.as_ref().parse::<f64>().is_ok()
  291. || amount.as_ref() == "ALL"
  292. || amount.as_ref() == "AVAILABLE"
  293. {
  294. Ok(())
  295. } else {
  296. Err(format!(
  297. "Unable to parse input amount as integer or float, provided: {amount}"
  298. ))
  299. }
  300. }
  301. pub fn is_rfc3339_datetime<T>(value: T) -> Result<(), String>
  302. where
  303. T: AsRef<str> + Display,
  304. {
  305. DateTime::parse_from_rfc3339(value.as_ref())
  306. .map(|_| ())
  307. .map_err(|e| format!("{e}"))
  308. }
  309. pub fn is_derivation<T>(value: T) -> Result<(), String>
  310. where
  311. T: AsRef<str> + Display,
  312. {
  313. let value = value.as_ref().replace('\'', "");
  314. let mut parts = value.split('/');
  315. let account = parts.next().unwrap();
  316. account
  317. .parse::<u32>()
  318. .map_err(|e| format!("Unable to parse derivation, provided: {account}, err: {e}"))
  319. .and_then(|_| {
  320. if let Some(change) = parts.next() {
  321. change.parse::<u32>().map_err(|e| {
  322. format!("Unable to parse derivation, provided: {change}, err: {e}")
  323. })
  324. } else {
  325. Ok(0)
  326. }
  327. })
  328. .map(|_| ())
  329. }
  330. pub fn is_structured_seed<T>(value: T) -> Result<(), String>
  331. where
  332. T: AsRef<str> + Display,
  333. {
  334. let (prefix, value) = value
  335. .as_ref()
  336. .split_once(':')
  337. .ok_or("Seed must contain ':' as delimiter")
  338. .unwrap();
  339. if prefix.is_empty() || value.is_empty() {
  340. Err(String::from("Seed prefix or value is empty"))
  341. } else {
  342. match prefix {
  343. "string" | "pubkey" | "hex" | "u8" => Ok(()),
  344. _ => {
  345. let len = prefix.len();
  346. if len != 5 && len != 6 {
  347. Err(format!("Wrong prefix length {len} {prefix}:{value}"))
  348. } else {
  349. let sign = &prefix[0..1];
  350. let type_size = &prefix[1..len.saturating_sub(2)];
  351. let byte_order = &prefix[len.saturating_sub(2)..len];
  352. if sign != "u" && sign != "i" {
  353. Err(format!("Wrong prefix sign {sign} {prefix}:{value}"))
  354. } else if type_size != "16"
  355. && type_size != "32"
  356. && type_size != "64"
  357. && type_size != "128"
  358. {
  359. Err(format!(
  360. "Wrong prefix type size {type_size} {prefix}:{value}"
  361. ))
  362. } else if byte_order != "le" && byte_order != "be" {
  363. Err(format!(
  364. "Wrong prefix byte order {byte_order} {prefix}:{value}"
  365. ))
  366. } else {
  367. Ok(())
  368. }
  369. }
  370. }
  371. }
  372. }
  373. }
  374. pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
  375. where
  376. T: AsRef<str> + Display,
  377. {
  378. let value = value.as_ref();
  379. if value.len() > MAX_SEED_LEN {
  380. Err(format!(
  381. "Address seed must not be longer than {MAX_SEED_LEN} bytes"
  382. ))
  383. } else {
  384. Ok(())
  385. }
  386. }
  387. pub fn validate_maximum_full_snapshot_archives_to_retain<T>(value: T) -> Result<(), String>
  388. where
  389. T: AsRef<str> + Display,
  390. {
  391. let value = value.as_ref();
  392. if value.eq("0") {
  393. Err(String::from(
  394. "--maximum-full-snapshot-archives-to-retain cannot be zero",
  395. ))
  396. } else {
  397. Ok(())
  398. }
  399. }
  400. pub fn validate_maximum_incremental_snapshot_archives_to_retain<T>(value: T) -> Result<(), String>
  401. where
  402. T: AsRef<str> + Display,
  403. {
  404. let value = value.as_ref();
  405. if value.eq("0") {
  406. Err(String::from(
  407. "--maximum-incremental-snapshot-archives-to-retain cannot be zero",
  408. ))
  409. } else {
  410. Ok(())
  411. }
  412. }
  413. pub fn validate_cpu_ranges<T>(value: T, err_prefix: &str) -> Result<(), String>
  414. where
  415. T: AsRef<str> + Display,
  416. {
  417. parse_cpu_ranges(value.as_ref())
  418. .map(|_| ())
  419. .map_err(|e| format!("{err_prefix} {e}"))
  420. }
  421. pub fn is_non_zero(value: impl AsRef<str>) -> Result<(), String> {
  422. let value = value.as_ref();
  423. if value.eq("0") {
  424. Err(String::from("cannot be zero"))
  425. } else {
  426. Ok(())
  427. }
  428. }
  429. #[cfg(test)]
  430. mod tests {
  431. use super::*;
  432. #[test]
  433. fn test_is_derivation() {
  434. assert_eq!(is_derivation("2"), Ok(()));
  435. assert_eq!(is_derivation("0"), Ok(()));
  436. assert_eq!(is_derivation("65537"), Ok(()));
  437. assert_eq!(is_derivation("0/2"), Ok(()));
  438. assert_eq!(is_derivation("0'/2'"), Ok(()));
  439. assert!(is_derivation("a").is_err());
  440. assert!(is_derivation("4294967296").is_err());
  441. assert!(is_derivation("a/b").is_err());
  442. assert!(is_derivation("0/4294967296").is_err());
  443. }
  444. }