diff --git a/src/lib.rs b/src/lib.rs index 355eebd1f..31448bde4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,11 +123,13 @@ use std::cmp; use std::error::Error; use std::fmt::{self, Write}; use std::hash; +use std::io; use std::mem; -use std::net::IpAddr; +use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::ops::{Range, RangeFrom, RangeTo}; use std::path::{Path, PathBuf}; use std::str; +use std::vec; pub use host::Host; pub use origin::{OpaqueOrigin, Origin}; @@ -945,6 +947,50 @@ impl Url { self.port.or_else(|| parser::default_port(self.scheme())) } + /// If the URL has a host, return something that implements `ToSocketAddrs`. + /// + /// If the URL has no port number and the scheme’s default port number is not known + /// (see `Url::port_or_known_default`), + /// the closure is called to obtain a port number. + /// Typically, this closure can match on the result `Url::scheme` + /// to have per-scheme default port numbers, + /// and panic for schemes it’s not prepared to handle. + /// For example: + /// + /// ```rust + /// # use url::Url; + /// # use std::net::TcpStream; + /// # use std::io; + /// fn connect(url: &Url) -> io::Result { + /// TcpStream::connect(url.with_default_port(default_port)?) + /// } + /// + /// fn default_port(url: &Url) -> Result { + /// match url.scheme() { + /// "git" => Ok(9418), + /// "git+ssh" => Ok(22), + /// "git+https" => Ok(443), + /// "git+http" => Ok(80), + /// _ => Err(()), + /// } + /// } + /// ``` + pub fn with_default_port(&self, f: F) -> io::Result<(&str, u16)> + where + F: FnOnce(&Url) -> Result, + { + Ok((self + .host_str() + .ok_or(()) + .or_else(|()| io_error("URL has no host"))?, + self + .port_or_known_default() + .ok_or(()) + .or_else(|()| f(self)) + .or_else(|()| io_error("URL has no port number"))?, + )) + } + /// Return the path for this URL, as a percent-encoded ASCII string. /// For cannot-be-a-base URLs, this is an arbitrary string that doesn’t start with '/'. /// For other URLs, this starts with a '/' slash @@ -2162,6 +2208,16 @@ impl Url { } } +/// Return an error if `Url::host` or `Url::port_or_known_default` return `None`. +impl ToSocketAddrs for Url { + type Iter = vec::IntoIter; + + fn to_socket_addrs(&self) -> io::Result { + self.with_default_port(|_| Err(()))?.to_socket_addrs() + } +} + + /// Parse a string as an URL, without a base URL or encoding override. impl str::FromStr for Url { type Err = ParseError; @@ -2172,6 +2228,10 @@ impl str::FromStr for Url { } } +fn io_error(reason: &str) -> io::Result { + Err(io::Error::new(io::ErrorKind::InvalidData, reason)) +} + /// Display the serialization of this URL. impl fmt::Display for Url { #[inline]