1use {
2 crate::keypair::{parse_signer_source, SignerSourceKind, ASK_KEYWORD},
3 chrono::DateTime,
4 solana_sdk::{
5 clock::{Epoch, Slot},
6 hash::Hash,
7 pubkey::{Pubkey, MAX_SEED_LEN},
8 signature::{read_keypair_file, Signature},
9 },
10 std::{fmt::Display, str::FromStr},
11};
12
13fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
14where
15 T: AsRef<str> + Display,
16 U: FromStr,
17 U::Err: Display,
18{
19 string
20 .as_ref()
21 .parse::<U>()
22 .map(|_| ())
23 .map_err(|err| format!("error parsing '{}': {}", string, err))
24}
25
26pub fn is_parsable<T>(string: String) -> Result<(), String>
29where
30 T: FromStr,
31 T::Err: Display,
32{
33 is_parsable_generic::<T, String>(string)
34}
35
36pub fn is_within_range<T>(string: String, range_min: T, range_max: T) -> Result<(), String>
39where
40 T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add<Output = T> + From<usize>,
41 T::Err: Display,
42{
43 match string.parse::<T>() {
44 Ok(input) => {
45 let range = range_min..range_max + 1.into();
46 if !range.contains(&input) {
47 Err(format!(
48 "input '{:?}' out of range ({:?}..{:?}]",
49 input, range_min, range_max
50 ))
51 } else {
52 Ok(())
53 }
54 }
55 Err(err) => Err(format!("error parsing '{}': {}", string, err)),
56 }
57}
58
59pub fn is_pubkey<T>(string: T) -> Result<(), String>
61where
62 T: AsRef<str> + Display,
63{
64 is_parsable_generic::<Pubkey, _>(string)
65}
66
67pub fn is_hash<T>(string: T) -> Result<(), String>
69where
70 T: AsRef<str> + Display,
71{
72 is_parsable_generic::<Hash, _>(string)
73}
74
75pub fn is_keypair<T>(string: T) -> Result<(), String>
77where
78 T: AsRef<str> + Display,
79{
80 read_keypair_file(string.as_ref())
81 .map(|_| ())
82 .map_err(|err| format!("{}", err))
83}
84
85pub fn is_keypair_or_ask_keyword<T>(string: T) -> Result<(), String>
87where
88 T: AsRef<str> + Display,
89{
90 if string.as_ref() == ASK_KEYWORD {
91 return Ok(());
92 }
93 read_keypair_file(string.as_ref())
94 .map(|_| ())
95 .map_err(|err| format!("{}", err))
96}
97
98pub fn is_prompt_signer_source<T>(string: T) -> Result<(), String>
100where
101 T: AsRef<str> + Display,
102{
103 if string.as_ref() == ASK_KEYWORD {
104 return Ok(());
105 }
106 match parse_signer_source(string.as_ref())
107 .map_err(|err| format!("{}", err))?
108 .kind
109 {
110 SignerSourceKind::Prompt => Ok(()),
111 _ => Err(format!(
112 "Unable to parse input as `prompt:` URI scheme or `ASK` keyword: {}",
113 string
114 )),
115 }
116}
117
118pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
120where
121 T: AsRef<str> + Display,
122{
123 is_pubkey(string.as_ref()).or_else(|_| is_keypair(string))
124}
125
126pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
129where
130 T: AsRef<str> + Display,
131{
132 match parse_signer_source(string.as_ref())
133 .map_err(|err| format!("{}", err))?
134 .kind
135 {
136 SignerSourceKind::Filepath(path) => is_keypair(path),
137 _ => Ok(()),
138 }
139}
140
141pub fn is_valid_signer<T>(string: T) -> Result<(), String>
150where
151 T: AsRef<str> + Display,
152{
153 is_valid_pubkey(string)
154}
155
156pub fn is_pubkey_sig<T>(string: T) -> Result<(), String>
158where
159 T: AsRef<str> + Display,
160{
161 let mut signer = string.as_ref().split('=');
162 match Pubkey::from_str(
163 signer
164 .next()
165 .ok_or_else(|| "Malformed signer string".to_string())?,
166 ) {
167 Ok(_) => {
168 match Signature::from_str(
169 signer
170 .next()
171 .ok_or_else(|| "Malformed signer string".to_string())?,
172 ) {
173 Ok(_) => Ok(()),
174 Err(err) => Err(format!("{}", err)),
175 }
176 }
177 Err(err) => Err(format!("{}", err)),
178 }
179}
180
181pub fn is_url<T>(string: T) -> Result<(), String>
183where
184 T: AsRef<str> + Display,
185{
186 match url::Url::parse(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
198pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
199where
200 T: AsRef<str> + Display,
201{
202 match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
203 Ok(url) => {
204 if url.has_host() {
205 Ok(())
206 } else {
207 Err("no host provided".to_string())
208 }
209 }
210 Err(err) => Err(format!("{}", err)),
211 }
212}
213
214pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
215 match url_or_moniker.as_ref() {
216 "m" | "mainnet-beta" => "https://api.mainnet-beta.safecoin.org",
217 "t" | "testnet" => "https://api.testnet.safecoin.org",
218 "d" | "devnet" => "https://api.devnet.safecoin.org",
219 "l" | "localhost" => "http://localhost:8899",
220 url => url,
221 }
222 .to_string()
223}
224
225pub fn is_epoch<T>(epoch: T) -> Result<(), String>
226where
227 T: AsRef<str> + Display,
228{
229 is_parsable_generic::<Epoch, _>(epoch)
230}
231
232pub fn is_slot<T>(slot: T) -> Result<(), String>
233where
234 T: AsRef<str> + Display,
235{
236 is_parsable_generic::<Slot, _>(slot)
237}
238
239pub fn is_pow2<T>(bins: T) -> Result<(), String>
240where
241 T: AsRef<str> + Display,
242{
243 bins.as_ref()
244 .parse::<usize>()
245 .map_err(|e| format!("Unable to parse, provided: {}, err: {}", bins, e))
246 .and_then(|v| {
247 if !v.is_power_of_two() {
248 Err(format!("Must be a power of 2: {}", v))
249 } else {
250 Ok(())
251 }
252 })
253}
254
255pub fn is_port<T>(port: T) -> Result<(), String>
256where
257 T: AsRef<str> + Display,
258{
259 is_parsable_generic::<u16, _>(port)
260}
261
262pub fn is_valid_percentage<T>(percentage: T) -> Result<(), String>
263where
264 T: AsRef<str> + Display,
265{
266 percentage
267 .as_ref()
268 .parse::<u8>()
269 .map_err(|e| {
270 format!(
271 "Unable to parse input percentage, provided: {}, err: {}",
272 percentage, e
273 )
274 })
275 .and_then(|v| {
276 if v > 100 {
277 Err(format!(
278 "Percentage must be in range of 0 to 100, provided: {}",
279 v
280 ))
281 } else {
282 Ok(())
283 }
284 })
285}
286
287pub fn is_amount<T>(amount: T) -> Result<(), String>
288where
289 T: AsRef<str> + Display,
290{
291 if amount.as_ref().parse::<u64>().is_ok() || amount.as_ref().parse::<f64>().is_ok() {
292 Ok(())
293 } else {
294 Err(format!(
295 "Unable to parse input amount as integer or float, provided: {}",
296 amount
297 ))
298 }
299}
300
301pub fn is_amount_or_all<T>(amount: T) -> Result<(), String>
302where
303 T: AsRef<str> + Display,
304{
305 if amount.as_ref().parse::<u64>().is_ok()
306 || amount.as_ref().parse::<f64>().is_ok()
307 || amount.as_ref() == "ALL"
308 {
309 Ok(())
310 } else {
311 Err(format!(
312 "Unable to parse input amount as integer or float, provided: {}",
313 amount
314 ))
315 }
316}
317
318pub fn is_rfc3339_datetime<T>(value: T) -> Result<(), String>
319where
320 T: AsRef<str> + Display,
321{
322 DateTime::parse_from_rfc3339(value.as_ref())
323 .map(|_| ())
324 .map_err(|e| format!("{}", e))
325}
326
327pub fn is_derivation<T>(value: T) -> Result<(), String>
328where
329 T: AsRef<str> + Display,
330{
331 let value = value.as_ref().replace('\'', "");
332 let mut parts = value.split('/');
333 let account = parts.next().unwrap();
334 account
335 .parse::<u32>()
336 .map_err(|e| {
337 format!(
338 "Unable to parse derivation, provided: {}, err: {}",
339 account, e
340 )
341 })
342 .and_then(|_| {
343 if let Some(change) = parts.next() {
344 change.parse::<u32>().map_err(|e| {
345 format!(
346 "Unable to parse derivation, provided: {}, err: {}",
347 change, e
348 )
349 })
350 } else {
351 Ok(0)
352 }
353 })
354 .map(|_| ())
355}
356
357pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
358where
359 T: AsRef<str> + Display,
360{
361 let value = value.as_ref();
362 if value.len() > MAX_SEED_LEN {
363 Err(format!(
364 "Address seed must not be longer than {} bytes",
365 MAX_SEED_LEN
366 ))
367 } else {
368 Ok(())
369 }
370}
371
372pub fn is_niceness_adjustment_valid<T>(value: T) -> Result<(), String>
373where
374 T: AsRef<str> + Display,
375{
376 let adjustment = value.as_ref().parse::<i8>().map_err(|err| {
377 format!(
378 "error parsing niceness adjustment value '{}': {}",
379 value, err
380 )
381 })?;
382 if solana_perf::thread::is_renice_allowed(adjustment) {
383 Ok(())
384 } else {
385 Err(String::from(
386 "niceness adjustment supported only on Linux; negative adjustment \
387 (priority increase) requires root or CAP_SYS_NICE (see `man 7 capabilities` \
388 for details)",
389 ))
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn test_is_derivation() {
399 assert_eq!(is_derivation("2"), Ok(()));
400 assert_eq!(is_derivation("0"), Ok(()));
401 assert_eq!(is_derivation("65537"), Ok(()));
402 assert_eq!(is_derivation("0/2"), Ok(()));
403 assert_eq!(is_derivation("0'/2'"), Ok(()));
404 assert!(is_derivation("a").is_err());
405 assert!(is_derivation("4294967296").is_err());
406 assert!(is_derivation("a/b").is_err());
407 assert!(is_derivation("0/4294967296").is_err());
408 }
409
410 #[test]
411 fn test_is_niceness_adjustment_valid() {
412 assert_eq!(is_niceness_adjustment_valid("0"), Ok(()));
413 assert!(is_niceness_adjustment_valid("128").is_err());
414 assert!(is_niceness_adjustment_valid("-129").is_err());
415 }
416}