refactor: clean up SocketAddr value parser (#777)

- Rename the function
- Add more docs explaining the supported formats
- Remove support for empty string (just use an `Option`),
  and remove support for `:` (should be considered a typo)
- Reduce allocations of strings
This commit is contained in:
Bjerg
2023-01-09 17:31:53 +01:00
committed by GitHub
parent 2b5ee2b18d
commit 40f30ec951
2 changed files with 33 additions and 30 deletions

View File

@ -8,7 +8,7 @@ use crate::{
util::{ util::{
chainspec::{chain_spec_value_parser, ChainSpecification}, chainspec::{chain_spec_value_parser, ChainSpecification},
init::{init_db, init_genesis}, init::{init_db, init_genesis},
socketaddr_value_parser, parse_socket_address,
}, },
NetworkOpts, NetworkOpts,
}; };
@ -66,7 +66,7 @@ pub struct Command {
/// Enable Prometheus metrics. /// Enable Prometheus metrics.
/// ///
/// The metrics will be served at the given interface and port. /// The metrics will be served at the given interface and port.
#[arg(long, value_name = "SOCKET", value_parser = socketaddr_value_parser)] #[arg(long, value_name = "SOCKET", value_parser = parse_socket_address)]
metrics: Option<SocketAddr>, metrics: Option<SocketAddr>,
/// Set the chain tip manually for testing purposes. /// Set the chain tip manually for testing purposes.

View File

@ -38,27 +38,30 @@ pub(crate) fn hash_or_num_value_parser(value: &str) -> Result<BlockHashOrNumber,
} }
} }
/// Parse [SocketAddr] /// Parse a [SocketAddr] from a `str`.
pub(crate) fn socketaddr_value_parser(value: &str) -> Result<SocketAddr, eyre::Error> { ///
const DEFAULT_DOMAIN: &str = "localhost"; /// The following formats are checked:
const DEFAULT_PORT: u16 = 9000; ///
let value = if value.is_empty() || value == ":" { /// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the
format!("{DEFAULT_DOMAIN}:{DEFAULT_PORT}") /// hostname is set to `localhost`.
} else if value.starts_with(':') { /// - If the value contains `:` it is assumed to be the format `<host>:<port>`
format!("{DEFAULT_DOMAIN}{value}") /// - Otherwise it is assumed to be a hostname
} else if value.ends_with(':') { ///
format!("{value}{DEFAULT_PORT}") /// An error is returned if the value is empty.
} else if value.parse::<u16>().is_ok() { pub(crate) fn parse_socket_address(value: &str) -> Result<SocketAddr, eyre::Error> {
format!("{DEFAULT_DOMAIN}:{value}") if value.is_empty() {
} else if value.contains(':') { eyre::bail!("Cannot parse socket address from an empty string");
value.to_string()
} else {
format!("{value}:{DEFAULT_PORT}")
};
match value.to_socket_addrs() {
Ok(mut iter) => iter.next().ok_or(eyre::Error::msg(format!("\"{value}\""))),
Err(e) => Err(eyre::Error::from(e).wrap_err(format!("\"{value}\""))),
} }
if value.starts_with(':') || value.parse::<u16>().is_ok() {
("localhost", 9000).to_socket_addrs()
} else if value.contains(':') {
value.to_socket_addrs()
} else {
(value, 9000).to_socket_addrs()
}?
.next()
.ok_or_else(|| eyre::eyre!("Could not parse socket address from {}", value))
} }
/// Tracing utility /// Tracing utility
@ -129,16 +132,16 @@ pub mod reth_tracing {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::net::ToSocketAddrs; use super::*;
use super::socketaddr_value_parser;
#[test] #[test]
fn parse_socketaddr_with_default() { fn parse_socket_addresses() {
let expected = "localhost:9000".to_socket_addrs().unwrap().next().unwrap(); for value in ["localhost:9000", ":9000", "9000", "localhost"] {
let test_values = ["localhost:9000", ":9000", "9000", "localhost:", "localhost", ":", ""]; let socket_addr = parse_socket_address(value)
for value in test_values { .expect(&format!("could not parse socket address: {}", value));
assert_eq!(socketaddr_value_parser(value).expect("value_parser failed"), expected);
assert!(socket_addr.ip().is_loopback());
assert_eq!(socket_addr.port(), 9000);
} }
} }
} }