diff --git a/crates/templated_uri/src/base_uri.rs b/crates/templated_uri/src/base_uri.rs index b074753e..9a3d7123 100644 --- a/crates/templated_uri/src/base_uri.rs +++ b/crates/templated_uri/src/base_uri.rs @@ -360,6 +360,36 @@ impl BaseUri { self.origin.effective_port() } + /// Returns the effective port of this [`BaseUri`], or an error when it + /// cannot be determined. + /// + /// Behaves like [`BaseUri::effective_port`], but returns a [`UriError`] + /// instead of `None` when no explicit port is present and the scheme has + /// no well-known default known to this crate. + /// + /// # Errors + /// + /// Returns a [`UriError`] when the authority does not specify a port and + /// the scheme is not one of the schemes with a default port known to this + /// crate (`http`, `https`). + /// + /// # Examples + /// + /// ``` + /// # use templated_uri::BaseUri; + /// let base_uri = BaseUri::from_static("https://example.com:8443"); + /// assert_eq!(base_uri.try_effective_port().unwrap(), 8443); + /// + /// let base_uri = BaseUri::from_static("https://example.com"); + /// assert_eq!(base_uri.try_effective_port().unwrap(), 443); + /// + /// let base_uri = BaseUri::from_static("http://example.com"); + /// assert_eq!(base_uri.try_effective_port().unwrap(), 80); + /// ``` + pub fn try_effective_port(&self) -> Result { + self.origin.try_effective_port() + } + /// Returns a new [`BaseUri`] with the given port. /// /// # Examples @@ -877,6 +907,34 @@ mod tests { let base_uri = BaseUri::from_static("http://example.com"); assert_eq!(base_uri.effective_port(), Some(80)); } + + #[test] + fn try_effective_port_explicit() { + let base_uri = BaseUri::from_static("https://example.com:8443"); + assert_eq!(base_uri.try_effective_port().unwrap(), 8443); + } + + #[test] + fn try_effective_port_infers_https_default() { + let base_uri = BaseUri::from_static("https://example.com"); + assert_eq!(base_uri.try_effective_port().unwrap(), 443); + } + + #[test] + fn try_effective_port_infers_http_default() { + let base_uri = BaseUri::from_static("http://example.com"); + assert_eq!(base_uri.try_effective_port().unwrap(), 80); + } + + #[test] + fn try_effective_port_errors_for_non_http_scheme_without_explicit_port() { + let base_uri = BaseUri::from_static("ftp://example.com"); + let err = base_uri + .try_effective_port() + .expect_err("expected non-http/https URI without explicit port to error"); + + assert!(err.to_string().contains("ftp")); + } } mod is_https { diff --git a/crates/templated_uri/src/origin.rs b/crates/templated_uri/src/origin.rs index e32bb01a..2210f2ef 100644 --- a/crates/templated_uri/src/origin.rs +++ b/crates/templated_uri/src/origin.rs @@ -147,6 +147,41 @@ impl Origin { } } + /// Returns the effective port of this origin, or an error when it cannot + /// be determined. + /// + /// Behaves like [`Origin::effective_port`], but returns a [`UriError`] + /// instead of `None` when no explicit port is present and the scheme has + /// no well-known default known to this crate. + /// + /// # Errors + /// + /// Returns a [`UriError`] when the authority does not specify a port and + /// the scheme is not one of the schemes with a default port known to this + /// crate (`http`, `https`). + /// + /// # Examples + /// + /// ``` + /// # use templated_uri::Origin; + /// let origin = Origin::from_static("https://example.com:8443"); + /// assert_eq!(origin.try_effective_port().unwrap(), 8443); + /// + /// let origin = Origin::from_static("https://example.com"); + /// assert_eq!(origin.try_effective_port().unwrap(), 443); + /// + /// let origin = Origin::from_static("http://example.com"); + /// assert_eq!(origin.try_effective_port().unwrap(), 80); + /// ``` + pub fn try_effective_port(&self) -> Result { + self.effective_port().ok_or_else(|| { + UriError::invalid_uri(format!( + "no explicit port and no known default port for scheme '{}'", + self.scheme.as_str() + )) + }) + } + /// Set port for this `Origin` instance. /// /// # Examples @@ -297,6 +332,31 @@ mod tests { assert_eq!(origin.effective_port(), Some(21)); } + #[test] + fn test_try_effective_port() { + // Explicit ports are returned verbatim. + let origin = Origin::from_parts(Scheme::HTTP, Authority::from_static("example.com:8080")); + assert_eq!(origin.try_effective_port().unwrap(), 8080); + + // Well-known defaults are inferred from the scheme when no port is set. + let origin = Origin::from_parts(Scheme::HTTP, Authority::from_static("example.com")); + assert_eq!(origin.try_effective_port().unwrap(), HTTP_DEFAULT_PORT); + + let origin = Origin::from_parts(Scheme::HTTPS, Authority::from_static("example.com")); + assert_eq!(origin.try_effective_port().unwrap(), HTTPS_DEFAULT_PORT); + + // Unknown schemes without an explicit port return an error. + let origin = Origin::from_parts(Scheme::from_str("ftp").unwrap(), Authority::from_static("example.com")); + let err = origin + .try_effective_port() + .expect_err("expected error for unknown scheme without explicit port"); + assert!(err.to_string().contains("ftp"), "unexpected error message: {err}"); + + // Unknown schemes still surface an explicit port. + let origin = Origin::from_parts(Scheme::from_str("ftp").unwrap(), Authority::from_static("example.com:21")); + assert_eq!(origin.try_effective_port().unwrap(), 21); + } + #[test] fn test_origin_display() { // Default ports omitted