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
11pub 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
17pub 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
34pub 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#[derive(thiserror::Error, Debug)]
44pub enum SocketAddressParsingError {
45 #[error("could not parse socket address: {0}")]
47 Io(#[from] std::io::Error),
48 #[error("cannot parse socket address from empty string")]
50 Empty,
51 #[error("could not parse socket address from {0}")]
53 Parse(String),
54 #[error("could not parse port: {0}")]
56 Port(#[from] std::num::ParseIntError),
57}
58
59pub 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
87pub 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}