diff --git a/crates/trippy-core/src/error.rs b/crates/trippy-core/src/error.rs index cc6e7400..cfff3b79 100644 --- a/crates/trippy-core/src/error.rs +++ b/crates/trippy-core/src/error.rs @@ -23,9 +23,9 @@ pub enum TracerError { IoError(#[from] IoError), #[error("insufficient buffer capacity")] InsufficientCapacity, - #[error("address not available: {0}")] + #[error("address {0} not available")] AddressNotAvailable(SocketAddr), - #[error("invalid source IP address: {0}")] + #[error("source IP address {0} could not be bound")] InvalidSourceAddr(IpAddr), #[error("missing address from socket call")] MissingAddr, diff --git a/crates/trippy/src/config.rs b/crates/trippy/src/config.rs index 66bbdf34..563e28be 100644 --- a/crates/trippy/src/config.rs +++ b/crates/trippy/src/config.rs @@ -2,12 +2,10 @@ use anyhow::anyhow; use clap::ValueEnum; use clap_complete::Shell; use file::ConfigFile; -use humantime::format_duration; use itertools::Itertools; use serde::Deserialize; use std::collections::HashMap; use std::net::IpAddr; -use std::str::FromStr; use std::time::Duration; use trippy_core::{ defaults, IcmpExtensionParseMode, MultipathStrategy, PortDirection, PrivilegeMode, Protocol, @@ -409,17 +407,17 @@ impl TrippyConfig { ); let target_port = cfg_layer_opt(args.target_port, cfg_file_strategy.target_port); let source_port = cfg_layer_opt(args.source_port, cfg_file_strategy.source_port); - let source_address = cfg_layer_opt(args.source_address, cfg_file_strategy.source_address); + let source_addr = cfg_layer_opt(args.source_address, cfg_file_strategy.source_address); let interface = cfg_layer_opt(args.interface, cfg_file_strategy.interface); let min_round_duration = cfg_layer( args.min_round_duration, cfg_file_strategy.min_round_duration, - format_duration(defaults::DEFAULT_STRATEGY_MIN_ROUND_DURATION).to_string(), + defaults::DEFAULT_STRATEGY_MIN_ROUND_DURATION, ); let max_round_duration = cfg_layer( args.max_round_duration, cfg_file_strategy.max_round_duration, - format_duration(defaults::DEFAULT_STRATEGY_MAX_ROUND_DURATION).to_string(), + defaults::DEFAULT_STRATEGY_MAX_ROUND_DURATION, ); let initial_sequence = cfg_layer( args.initial_sequence, @@ -434,7 +432,7 @@ impl TrippyConfig { let grace_duration = cfg_layer( args.grace_duration, cfg_file_strategy.grace_duration, - format_duration(defaults::DEFAULT_STRATEGY_GRACE_DURATION).to_string(), + defaults::DEFAULT_STRATEGY_GRACE_DURATION, ); let max_inflight = cfg_layer( args.max_inflight, @@ -479,7 +477,7 @@ impl TrippyConfig { let read_timeout = cfg_layer( args.read_timeout, cfg_file_strategy.read_timeout, - format_duration(defaults::DEFAULT_STRATEGY_READ_TIMEOUT).to_string(), + defaults::DEFAULT_STRATEGY_READ_TIMEOUT, ); let tui_max_samples = cfg_layer( args.tui_max_samples, @@ -499,7 +497,7 @@ impl TrippyConfig { let tui_refresh_rate = cfg_layer( args.tui_refresh_rate, cfg_file_tui.tui_refresh_rate, - String::from(constants::DEFAULT_TUI_REFRESH_RATE), + constants::DEFAULT_TUI_REFRESH_RATE, ); let tui_privacy_max_ttl = cfg_layer( args.tui_privacy_max_ttl, @@ -546,7 +544,7 @@ impl TrippyConfig { let dns_timeout = cfg_layer( args.dns_timeout, cfg_file_dns.dns_timeout, - String::from(constants::DEFAULT_DNS_TIMEOUT), + constants::DEFAULT_DNS_TIMEOUT, ); let report_cycles = cfg_layer( args.report_cycles, @@ -559,18 +557,6 @@ impl TrippyConfig { (false, false, false, ProtocolConfig::Tcp) | (_, true, _, _) => Protocol::Tcp, (false, false, false, ProtocolConfig::Icmp) | (_, _, true, _) => Protocol::Icmp, }; - let read_timeout = humantime::parse_duration(&read_timeout)?; - let min_round_duration = humantime::parse_duration(&min_round_duration)?; - let max_round_duration = humantime::parse_duration(&max_round_duration)?; - let grace_duration = humantime::parse_duration(&grace_duration)?; - let source_addr = source_address - .as_ref() - .map(|addr| { - IpAddr::from_str(addr) - .map_err(|_| anyhow!("invalid source IP address format: {}", addr)) - }) - .transpose()?; - #[allow(clippy::match_same_arms)] let addr_family = match ( args.ipv4, @@ -622,14 +608,12 @@ impl TrippyConfig { )); } }; - let tui_refresh_rate = humantime::parse_duration(&tui_refresh_rate)?; let dns_resolve_method = match dns_resolve_method_config { DnsResolveMethodConfig::System => ResolveMethod::System, DnsResolveMethodConfig::Resolv => ResolveMethod::Resolv, DnsResolveMethodConfig::Google => ResolveMethod::Google, DnsResolveMethodConfig::Cloudflare => ResolveMethod::Cloudflare, }; - let dns_timeout = humantime::parse_duration(&dns_timeout)?; let max_rounds = match mode { Mode::Stream | Mode::Tui => None, Mode::Pretty @@ -744,13 +728,13 @@ impl Default for TrippyConfig { interface: None, multipath_strategy: defaults::DEFAULT_STRATEGY_MULTIPATH, port_direction: PortDirection::None, - dns_timeout: duration(constants::DEFAULT_DNS_TIMEOUT), + dns_timeout: constants::DEFAULT_DNS_TIMEOUT, dns_resolve_method: dns_resolve_method(constants::DEFAULT_DNS_RESOLVE_METHOD), dns_lookup_as_info: constants::DEFAULT_DNS_LOOKUP_AS_INFO, tui_max_samples: constants::DEFAULT_TUI_MAX_SAMPLES, tui_max_flows: constants::DEFAULT_TUI_MAX_FLOWS, tui_preserve_screen: constants::DEFAULT_TUI_PRESERVE_SCREEN, - tui_refresh_rate: duration(constants::DEFAULT_TUI_REFRESH_RATE), + tui_refresh_rate: constants::DEFAULT_TUI_REFRESH_RATE, tui_privacy_max_ttl: constants::DEFAULT_TUI_PRIVACY_MAX_TTL, tui_address_mode: constants::DEFAULT_TUI_ADDRESS_MODE, tui_as_mode: constants::DEFAULT_TUI_AS_MODE, @@ -774,10 +758,6 @@ impl Default for TrippyConfig { } } -fn duration(duration: &str) -> Duration { - humantime::parse_duration(duration).expect("valid duration") -} - fn dns_resolve_method(dns_resolve_method: DnsResolveMethodConfig) -> ResolveMethod { match dns_resolve_method { DnsResolveMethodConfig::System => ResolveMethod::System, @@ -1118,6 +1098,7 @@ mod tests { use super::*; use crossterm::event::KeyCode; use std::net::{Ipv4Addr, Ipv6Addr}; + use std::str::FromStr; use test_case::test_case; use trippy_core::Port; @@ -1280,11 +1261,11 @@ mod tests { #[test_case("trip example.com", Ok(cfg().min_round_duration(Duration::from_millis(1000)).build()); "default min round duration")] #[test_case("trip example.com --min-round-duration 250ms", Ok(cfg().min_round_duration(Duration::from_millis(250)).build()); "custom min round duration")] #[test_case("trip example.com -i 250ms", Ok(cfg().min_round_duration(Duration::from_millis(250)).build()); "custom min round duration short")] - #[test_case("trip example.com --min-round-duration 0", Err(anyhow!("time unit needed, for example 0sec or 0ms")); "invalid format min round duration")] + #[test_case("trip example.com --min-round-duration 0", Err(anyhow!("error: invalid value '0' for '--min-round-duration ': time unit needed, for example 0sec or 0ms For more information, try '--help'.")); "invalid format min round duration")] #[test_case("trip example.com", Ok(cfg().min_round_duration(Duration::from_millis(1000)).build()); "default max round duration")] #[test_case("trip example.com --max-round-duration 1250ms", Ok(cfg().max_round_duration(Duration::from_millis(1250)).build()); "custom max round duration")] #[test_case("trip example.com -T 2s", Ok(cfg().max_round_duration(Duration::from_millis(2000)).build()); "custom max round duration short")] - #[test_case("trip example.com --min-round-duration 0", Err(anyhow!("time unit needed, for example 0sec or 0ms")); "invalid format max round duration")] + #[test_case("trip example.com --max-round-duration 0", Err(anyhow!("error: invalid value '0' for '--max-round-duration ': time unit needed, for example 0sec or 0ms For more information, try '--help'.")); "invalid format max round duration")] #[test_case("trip example.com -i 250ms -T 250ms", Ok(cfg().min_round_duration(Duration::from_millis(250)).max_round_duration(Duration::from_millis(250)).build()); "custom min and max round duration")] #[test_case("trip example.com -i 300ms -T 250ms", Err(anyhow!("max-round-duration (250ms) must not be less than min-round-duration (300ms)")); "min round duration greater than max")] fn test_round_duration(cmd: &str, expected: anyhow::Result) { @@ -1294,7 +1275,7 @@ mod tests { #[test_case("trip example.com", Ok(cfg().grace_duration(Duration::from_millis(100)).build()); "default grace duration")] #[test_case("trip example.com --grace-duration 10ms", Ok(cfg().grace_duration(Duration::from_millis(10)).build()); "custom grace duration")] #[test_case("trip example.com -g 50ms", Ok(cfg().grace_duration(Duration::from_millis(50)).build()); "custom grace duration short")] - #[test_case("trip example.com --grace-duration 0", Err(anyhow!("time unit needed, for example 0sec or 0ms")); "invalid format grace duration")] + #[test_case("trip example.com --grace-duration 0", Err(anyhow!("error: invalid value '0' for '--grace-duration ': time unit needed, for example 0sec or 0ms For more information, try '--help'.")); "invalid format grace duration")] #[test_case("trip example.com --grace-duration 9ms", Err(anyhow!("grace-duration (9ms) must be between 10ms and 1s inclusive")); "invalid low grace duration")] #[test_case("trip example.com --grace-duration 1001ms", Err(anyhow!("grace-duration (1.001s) must be between 10ms and 1s inclusive")); "invalid high grace duration")] fn test_grace_duration(cmd: &str, expected: anyhow::Result) { @@ -1337,7 +1318,7 @@ mod tests { #[test_case("trip example.com", Ok(cfg().read_timeout(Duration::from_millis(10)).build()); "default read timeout")] #[test_case("trip example.com --read-timeout 20ms", Ok(cfg().read_timeout(Duration::from_millis(20)).build()); "custom read timeout")] - #[test_case("trip example.com --read-timeout 20", Err(anyhow!("time unit needed, for example 20sec or 20ms")); "invalid custom read timeout")] + #[test_case("trip example.com --read-timeout 20", Err(anyhow!("error: invalid value '20' for '--read-timeout ': time unit needed, for example 20sec or 20ms For more information, try '--help'.")); "invalid custom read timeout")] #[test_case("trip example.com --read-timeout 9ms", Err(anyhow!("read-timeout (9ms) must be between 10ms and 100ms inclusive")); "invalid low custom read timeout")] #[test_case("trip example.com --read-timeout 101ms", Err(anyhow!("read-timeout (101ms) must be between 10ms and 100ms inclusive")); "invalid high custom read timeout")] fn test_read_timeout(cmd: &str, expected: anyhow::Result) { @@ -1370,7 +1351,7 @@ mod tests { #[test_case("trip example.com --source-address 10.0.0.1", Ok(cfg().source_addr(Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))).build()); "custom ipv4 source address")] #[test_case("trip example.com --source-address 2404:6800:4005:81a::200e", Ok(cfg().source_addr(Some(IpAddr::V6(Ipv6Addr::from_str("2404:6800:4005:81a::200e").unwrap()))).build()); "custom ipv6 source address")] #[test_case("trip example.com -A 10.0.0.1", Ok(cfg().source_addr(Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))).build()); "custom ipv4 source address short")] - #[test_case("trip example.com --source-address foobar", Err(anyhow!("invalid source IP address format: foobar")); "invalid source address")] + #[test_case("trip example.com --source-address foobar", Err(anyhow!("error: invalid value 'foobar' for '--source-address ': invalid IP address syntax For more information, try '--help'.")); "invalid source address")] fn test_source_address(cmd: &str, expected: anyhow::Result) { compare(parse_config(cmd), expected); } @@ -1384,7 +1365,7 @@ mod tests { #[test_case("trip example.com", Ok(cfg().dns_timeout(Duration::from_millis(5000)).build()); "default dns timeout")] #[test_case("trip example.com --dns-timeout 20ms", Ok(cfg().dns_timeout(Duration::from_millis(20)).build()); "custom dns timeout")] - #[test_case("trip example.com --dns-timeout 20", Err(anyhow!("time unit needed, for example 20sec or 20ms")); "invalid custom dns timeout")] + #[test_case("trip example.com --dns-timeout 20", Err(anyhow!("error: invalid value '20' for '--dns-timeout ': time unit needed, for example 20sec or 20ms For more information, try '--help'.")); "invalid custom dns timeout")] fn test_dns_timeout(cmd: &str, expected: anyhow::Result) { compare(parse_config(cmd), expected); } @@ -1440,7 +1421,8 @@ mod tests { #[test_case("trip example.com --tui-refresh-rate 200ms", Ok(cfg().tui_refresh_rate(Duration::from_millis(200)).build()); "custom tui refresh rate")] #[test_case("trip example.com --tui-refresh-rate 49ms", Err(anyhow!("tui-refresh-rate (49ms) must be between 50ms and 1s inclusive")); "invalid low tui refresh rate")] #[test_case("trip example.com --tui-refresh-rate 1001ms", Err(anyhow!("tui-refresh-rate (1.001s) must be between 50ms and 1s inclusive")); "invalid high tui refresh rate")] - #[test_case("trip example.com --tui-refresh-rate foo", Err(anyhow!("expected number at 0")); "invalid format tui refresh rate")] + #[test_case("trip example.com --tui-refresh-rate foo", Err(anyhow!("error: invalid value 'foo' for '--tui-refresh-rate ': expected number at 0 For more information, try '--help'.")); "invalid format tui refresh rate")] + #[test_case("trip example.com --tui-refresh-rate 10xx", Err(anyhow!("error: invalid value '10xx' for '--tui-refresh-rate ': unknown time unit \"xx\", supported units: ns, us, ms, sec, min, hours, days, weeks, months, years (and few variations) For more information, try '--help'.")); "invalid time unit tui refresh rate")] fn test_tui_refresh_rate(cmd: &str, expected: anyhow::Result) { compare(parse_config(cmd), expected); } diff --git a/crates/trippy/src/config/cmd.rs b/crates/trippy/src/config/cmd.rs index 2d69be05..35665281 100644 --- a/crates/trippy/src/config/cmd.rs +++ b/crates/trippy/src/config/cmd.rs @@ -9,6 +9,9 @@ use anyhow::anyhow; use clap::builder::Styles; use clap::Parser; use clap_complete::Shell; +use std::net::IpAddr; +use std::str::FromStr; +use std::time::Duration; /// Trace a route to a host and record statistics #[allow(clippy::doc_markdown)] @@ -93,25 +96,25 @@ pub struct Args { pub source_port: Option, /// The source IP address [default: auto] - #[arg(short = 'A', long, conflicts_with = "interface")] - pub source_address: Option, + #[arg(short = 'A', long, value_parser = parse_addr, conflicts_with = "interface")] + pub source_address: Option, /// The network interface [default: auto] #[arg(short = 'I', long)] pub interface: Option, /// The minimum duration of every round [default: 1s] - #[arg(short = 'i', long)] - pub min_round_duration: Option, + #[arg(short = 'i', long, value_parser = parse_duration)] + pub min_round_duration: Option, /// The maximum duration of every round [default: 1s] - #[arg(short = 'T', long)] - pub max_round_duration: Option, + #[arg(short = 'T', long, value_parser = parse_duration)] + pub max_round_duration: Option, /// The period of time to wait for additional ICMP responses after the target has responded /// [default: 100ms] - #[arg(short = 'g', long)] - pub grace_duration: Option, + #[arg(short = 'g', long, value_parser = parse_duration)] + pub grace_duration: Option, /// The initial sequence number [default: 33000] #[arg(long)] @@ -150,8 +153,8 @@ pub struct Args { pub icmp_extensions: bool, /// The socket read timeout [default: 10ms] - #[arg(long)] - pub read_timeout: Option, + #[arg(long, value_parser = parse_duration)] + pub read_timeout: Option, /// How to perform DNS queries [default: system] #[arg(value_enum, short = 'r', long)] @@ -162,8 +165,8 @@ pub struct Args { pub dns_resolve_all: bool, /// The maximum time to wait to perform DNS queries [default: 5s] - #[arg(long)] - pub dns_timeout: Option, + #[arg(long, value_parser = parse_duration)] + pub dns_timeout: Option, /// Lookup autonomous system (AS) information during DNS queries [default: false] #[arg(long, short = 'z')] @@ -206,8 +209,8 @@ pub struct Args { pub tui_preserve_screen: bool, /// The Tui refresh rate [default: 100ms] - #[arg(long)] - pub tui_refresh_rate: Option, + #[arg(long, value_parser = parse_duration)] + pub tui_refresh_rate: Option, /// The maximum ttl of hops which will be masked for privacy [default: 0] #[arg(long)] @@ -283,3 +286,11 @@ fn parse_tui_binding_value(value: &str) -> anyhow::Result<(TuiCommandItem, TuiKe let binding = TuiKeyBinding::try_from(&value[pos + 1..])?; Ok((item, binding)) } + +fn parse_duration(value: &str) -> anyhow::Result { + Ok(humantime::parse_duration(value)?) +} + +fn parse_addr(value: &str) -> anyhow::Result { + Ok(IpAddr::from_str(value)?) +} diff --git a/crates/trippy/src/config/constants.rs b/crates/trippy/src/config/constants.rs index 5d8eb354..29b16dd2 100644 --- a/crates/trippy/src/config/constants.rs +++ b/crates/trippy/src/config/constants.rs @@ -53,7 +53,7 @@ pub const DEFAULT_TUI_MAX_ADDRS: u8 = 0; pub const DEFAULT_TUI_ADDRESS_MODE: AddressMode = AddressMode::Host; /// The default value for `tui-refresh-rate`. -pub const DEFAULT_TUI_REFRESH_RATE: &str = "100ms"; +pub const DEFAULT_TUI_REFRESH_RATE: Duration = Duration::from_millis(100); /// The default value for `tui-privacy-max-ttl`. pub const DEFAULT_TUI_PRIVACY_MAX_TTL: u8 = 0; @@ -68,7 +68,7 @@ pub const DEFAULT_ADDR_FAMILY: AddressFamilyConfig = AddressFamilyConfig::Ipv4Th pub const DEFAULT_DNS_LOOKUP_AS_INFO: bool = false; /// The default value for `dns-timeout`. -pub const DEFAULT_DNS_TIMEOUT: &str = "5s"; +pub const DEFAULT_DNS_TIMEOUT: Duration = Duration::from_millis(5000); /// The default value for `report-cycles`. pub const DEFAULT_REPORT_CYCLES: usize = 10; diff --git a/crates/trippy/src/config/file.rs b/crates/trippy/src/config/file.rs index 7e4113e8..4f74b93a 100644 --- a/crates/trippy/src/config/file.rs +++ b/crates/trippy/src/config/file.rs @@ -7,11 +7,13 @@ use crate::config::{ use anyhow::Context; use encoding_rs_io::DecodeReaderBytes; use etcetera::BaseStrategy; -use humantime::format_duration; use serde::Deserialize; use std::fs::File; use std::io::{BufReader, Read}; +use std::net::IpAddr; use std::path::Path; +use std::str::FromStr; +use std::time::Duration; use trippy_core::defaults; const DEFAULT_CONFIG_FILE: &str = "trippy.toml"; @@ -130,13 +132,21 @@ pub struct ConfigStrategy { pub addr_family: Option, pub target_port: Option, pub source_port: Option, - pub source_address: Option, + #[serde(default)] + #[serde(deserialize_with = "addr_deser")] + pub source_address: Option, pub interface: Option, - pub min_round_duration: Option, - pub max_round_duration: Option, + #[serde(default)] + #[serde(deserialize_with = "humantime_deser")] + pub min_round_duration: Option, + #[serde(default)] + #[serde(deserialize_with = "humantime_deser")] + pub max_round_duration: Option, pub initial_sequence: Option, pub multipath_strategy: Option, - pub grace_duration: Option, + #[serde(default)] + #[serde(deserialize_with = "humantime_deser")] + pub grace_duration: Option, pub max_inflight: Option, pub first_ttl: Option, pub max_ttl: Option, @@ -144,7 +154,9 @@ pub struct ConfigStrategy { pub payload_pattern: Option, pub tos: Option, pub icmp_extensions: Option, - pub read_timeout: Option, + #[serde(default)] + #[serde(deserialize_with = "humantime_deser")] + pub read_timeout: Option, } impl Default for ConfigStrategy { @@ -156,19 +168,13 @@ impl Default for ConfigStrategy { source_port: None, source_address: None, interface: None, - min_round_duration: Some( - format_duration(defaults::DEFAULT_STRATEGY_MIN_ROUND_DURATION).to_string(), - ), - max_round_duration: Some( - format_duration(defaults::DEFAULT_STRATEGY_MAX_ROUND_DURATION).to_string(), - ), + min_round_duration: Some(defaults::DEFAULT_STRATEGY_MIN_ROUND_DURATION), + max_round_duration: Some(defaults::DEFAULT_STRATEGY_MAX_ROUND_DURATION), initial_sequence: Some(defaults::DEFAULT_STRATEGY_INITIAL_SEQUENCE), multipath_strategy: Some(MultipathStrategyConfig::from( defaults::DEFAULT_STRATEGY_MULTIPATH, )), - grace_duration: Some( - format_duration(defaults::DEFAULT_STRATEGY_GRACE_DURATION).to_string(), - ), + grace_duration: Some(defaults::DEFAULT_STRATEGY_GRACE_DURATION), max_inflight: Some(defaults::DEFAULT_STRATEGY_MAX_INFLIGHT), first_ttl: Some(defaults::DEFAULT_STRATEGY_FIRST_TTL), max_ttl: Some(defaults::DEFAULT_STRATEGY_MAX_TTL), @@ -176,9 +182,7 @@ impl Default for ConfigStrategy { payload_pattern: Some(defaults::DEFAULT_STRATEGY_PAYLOAD_PATTERN), tos: Some(defaults::DEFAULT_STRATEGY_TOS), icmp_extensions: Some(defaults::DEFAULT_ICMP_EXTENSION_PARSE_MODE.is_enabled()), - read_timeout: Some( - format_duration(defaults::DEFAULT_STRATEGY_READ_TIMEOUT).to_string(), - ), + read_timeout: Some(defaults::DEFAULT_STRATEGY_READ_TIMEOUT), } } } @@ -189,7 +193,9 @@ pub struct ConfigDns { pub dns_resolve_method: Option, pub dns_resolve_all: Option, pub dns_lookup_as_info: Option, - pub dns_timeout: Option, + #[serde(default)] + #[serde(deserialize_with = "humantime_deser")] + pub dns_timeout: Option, } impl Default for ConfigDns { @@ -198,7 +204,7 @@ impl Default for ConfigDns { dns_resolve_method: Some(super::constants::DEFAULT_DNS_RESOLVE_METHOD), dns_resolve_all: Some(super::constants::DEFAULT_DNS_RESOLVE_ALL), dns_lookup_as_info: Some(super::constants::DEFAULT_DNS_LOOKUP_AS_INFO), - dns_timeout: Some(String::from(super::constants::DEFAULT_DNS_TIMEOUT)), + dns_timeout: Some(super::constants::DEFAULT_DNS_TIMEOUT), } } } @@ -223,7 +229,9 @@ pub struct ConfigTui { pub tui_max_samples: Option, pub tui_max_flows: Option, pub tui_preserve_screen: Option, - pub tui_refresh_rate: Option, + #[serde(default)] + #[serde(deserialize_with = "humantime_deser")] + pub tui_refresh_rate: Option, pub tui_privacy_max_ttl: Option, pub tui_address_mode: Option, pub tui_as_mode: Option, @@ -240,7 +248,7 @@ impl Default for ConfigTui { tui_max_samples: Some(super::constants::DEFAULT_TUI_MAX_SAMPLES), tui_max_flows: Some(super::constants::DEFAULT_TUI_MAX_FLOWS), tui_preserve_screen: Some(super::constants::DEFAULT_TUI_PRESERVE_SCREEN), - tui_refresh_rate: Some(String::from(super::constants::DEFAULT_TUI_REFRESH_RATE)), + tui_refresh_rate: Some(super::constants::DEFAULT_TUI_REFRESH_RATE), tui_privacy_max_ttl: Some(super::constants::DEFAULT_TUI_PRIVACY_MAX_TTL), tui_address_mode: Some(super::constants::DEFAULT_TUI_ADDRESS_MODE), tui_as_mode: Some(super::constants::DEFAULT_TUI_AS_MODE), @@ -399,6 +407,24 @@ impl Default for ConfigBindings { } } +fn humantime_deser<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + humantime::parse_duration(&String::deserialize(deserializer)?) + .map_err(serde::de::Error::custom) + .map(Some) +} + +fn addr_deser<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + IpAddr::from_str(&String::deserialize(deserializer)?) + .map_err(serde::de::Error::custom) + .map(Some) +} + #[cfg(test)] mod tests { use super::*;