With a zero-size custom error type, and a helper function because the type annotations for convincing the compiler of choosing dyn Fn…
are annoying otherwise, this is something I could come up with:
struct ResolverArgParseError;
fn try_alternatives<T>(alts: &[&dyn Fn() -> Option<T>]) -> Option<T> {
alts.iter().find_map(|f| f())
}
impl FromStr for ResolverArg {
type Err = ResolverArgParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
try_alternatives(&[
&|| (s == "system").then(|| Self::System),
&|| (s == "quad9").then(|| Self::Quad9),
&|| Some(Ip(SocketAddr::new(s.parse().ok()?, DNS_PORT))),
&|| Some(Ip(s.parse().ok()?)),
])
.ok_or(ResolverArgParseError)
}
}
of if you prefer .map(…)
over use of ?
here:
struct ResolverArgParseError;
fn try_alternatives<T>(alts: &[&dyn Fn() -> Option<T>]) -> Option<T> {
alts.iter().find_map(|f| f())
}
impl FromStr for ResolverArg {
type Err = ResolverArgParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
try_alternatives(&[
&|| (s == "system").then(|| Self::System),
&|| (s == "quad9").then(|| Self::Quad9),
&|| s.parse().ok().map(|ip| Ip(SocketAddr::new(ip, DNS_PORT))),
&|| s.parse().ok().map(Ip),
])
.ok_or(ResolverArgParseError)
}
}
Edit: Actually… it could be worse I guess, without the helper function:
struct ResolverArgParseError;
impl FromStr for ResolverArg {
type Err = ResolverArgParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
<[&dyn Fn() -> _]>::iter(&[
&|| (s == "system").then(|| Self::System),
&|| (s == "quad9").then(|| Self::Quad9),
&|| Some(Ip(SocketAddr::new(s.parse().ok()?, DNS_PORT))),
&|| Some(Ip(s.parse().ok()?)),
])
.find_map(|f| f())
.ok_or(ResolverArgParseError)
}
}
Edit2: Another way, if you looove your combinators
struct ResolverArgParseError;
impl FromStr for ResolverArg {
type Err = ResolverArgParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
None.or_else(|| (s == "system").then(|| Self::System))
.or_else(|| (s == "quad9").then(|| Self::Quad9))
.or_else(|| s.parse().ok().map(|ip| Ip(SocketAddr::new(ip, DNS_PORT))))
.or_else(|| s.parse().ok().map(Ip))
.ok_or(ResolverArgParseError)
}
}
Edit3: The combinators can also handle the original logic of keeping the last errors, e.g. like this
impl FromStr for ResolverArg {
type Err = AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
None.or_else(|| (s == "system").then(|| Self::System))
.or_else(|| (s == "quad9").then(|| Self::Quad9))
.or_else(|| s.parse().ok().map(|ip| Ip(SocketAddr::new(ip, DNS_PORT))))
.ok_or(())
.or_else(|()| s.parse().map(Ip))
}
}