1use {
2 crate::{
3 input_parsers::signer::{SignerSource, SignerSourceKind},
4 keypair::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
15fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
16where
17 T: AsRef<str> + Display,
18 U: FromStr,
19 U::Err: Display,
20{
21 string
22 .as_ref()
23 .parse::<U>()
24 .map(|_| ())
25 .map_err(|err| format!("error parsing '{string}': {err}"))
26}
27
28#[deprecated(since = "1.17.0", note = "please use `clap::value_parser!` instead")]
31pub fn is_parsable<T>(string: &str) -> Result<(), String>
32where
33 T: FromStr,
34 T::Err: Display,
35{
36 is_parsable_generic::<T, &str>(string)
37}
38
39#[deprecated(
42 since = "1.17.0",
43 note = "please use `clap::builder::RangedI64ValueParser` instead"
44)]
45pub fn is_within_range<T, R>(string: String, range: R) -> Result<(), String>
46where
47 T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add<Output = T> + From<usize>,
48 T::Err: Display,
49 R: RangeBounds<T> + std::fmt::Debug,
50{
51 match string.parse::<T>() {
52 Ok(input) => {
53 if !range.contains(&input) {
54 Err(format!("input '{input:?}' out of range {range:?}"))
55 } else {
56 Ok(())
57 }
58 }
59 Err(err) => Err(format!("error parsing '{string}': {err}")),
60 }
61}
62
63#[deprecated(
65 since = "1.18.0",
66 note = "please use `clap::value_parser!(Pubkey)` instead"
67)]
68pub fn is_pubkey(string: &str) -> Result<(), String> {
69 is_parsable_generic::<Pubkey, _>(string)
70}
71
72#[deprecated(
74 since = "1.17.0",
75 note = "please use `clap::value_parser!(Hash)` instead"
76)]
77pub fn is_hash<T>(string: T) -> Result<(), String>
78where
79 T: AsRef<str> + Display,
80{
81 is_parsable_generic::<Hash, _>(string)
82}
83
84#[deprecated(
86 since = "1.18.0",
87 note = "please use `SignerSourceParserBuilder::default().allow_file_path().build()` instead"
88)]
89pub fn is_keypair<T>(string: T) -> Result<(), String>
90where
91 T: AsRef<str> + Display,
92{
93 read_keypair_file(string.as_ref())
94 .map(|_| ())
95 .map_err(|err| format!("{err}"))
96}
97
98#[deprecated(
100 since = "1.18.0",
101 note = "please use `SignerSourceParserBuilder::default().allow_file_path().allow_prompt().allow_legacy().build()` instead"
102)]
103pub fn is_keypair_or_ask_keyword<T>(string: T) -> Result<(), String>
104where
105 T: AsRef<str> + Display,
106{
107 if string.as_ref() == ASK_KEYWORD {
108 return Ok(());
109 }
110 read_keypair_file(string.as_ref())
111 .map(|_| ())
112 .map_err(|err| format!("{err}"))
113}
114
115#[deprecated(
117 since = "1.18.0",
118 note = "please use `SignerSourceParserBuilder::default().allow_prompt().allow_legacy().build()` instead"
119)]
120pub fn is_prompt_signer_source(string: &str) -> Result<(), String> {
121 if string == ASK_KEYWORD {
122 return Ok(());
123 }
124 match SignerSource::parse(string)
125 .map_err(|err| format!("{err}"))?
126 .kind
127 {
128 SignerSourceKind::Prompt => Ok(()),
129 _ => Err(format!(
130 "Unable to parse input as `prompt:` URI scheme or `ASK` keyword: {string}"
131 )),
132 }
133}
134
135#[deprecated(
137 since = "1.18.0",
138 note = "please use `SignerSourceParserBuilder::default().allow_pubkey().allow_file_path().build()` instead"
139)]
140#[allow(deprecated)]
141pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
142where
143 T: AsRef<str> + Display,
144{
145 is_pubkey(string.as_ref()).or_else(|_| is_keypair(string))
146}
147
148#[deprecated(
151 since = "1.18.0",
152 note = "please use `SignerSourceParserBuilder::default().allow_all().build()` instead"
153)]
154#[allow(deprecated)]
155pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
156where
157 T: AsRef<str> + Display,
158{
159 match SignerSource::parse(string.as_ref())
160 .map_err(|err| format!("{err}"))?
161 .kind
162 {
163 SignerSourceKind::Filepath(path) => is_keypair(path),
164 _ => Ok(()),
165 }
166}
167
168#[deprecated(
177 since = "1.18.0",
178 note = "please use `SignerSourceParserBuilder::default().allow_all().build()` instead"
179)]
180#[allow(deprecated)]
181pub fn is_valid_signer<T>(string: T) -> Result<(), String>
182where
183 T: AsRef<str> + Display,
184{
185 is_valid_pubkey(string)
186}
187
188#[deprecated(
190 since = "1.17.0",
191 note = "please use `clap::value_parser!(PubkeySignature)` instead"
192)]
193#[allow(deprecated)]
194pub fn is_pubkey_sig<T>(string: T) -> Result<(), String>
195where
196 T: AsRef<str> + Display,
197{
198 let mut signer = string.as_ref().split('=');
199 match Pubkey::from_str(
200 signer
201 .next()
202 .ok_or_else(|| "Malformed signer string".to_string())?,
203 ) {
204 Ok(_) => {
205 match Signature::from_str(
206 signer
207 .next()
208 .ok_or_else(|| "Malformed signer string".to_string())?,
209 ) {
210 Ok(_) => Ok(()),
211 Err(err) => Err(format!("{err}")),
212 }
213 }
214 Err(err) => Err(format!("{err}")),
215 }
216}
217
218#[deprecated(since = "1.17.0", note = "please use `parse_url` instead")]
220pub fn is_url<T>(string: T) -> Result<(), String>
221where
222 T: AsRef<str> + Display,
223{
224 match url::Url::parse(string.as_ref()) {
225 Ok(url) => {
226 if url.has_host() {
227 Ok(())
228 } else {
229 Err("no host provided".to_string())
230 }
231 }
232 Err(err) => Err(format!("{err}")),
233 }
234}
235
236#[deprecated(since = "1.17.0", note = "please use `parse_url_or_moniker` instead")]
237pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
238where
239 T: AsRef<str> + Display,
240{
241 match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
242 Ok(url) => {
243 if url.has_host() {
244 Ok(())
245 } else {
246 Err("no host provided".to_string())
247 }
248 }
249 Err(err) => Err(format!("{err}")),
250 }
251}
252
253pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
254 match url_or_moniker.as_ref() {
255 "m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com",
256 "t" | "testnet" => "https://api.testnet.solana.com",
257 "d" | "devnet" => "https://api.devnet.solana.com",
258 "l" | "localhost" => "http://localhost:8899",
259 url => url,
260 }
261 .to_string()
262}
263
264#[deprecated(
265 since = "1.17.0",
266 note = "please use `clap::value_parser!(Epoch)` instead"
267)]
268pub fn is_epoch<T>(epoch: T) -> Result<(), String>
269where
270 T: AsRef<str> + Display,
271{
272 is_parsable_generic::<Epoch, _>(epoch)
273}
274
275#[deprecated(
276 since = "1.17.0",
277 note = "please use `clap::value_parser!(Slot)` instead"
278)]
279pub fn is_slot<T>(slot: T) -> Result<(), String>
280where
281 T: AsRef<str> + Display,
282{
283 is_parsable_generic::<Slot, _>(slot)
284}
285
286#[deprecated(since = "1.17.0", note = "please use `parse_pow2` instead")]
287pub fn is_pow2<T>(bins: T) -> Result<(), String>
288where
289 T: AsRef<str> + Display,
290{
291 bins.as_ref()
292 .parse::<usize>()
293 .map_err(|e| format!("Unable to parse, provided: {bins}, err: {e}"))
294 .and_then(|v| {
295 if !v.is_power_of_two() {
296 Err(format!("Must be a power of 2: {v}"))
297 } else {
298 Ok(())
299 }
300 })
301}
302
303#[deprecated(
304 since = "1.17.0",
305 note = "please use `clap_value_parser!(u16)` instead"
306)]
307pub fn is_port<T>(port: T) -> Result<(), String>
308where
309 T: AsRef<str> + Display,
310{
311 is_parsable_generic::<u16, _>(port)
312}
313
314#[deprecated(since = "1.17.0", note = "please use `parse_percentage` instead")]
315pub fn is_valid_percentage<T>(percentage: T) -> Result<(), String>
316where
317 T: AsRef<str> + Display,
318{
319 percentage
320 .as_ref()
321 .parse::<u8>()
322 .map_err(|e| format!("Unable to parse input percentage, provided: {percentage}, err: {e}"))
323 .and_then(|v| {
324 if v > 100 {
325 Err(format!(
326 "Percentage must be in range of 0 to 100, provided: {v}"
327 ))
328 } else {
329 Ok(())
330 }
331 })
332}
333
334#[deprecated(since = "1.17.0", note = "please use `Amount::parse_decimal` instead")]
335pub fn is_amount<T>(amount: T) -> Result<(), String>
336where
337 T: AsRef<str> + Display,
338{
339 if amount.as_ref().parse::<u64>().is_ok() || amount.as_ref().parse::<f64>().is_ok() {
340 Ok(())
341 } else {
342 Err(format!(
343 "Unable to parse input amount as integer or float, provided: {amount}"
344 ))
345 }
346}
347
348#[deprecated(
349 since = "1.17.0",
350 note = "please use `TokenAmount::parse_decimal` instead"
351)]
352pub fn is_amount_or_all<T>(amount: T) -> Result<(), String>
353where
354 T: AsRef<str> + Display,
355{
356 if amount.as_ref().parse::<u64>().is_ok()
357 || amount.as_ref().parse::<f64>().is_ok()
358 || amount.as_ref() == "ALL"
359 {
360 Ok(())
361 } else {
362 Err(format!(
363 "Unable to parse input amount as integer or float, provided: {amount}"
364 ))
365 }
366}
367
368#[deprecated(since = "1.17.0", note = "please use `parse_rfc3339_datetime` instead")]
369pub fn is_rfc3339_datetime<T>(value: T) -> Result<(), String>
370where
371 T: AsRef<str> + Display,
372{
373 DateTime::parse_from_rfc3339(value.as_ref())
374 .map(|_| ())
375 .map_err(|e| format!("{e}"))
376}
377
378#[deprecated(since = "1.17.0", note = "please use `parse_derivation` instead")]
379pub fn is_derivation<T>(value: T) -> Result<(), String>
380where
381 T: AsRef<str> + Display,
382{
383 let value = value.as_ref().replace('\'', "");
384 let mut parts = value.split('/');
385 let account = parts.next().unwrap();
386 account
387 .parse::<u32>()
388 .map_err(|e| format!("Unable to parse derivation, provided: {account}, err: {e}"))
389 .and_then(|_| {
390 if let Some(change) = parts.next() {
391 change.parse::<u32>().map_err(|e| {
392 format!("Unable to parse derivation, provided: {change}, err: {e}")
393 })
394 } else {
395 Ok(0)
396 }
397 })
398 .map(|_| ())
399}
400
401#[deprecated(since = "1.17.0", note = "please use `parse_structured_seed` instead")]
402pub fn is_structured_seed<T>(value: T) -> Result<(), String>
403where
404 T: AsRef<str> + Display,
405{
406 let (prefix, value) = value
407 .as_ref()
408 .split_once(':')
409 .ok_or("Seed must contain ':' as delimiter")
410 .unwrap();
411 if prefix.is_empty() || value.is_empty() {
412 Err(String::from("Seed prefix or value is empty"))
413 } else {
414 match prefix {
415 "string" | "pubkey" | "hex" | "u8" => Ok(()),
416 _ => {
417 let len = prefix.len();
418 if len != 5 && len != 6 {
419 Err(format!("Wrong prefix length {len} {prefix}:{value}"))
420 } else {
421 let sign = &prefix[0..1];
422 let type_size = &prefix[1..len.saturating_sub(2)];
423 let byte_order = &prefix[len.saturating_sub(2)..len];
424 if sign != "u" && sign != "i" {
425 Err(format!("Wrong prefix sign {sign} {prefix}:{value}"))
426 } else if type_size != "16"
427 && type_size != "32"
428 && type_size != "64"
429 && type_size != "128"
430 {
431 Err(format!(
432 "Wrong prefix type size {type_size} {prefix}:{value}"
433 ))
434 } else if byte_order != "le" && byte_order != "be" {
435 Err(format!(
436 "Wrong prefix byte order {byte_order} {prefix}:{value}"
437 ))
438 } else {
439 Ok(())
440 }
441 }
442 }
443 }
444 }
445}
446
447#[deprecated(
448 since = "1.17.0",
449 note = "please use `parse_derived_address_seed` instead"
450)]
451pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
452where
453 T: AsRef<str> + Display,
454{
455 let value = value.as_ref();
456 if value.len() > MAX_SEED_LEN {
457 Err(format!(
458 "Address seed must not be longer than {MAX_SEED_LEN} bytes"
459 ))
460 } else {
461 Ok(())
462 }
463}