Skip to content

Commit

Permalink
Several changes
Browse files Browse the repository at this point in the history
Improve doc
Unstabilize `Authority::port_to_u16`
Make `Authority::to_socket_addrs` conservative
  • Loading branch information
yescallop committed Jun 8, 2024
1 parent db4cde7 commit b759c34
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 31 deletions.
6 changes: 4 additions & 2 deletions src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ impl<S: To<SchemeEnd>> Builder<S> {
/// Sets the [scheme] component.
///
/// Note that the scheme component is *case-insensitive* and normalized to
/// *lowercase*. You should use only lowercase in scheme names for consistency.
/// *lowercase*. For consistency, you should only produce lowercase scheme names.
///
/// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986/#section-3.1
pub fn scheme(mut self, scheme: &Scheme) -> Builder<SchemeEnd> {
Expand Down Expand Up @@ -314,7 +314,7 @@ impl<S: To<HostEnd>> Builder<S> {
/// the resulting [`Uri`] will output a [`Host::Ipv4`] variant instead.
///
/// Note that the host subcomponent is *case-insensitive* and normalized to
/// *lowercase*. You should use only lowercase in registered names for consistency.
/// *lowercase*. For consistency, you should only produce lowercase registered names.
///
/// [host]: https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
/// [`Ipv4Addr`]: std::net::Ipv4Addr
Expand Down Expand Up @@ -364,6 +364,8 @@ impl<S: To<PortEnd>> Builder<S> {
///
/// This method takes either a `u16` or <code>&amp;[EStr]&lt;[Port]&gt;</code> as argument.
///
/// For consistency, you should not produce an empty port.
///
/// [port-spec]: https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.3
pub fn port<P: AsPort>(mut self, port: P) -> Builder<PortEnd> {
port.push_to(&mut self.inner.buf);
Expand Down
41 changes: 24 additions & 17 deletions src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
Uri,
};
use borrow_or_share::BorrowOrShare;
use core::{iter, num::ParseIntError};
use core::iter;
use ref_cast::{ref_cast_custom, RefCastCustom};

#[cfg(feature = "net")]
Expand Down Expand Up @@ -284,13 +284,16 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Authority<T> {

/// Returns the optional [port] subcomponent.
///
/// [port]: https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.3
/// A scheme may define a default port to use when the port is
/// not present or is empty.
///
/// Note that the port may be empty, with leading zeros, or larger than [`u16::MAX`].
/// It is up to you to decide whether to deny such ports, fallback to the scheme's
/// default if it is empty, ignore the leading zeros, or use a different addressing
/// mechanism that allows ports larger than [`u16::MAX`].
///
/// [port]: https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.3
///
/// # Examples
///
/// ```
Expand Down Expand Up @@ -321,12 +324,14 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Authority<T> {

/// Converts the [port] subcomponent to `u16`, if present.
///
/// Leading zeros are ignored.
/// Returns `Ok(None)` if the port is not present or is empty,
/// or `Err` if the port cannot be parsed into `u16`.
/// Returns `Ok(None)` if the port is not present. Leading zeros are ignored.
///
/// [port]: https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.3
///
/// # Errors
///
/// Returns `Err` if the port cannot be parsed into `u16`.
///
/// # Examples
///
/// ```
Expand All @@ -336,36 +341,36 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Authority<T> {
/// let auth = uri.authority().unwrap();
/// assert_eq!(auth.port_to_u16(), Ok(Some(4673)));
///
/// let uri = Uri::parse("//localhost:/")?;
/// let uri = Uri::parse("//localhost/")?;
/// let auth = uri.authority().unwrap();
/// assert_eq!(auth.port_to_u16(), Ok(None));
///
/// let uri = Uri::parse("//localhost/")?;
/// let uri = Uri::parse("//localhost:/")?;
/// let auth = uri.authority().unwrap();
/// assert_eq!(auth.port_to_u16(), Ok(None));
/// assert!(auth.port_to_u16().is_err());
///
/// let uri = Uri::parse("//localhost:123456/")?;
/// let auth = uri.authority().unwrap();
/// assert!(auth.port_to_u16().is_err());
/// # Ok::<_, fluent_uri::error::ParseError>(())
/// ```
pub fn port_to_u16(&'i self) -> Result<Option<u16>, ParseIntError> {
self.port()
.filter(|port| !port.is_empty())
.map(|port| port.as_str().parse())
.transpose()
#[cfg(fluent_uri_unstable)]
pub fn port_to_u16(&'i self) -> Result<Option<u16>, core::num::ParseIntError> {
self.port().map(|s| s.as_str().parse()).transpose()
}

/// Converts the authority component to an iterator of resolved [`SocketAddr`]s.
///
/// The default port is used if the port component is not present or is empty.
/// The default port is used if the port component is not present.
///
/// A registered name is **not** normalized prior to resolution and is resolved
/// with [`ToSocketAddrs`] as is.
/// with [`ToSocketAddrs`] as is. The port must **not** be empty.
/// Use [`Uri::normalize`] if necessary.
///
/// # Errors
///
/// Returns `Err` if the port cannot be parsed into `u16`
/// Returns `Err` if the port cannot be parsed into `u16`,
/// if the host is an IPvFuture address,
/// or if the resolution of a registered name fails.
#[cfg(all(feature = "net", feature = "std"))]
pub fn to_socket_addrs(
Expand All @@ -375,7 +380,9 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Authority<T> {
use std::vec;

let port = self
.port_to_u16()
.port()
.map(|s| s.as_str().parse())
.transpose()
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid port value"))?
.unwrap_or(default_port);

Expand Down
2 changes: 2 additions & 0 deletions src/encoding/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ impl<'a> Decode<'a> {

/// Converts the decoded bytes to a string.
///
/// # Errors
///
/// Returns `Err` if the bytes are not valid UTF-8.
#[inline]
pub fn into_string(self) -> Result<Cow<'a, str>, FromUtf8Error> {
Expand Down
7 changes: 3 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
clippy::if_not_else,
clippy::ignored_unit_patterns,
clippy::map_unwrap_or,
clippy::missing_errors_doc,
clippy::must_use_candidate,
// clippy::redundant_closure_for_method_calls,
clippy::semicolon_if_nothing_returned,
clippy::single_match_else,
// clippy::missing_errors_doc,
// clippy::redundant_closure_for_method_calls,
)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
Expand Down Expand Up @@ -165,7 +165,6 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
/// assert_eq!(auth.host(), "example.com");
/// assert!(matches!(auth.host_parsed(), Host::RegName(name) if name == "example.com"));
/// assert_eq!(auth.port().unwrap(), "8042");
/// assert_eq!(auth.port_to_u16(), Ok(Some(8042)));
///
/// assert_eq!(uri.path(), "/over/there");
/// assert_eq!(uri.query().unwrap(), "name=ferret");
Expand Down Expand Up @@ -480,7 +479,7 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Uri<T> {
/// normalizing the base URI and then resolving `"."` against it yields `"foo:/"`.
///
/// No normalization except the removal of dot segments will be performed.
/// Use [`normalize`] if need be.
/// Use [`normalize`] if necessary.
///
/// [absolute URI]: Self::is_absolute_uri
/// [rootless]: EStr::<Path>::is_rootless
Expand Down
19 changes: 11 additions & 8 deletions tests/to_socket_addrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ fn test_to_socket_addrs() {
.unwrap()
.eq([SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80).into()]));

let u = Uri::parse("//127.0.0.1:").unwrap();
assert!(u
.authority()
.unwrap()
.to_socket_addrs(80)
.unwrap()
.eq([SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80).into()]));

let u = Uri::parse("//[::1]").unwrap();
assert!(u
.authority()
Expand All @@ -38,6 +30,17 @@ fn test_to_socket_addrs() {
.unwrap()
.eq([SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 80, 0, 0).into()]));

let u = Uri::parse("//127.0.0.1:").unwrap();
assert_eq!(
u.authority()
.unwrap()
.to_socket_addrs(80)
.err()
.unwrap()
.to_string(),
"invalid port value"
);

let u = Uri::parse("//127.0.0.1:65537").unwrap();
assert_eq!(
u.authority()
Expand Down

0 comments on commit b759c34

Please sign in to comment.