reth_cli_util/
parsers.rs

1use alloy_eips::BlockHashOrNumber;
2use alloy_primitives::B256;
3use reth_fs_util::FsPathError;
4use std::{
5    net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs},
6    path::Path,
7    str::FromStr,
8    time::Duration,
9};
10
11/// Helper to parse a [Duration] from seconds
12pub fn parse_duration_from_secs(arg: &str) -> eyre::Result<Duration, std::num::ParseIntError> {
13    let seconds = arg.parse()?;
14    Ok(Duration::from_secs(seconds))
15}
16
17/// Helper to parse a [Duration] from seconds if it's a number or milliseconds if the input contains
18/// a `ms` suffix:
19///  * `5ms` -> 5 milliseconds
20///  * `5` -> 5 seconds
21///  * `5s` -> 5 seconds
22pub fn parse_duration_from_secs_or_ms(
23    arg: &str,
24) -> eyre::Result<Duration, std::num::ParseIntError> {
25    if arg.ends_with("ms") {
26        arg.trim_end_matches("ms").parse().map(Duration::from_millis)
27    } else if arg.ends_with('s') {
28        arg.trim_end_matches('s').parse().map(Duration::from_secs)
29    } else {
30        arg.parse().map(Duration::from_secs)
31    }
32}
33
34/// Parse [`BlockHashOrNumber`]
35pub fn hash_or_num_value_parser(value: &str) -> eyre::Result<BlockHashOrNumber, eyre::Error> {
36    match B256::from_str(value) {
37        Ok(hash) => Ok(BlockHashOrNumber::Hash(hash)),
38        Err(_) => Ok(BlockHashOrNumber::Number(value.parse()?)),
39    }
40}
41
42/// Error thrown while parsing a socket address.
43#[derive(thiserror::Error, Debug)]
44pub enum SocketAddressParsingError {
45    /// Failed to convert the string into a socket addr
46    #[error("could not parse socket address: {0}")]
47    Io(#[from] std::io::Error),
48    /// Input must not be empty
49    #[error("cannot parse socket address from empty string")]
50    Empty,
51    /// Failed to parse the address
52    #[error("could not parse socket address from {0}")]
53    Parse(String),
54    /// Failed to parse port
55    #[error("could not parse port: {0}")]
56    Port(#[from] std::num::ParseIntError),
57}
58
59/// Parse a [`SocketAddr`] from a `str`.
60///
61/// The following formats are checked:
62///
63/// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the
64///   hostname is set to `localhost`.
65/// - If the value contains `:` it is assumed to be the format `<host>:<port>`
66/// - Otherwise it is assumed to be a hostname
67///
68/// An error is returned if the value is empty.
69pub fn parse_socket_address(value: &str) -> eyre::Result<SocketAddr, SocketAddressParsingError> {
70    if value.is_empty() {
71        return Err(SocketAddressParsingError::Empty)
72    }
73
74    if let Some(port) = value.strip_prefix(':').or_else(|| value.strip_prefix("localhost:")) {
75        let port: u16 = port.parse()?;
76        return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
77    }
78    if let Ok(port) = value.parse() {
79        return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
80    }
81    value
82        .to_socket_addrs()?
83        .next()
84        .ok_or_else(|| SocketAddressParsingError::Parse(value.to_string()))
85}
86
87/// Wrapper around [`reth_fs_util::read_json_file`] which can be used as a clap value parser.
88pub fn read_json_from_file<T: serde::de::DeserializeOwned>(path: &str) -> Result<T, FsPathError> {
89    reth_fs_util::read_json_file(Path::new(path))
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use rand::Rng;
96
97    #[test]
98    fn parse_socket_addresses() {
99        for value in ["localhost:9000", ":9000", "9000"] {
100            let socket_addr = parse_socket_address(value)
101                .unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
102
103            assert!(socket_addr.ip().is_loopback());
104            assert_eq!(socket_addr.port(), 9000);
105        }
106    }
107
108    #[test]
109    fn parse_socket_address_random() {
110        let port: u16 = rand::thread_rng().gen();
111
112        for value in [format!("localhost:{port}"), format!(":{port}"), port.to_string()] {
113            let socket_addr = parse_socket_address(&value)
114                .unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
115
116            assert!(socket_addr.ip().is_loopback());
117            assert_eq!(socket_addr.port(), port);
118        }
119    }
120
121    #[test]
122    fn parse_ms_or_seconds() {
123        let ms = parse_duration_from_secs_or_ms("5ms").unwrap();
124        assert_eq!(ms, Duration::from_millis(5));
125
126        let seconds = parse_duration_from_secs_or_ms("5").unwrap();
127        assert_eq!(seconds, Duration::from_secs(5));
128
129        let seconds = parse_duration_from_secs_or_ms("5s").unwrap();
130        assert_eq!(seconds, Duration::from_secs(5));
131
132        assert!(parse_duration_from_secs_or_ms("5ns").is_err());
133    }
134}