diff --git a/fuzz/fuzz_targets/against_iref.rs b/fuzz/fuzz_targets/against_iref.rs index 57d187b..31bfadf 100644 --- a/fuzz/fuzz_targets/against_iref.rs +++ b/fuzz/fuzz_targets/against_iref.rs @@ -15,8 +15,11 @@ fuzz_target!(|data: &str| { u2.scheme().map(|s| s.as_str()) ); assert_eq!( - u1.authority() - .map(|a| (a.userinfo().map(|s| s.as_str()), a.host(), a.port())), + u1.authority().map(|a| ( + a.userinfo().map(|s| s.as_str()), + a.host(), + a.port().map(|s| s.as_str()) + )), u2.authority().map(|a| ( a.user_info().map(|s| s.as_str()), a.host().as_str(), diff --git a/fuzz/fuzz_targets/against_iri_string.rs b/fuzz/fuzz_targets/against_iri_string.rs index f1d2ac3..ee0648f 100644 --- a/fuzz/fuzz_targets/against_iri_string.rs +++ b/fuzz/fuzz_targets/against_iri_string.rs @@ -12,8 +12,11 @@ fuzz_target!(|data: &str| { assert_eq!(u1.scheme().map(|s| s.as_str()), u2.scheme_str()); assert_eq!( - u1.authority() - .map(|a| (a.userinfo().map(|s| s.as_str()), a.host(), a.port())), + u1.authority().map(|a| ( + a.userinfo().map(|s| s.as_str()), + a.host(), + a.port().map(|s| s.as_str()) + )), u2.authority_components() .map(|a| (a.userinfo(), a.host(), a.port())) ); diff --git a/fuzz/fuzz_targets/against_uriparser.rs b/fuzz/fuzz_targets/against_uriparser.rs index be0aadd..98ca4fc 100644 --- a/fuzz/fuzz_targets/against_uriparser.rs +++ b/fuzz/fuzz_targets/against_uriparser.rs @@ -61,7 +61,7 @@ unsafe fn check(data: &str, cstr: &CStr) { } } - assert_text_eq(a.port(), uri1.portText); + assert_text_eq(a.port().map(|s| s.as_str()), uri1.portText); } else { assert_text_eq(None, uri1.userInfo); assert_text_eq(None, uri1.hostText); diff --git a/fuzz/fuzz_targets/build_parse.rs b/fuzz/fuzz_targets/build_parse.rs index f1060ec..e0d4bb5 100644 --- a/fuzz/fuzz_targets/build_parse.rs +++ b/fuzz/fuzz_targets/build_parse.rs @@ -65,25 +65,11 @@ impl<'a> HostWrapper<'a> { } } -#[derive(Debug, Clone, Copy)] -struct Port<'a>(&'a str); - -impl<'a> Arbitrary<'a> for Port<'a> { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let s: &str = u.arbitrary()?; - if s.bytes().all(|x| x.is_ascii_digit()) { - Ok(Port(s)) - } else { - Err(Error::IncorrectFormat) - } - } -} - #[derive(Arbitrary, Clone, Copy, Debug)] struct Authority<'a> { userinfo: Option>, host: HostWrapper<'a>, - port: Option>, + port: Option>, } #[derive(Arbitrary, Clone, Copy, Debug)] diff --git a/fuzz/fuzz_targets/parse.rs b/fuzz/fuzz_targets/parse.rs index e341f4f..b4fa993 100644 --- a/fuzz/fuzz_targets/parse.rs +++ b/fuzz/fuzz_targets/parse.rs @@ -20,7 +20,7 @@ fuzz_target!(|data: &str| { buf.push_str(a.host()); if let Some(p) = a.port() { buf.push(':'); - buf.push_str(p); + buf.push_str(p.as_str()); } assert_eq!(&buf[start..], a.as_str()); } diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 96f4ed9..b72d38b 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -5,7 +5,7 @@ mod state; use crate::{ component::{Host, Scheme}, encoding::{ - encoder::{Fragment, Path, Query, Userinfo}, + encoder::{Fragment, Path, Port, Query, Userinfo}, EStr, }, error::{BuildError, BuildErrorKind}, @@ -336,36 +336,30 @@ impl> Builder { } } -pub trait PortLike { +pub trait AsPort { fn push_to(&self, buf: &mut String); } -impl PortLike for u16 { +impl AsPort for u16 { fn push_to(&self, buf: &mut String) { write!(buf, ":{self}").unwrap(); } } -impl PortLike for &str { +impl AsPort for &EStr { fn push_to(&self, buf: &mut String) { - assert!(self.bytes().all(|x| x.is_ascii_digit()), "invalid port"); buf.push(':'); - buf.push_str(self); + buf.push_str(self.as_str()); } } impl> Builder { /// Sets the [port] subcomponent of authority. /// - /// This method takes either a `u16` or `&str` as argument. - /// - /// # Panics - /// - /// Panics if an input string is not a valid port according to - /// [Section 3.2.3 of RFC 3986][port]. + /// This method takes either a `u16` or `&EStr` as argument. /// /// [port]: https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.3 - pub fn port(mut self, port: T) -> Builder { + pub fn port(mut self, port: P) -> Builder { port.push_to(&mut self.inner.buf); self.cast() } diff --git a/src/component.rs b/src/component.rs index b83ec07..4e8c525 100644 --- a/src/component.rs +++ b/src/component.rs @@ -2,7 +2,7 @@ use crate::{ encoding::{ - encoder::{RegName, Userinfo}, + encoder::{Port, RegName, Userinfo}, table, EStr, EString, }, internal::{AuthMeta, HostMeta}, @@ -292,15 +292,15 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Authority { /// # Examples /// /// ``` - /// use fluent_uri::Uri; + /// use fluent_uri::{encoding::EStr, Uri}; /// /// let uri = Uri::parse("//localhost:4673/")?; /// let auth = uri.authority().unwrap(); - /// assert_eq!(auth.port(), Some("4673")); + /// assert_eq!(auth.port(), Some(EStr::new_or_panic("4673"))); /// /// let uri = Uri::parse("//localhost:/")?; /// let auth = uri.authority().unwrap(); - /// assert_eq!(auth.port(), Some("")); + /// assert_eq!(auth.port(), Some(EStr::EMPTY)); /// /// let uri = Uri::parse("//localhost/")?; /// let auth = uri.authority().unwrap(); @@ -308,13 +308,13 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Authority { /// /// let uri = Uri::parse("//localhost:123456/")?; /// let auth = uri.authority().unwrap(); - /// assert_eq!(auth.port(), Some("123456")); + /// assert_eq!(auth.port(), Some(EStr::new_or_panic("123456"))); /// # Ok::<_, fluent_uri::error::ParseError>(()) /// ``` #[must_use] - pub fn port(&'i self) -> Option<&'o str> { + pub fn port(&'i self) -> Option<&'o EStr> { let (host_end, end) = (self.host_bounds().1, self.end()); - (host_end != end).then(|| self.uri.slice(host_end + 1, end)) + (host_end != end).then(|| self.uri.eslice(host_end + 1, end)) } /// Converts the [port] subcomponent to `u16`, if present. @@ -350,7 +350,7 @@ impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> Authority { pub fn port_to_u16(&'i self) -> Result, ParseIntError> { self.port() .filter(|port| !port.is_empty()) - .map(str::parse) + .map(|port| port.as_str().parse()) .transpose() } diff --git a/src/encoding/encoder.rs b/src/encoding/encoder.rs index dddf975..5f47e67 100644 --- a/src/encoding/encoder.rs +++ b/src/encoding/encoder.rs @@ -18,6 +18,13 @@ impl Encoder for RegName { const TABLE: &'static Table = REG_NAME; } +/// An encoder for port. +pub struct Port(()); + +impl Encoder for Port { + const TABLE: &'static Table = DIGIT; +} + /// An encoder for path. /// /// [`EStr`] has [extension methods] for the path component. diff --git a/src/lib.rs b/src/lib.rs index ed5d088..c68b3a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,7 +158,7 @@ use internal::{Meta, ToUri, Value}; /// assert_eq!(auth.userinfo().unwrap(), "user"); /// assert_eq!(auth.host(), "example.com"); /// assert!(matches!(auth.host_parsed(), Host::RegName(name) if name == "example.com")); -/// assert_eq!(auth.port(), Some("8042")); +/// assert_eq!(auth.port().unwrap(), "8042"); /// assert_eq!(auth.port_to_u16(), Ok(Some(8042))); /// /// assert_eq!(uri.path(), "/over/there"); diff --git a/src/normalizer.rs b/src/normalizer.rs index 53aaa24..f3fc632 100644 --- a/src/normalizer.rs +++ b/src/normalizer.rs @@ -76,7 +76,7 @@ pub(crate) fn normalize(u: Uri<&str>) -> Uri { if let Some(port) = auth.port() { if !port.is_empty() { buf.push(':'); - buf.push_str(port); + buf.push_str(port.as_str()); } } } diff --git a/tests/parse.rs b/tests/parse.rs index ef638f7..64bda4a 100644 --- a/tests/parse.rs +++ b/tests/parse.rs @@ -87,7 +87,7 @@ fn parse_absolute() { assert_eq!(a.host(), "192.0.2.16"); #[cfg(feature = "net")] assert!(matches!(a.host_parsed(), Host::Ipv4(addr) if addr == Ipv4Addr::new(192, 0, 2, 16))); - assert_eq!(a.port(), Some("80")); + assert_eq!(a.port(), Some(EStr::new_or_panic("80"))); assert_eq!(u.path(), "/"); assert_eq!(u.query(), None); assert_eq!(u.fragment(), None); @@ -106,7 +106,7 @@ fn parse_absolute() { assert_eq!(a.userinfo(), None); assert_eq!(a.host(), "example.com"); assert!(matches!(a.host_parsed(), Host::RegName(name) if name == "example.com")); - assert_eq!(a.port(), Some("8042")); + assert_eq!(a.port(), Some(EStr::new_or_panic("8042"))); assert_eq!(u.path(), "/over/there"); assert_eq!(u.query(), Some(EStr::new_or_panic("name=ferret"))); assert_eq!(u.fragment(), Some(EStr::new_or_panic("nose"))); @@ -147,7 +147,7 @@ fn parse_absolute() { assert_eq!(a.host(), "127.0.0.1"); #[cfg(feature = "net")] assert!(matches!(a.host_parsed(), Host::Ipv4(addr) if addr == Ipv4Addr::new(127, 0, 0, 1))); - assert_eq!(a.port(), Some("")); + assert_eq!(a.port(), Some(EStr::EMPTY)); assert_eq!(u.path(), "/"); assert_eq!(u.query(), None); assert_eq!(u.fragment(), None); @@ -160,7 +160,7 @@ fn parse_absolute() { assert_eq!(a.host(), "127.0.0.1"); #[cfg(feature = "net")] assert!(matches!(a.host_parsed(), Host::Ipv4(addr) if addr == Ipv4Addr::new(127, 0, 0, 1))); - assert_eq!(a.port(), Some("8080")); + assert_eq!(a.port(), Some(EStr::new_or_panic("8080"))); assert_eq!(u.path(), "/"); assert_eq!(u.query(), None); assert_eq!(u.fragment(), None); @@ -173,7 +173,7 @@ fn parse_absolute() { assert_eq!(a.host(), "127.0.0.1"); #[cfg(feature = "net")] assert!(matches!(a.host_parsed(), Host::Ipv4(addr) if addr == Ipv4Addr::new(127, 0, 0, 1))); - assert_eq!(a.port(), Some("80808")); + assert_eq!(a.port(), Some(EStr::new_or_panic("80808"))); assert_eq!(u.path(), "/"); assert_eq!(u.query(), None); assert_eq!(u.fragment(), None);