use crate::std::{
self as std,
borrow::Cow,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use rama_core::bytes::BytesMut;
mod error;
#[doc(inline)]
pub use error::{Component, ParseError, UriError};
mod component_input;
#[doc(inline)]
pub use component_input::IntoUriComponent;
mod encode;
mod input;
#[doc(inline)]
pub use input::IntoUriInput;
mod path;
#[doc(inline)]
pub use path::{PathMatchOptions, PathRef, PathSegment, PathSegments};
mod path_mut;
#[doc(inline)]
pub use path_mut::PathMut;
mod path_matcher;
#[doc(inline)]
pub use path_matcher::{
PathCaptures, PathPattern, PathPatternSegmentKind, PathPatternSegmentSpecificity,
PathRouteCaptures, PathRouteMatch, PathRouter, PathRouterError,
};
mod query;
#[cfg(feature = "std")]
#[doc(inline)]
pub use query::QueryDeserializeError;
#[doc(inline)]
pub use query::{Query, QueryPair, QueryPairRef, QueryPairs, QueryRef, QueryValues};
mod query_mut;
#[doc(inline)]
pub use query_mut::{Drain as QueryDrain, QueryMut};
mod canonicalize;
mod resolve;
#[doc(inline)]
pub use resolve::ResolveError;
mod wire;
#[doc(inline)]
pub use wire::WireError;
mod fragment;
#[doc(inline)]
pub use fragment::{Fragment, FragmentRef};
mod lazy;
mod owned;
pub(crate) mod parser;
use crate::address::{AuthorityRef, HostRef, UserInfoRef};
use lazy::{LazyAuthority, LazyUriRef};
use owned::OwnedUriRef;
use parser::ParserMode;
pub mod util {
pub use ::percent_encoding;
}
#[derive(Clone)]
pub struct Uri {
pub(crate) inner: UriInner,
}
#[derive(Debug, Clone)]
pub(crate) enum UriInner {
Asterisk,
Lazy(Arc<LazyUriRef>),
Owned(Arc<OwnedUriRef>),
}
impl Uri {
pub const MAX_LEN: usize = parser::MAX_URI_LEN;
pub fn parse<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
parser::parse(input::into_uri_input(input), ParserMode::Graceful)
}
pub fn parse_strict<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
parser::parse(input::into_uri_input(input), ParserMode::Strict)
}
pub fn parse_reference<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
parser::parse_uri_reference(input::into_uri_input(input), ParserMode::Graceful)
}
pub fn parse_reference_strict<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
parser::parse_uri_reference(input::into_uri_input(input), ParserMode::Strict)
}
#[must_use]
#[expect(
clippy::panic,
reason = "static-str invariant: panic at runtime for what's intended to be a compile-time-known URI string"
)]
pub fn from_static(s: &'static str) -> Self {
match Self::parse(rama_core::bytes::Bytes::from_static(s.as_bytes())) {
Ok(uri) => uri,
Err(e) => panic!("Uri::from_static: invalid URI {s:?}: {e}"),
}
}
pub fn parse_authority_form<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
parser::parse_authority_form(input::into_uri_input(input), ParserMode::Graceful)
}
pub fn parse_authority_form_strict<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
parser::parse_authority_form(input::into_uri_input(input), ParserMode::Strict)
}
#[must_use]
pub fn from_authority_form(authority: crate::address::Authority) -> Self {
use core::fmt::Write as _;
let crate::address::Authority { user_info, address } = authority;
let mut s = String::new();
let userinfo_range = match &user_info {
Some(ui) => {
_ = write!(s, "{ui}");
let end = u16::try_from(s.len()).unwrap_or(u16::MAX);
s.push('@');
Some((0, end))
}
None => None,
};
_ = write!(s, "{address}");
let crate::address::HostWithOptPort { host, port } = address;
let bytes = rama_core::bytes::Bytes::from(s);
let len = u16::try_from(bytes.len()).unwrap_or(u16::MAX);
Self::from_lazy(LazyUriRef {
scheme: None,
authority: Some(LazyAuthority {
userinfo_range,
host,
port,
}),
path: (len, len),
query: None,
fragment: None,
bytes,
})
}
#[must_use]
pub fn as_authority_form(&self) -> Option<Self> {
Some(Self::from_authority_form(self.authority()?.into_owned()))
}
#[must_use]
pub fn from_authority(
scheme: impl Into<crate::Protocol>,
authority: impl Into<crate::address::Authority>,
) -> Self {
Self::from_scheme_and_authority(scheme.into(), authority.into())
}
pub fn try_from_authority<A>(
scheme: impl Into<crate::Protocol>,
authority: A,
) -> Result<Self, UriError>
where
A: TryInto<crate::address::Authority>,
A::Error: Into<rama_core::error::BoxError>,
{
let authority = authority
.try_into()
.map_err(|e| UriError::ComponentConversion {
component: Component::Authority,
cause: e.into(),
})?;
Ok(Self::from_scheme_and_authority(scheme.into(), authority))
}
fn from_scheme_and_authority(
scheme: crate::Protocol,
authority: crate::address::Authority,
) -> Self {
use core::fmt::Write as _;
let crate::address::Authority { user_info, address } = authority;
let mut s = String::new();
_ = write!(s, "{scheme}://");
let userinfo_range = match &user_info {
Some(ui) => {
let start = u16::try_from(s.len()).unwrap_or(u16::MAX);
_ = write!(s, "{ui}");
let end = u16::try_from(s.len()).unwrap_or(u16::MAX);
s.push('@');
Some((start, end))
}
None => None,
};
_ = write!(s, "{address}");
let crate::address::HostWithOptPort { host, port } = address;
let bytes = rama_core::bytes::Bytes::from(s);
let len = u16::try_from(bytes.len()).unwrap_or(u16::MAX);
Self::from_lazy(LazyUriRef {
scheme: Some(scheme),
authority: Some(LazyAuthority {
userinfo_range,
host,
port,
}),
path: (len, len),
query: None,
fragment: None,
bytes,
})
}
pub fn as_str(&self) -> Cow<'_, str> {
match &self.inner {
UriInner::Asterisk => Cow::Borrowed("*"),
UriInner::Lazy(lazy_uri_ref) => {
let s = unsafe { core::str::from_utf8_unchecked(&lazy_uri_ref.bytes) };
Cow::Borrowed(s)
}
UriInner::Owned(_) => Cow::Owned(self.to_string()),
}
}
pub fn encode_to(&self, buf: &mut Vec<u8>) {
match &self.inner {
UriInner::Asterisk => buf.push(b'*'),
UriInner::Lazy(lazy) => buf.extend_from_slice(&lazy.bytes),
UriInner::Owned(_) => buf.extend_from_slice(self.to_string().as_bytes()),
}
}
#[must_use]
pub fn is_asterisk(&self) -> bool {
matches!(self.inner, UriInner::Asterisk)
}
#[must_use]
pub fn is_absolute(&self) -> bool {
self.scheme().is_some()
}
#[must_use]
pub fn scheme(&self) -> Option<&crate::Protocol> {
match &self.inner {
UriInner::Asterisk => None,
UriInner::Lazy(arc) => arc.scheme.as_ref(),
UriInner::Owned(arc) => arc.scheme.as_ref(),
}
}
#[must_use]
pub fn path(&self) -> Option<PathRef<'_>> {
match &self.inner {
UriInner::Asterisk => None,
UriInner::Lazy(arc) => {
let (s, e) = arc.path;
Some(PathRef::new(&arc.bytes[s as usize..e as usize]))
}
UriInner::Owned(arc) => Some(PathRef::new(&arc.path)),
}
}
#[must_use]
pub fn query(&self) -> Option<QueryRef<'_>> {
match &self.inner {
UriInner::Asterisk => None,
UriInner::Lazy(arc) => {
let (s, e) = arc.query?;
Some(QueryRef::new(&arc.bytes[s as usize..e as usize]))
}
UriInner::Owned(arc) => arc.query.as_ref().map(|q| q.view()),
}
}
#[must_use]
pub fn fragment(&self) -> Option<FragmentRef<'_>> {
match &self.inner {
UriInner::Asterisk => None,
UriInner::Lazy(arc) => {
let (s, e) = arc.fragment?;
Some(FragmentRef::new(&arc.bytes[s as usize..e as usize]))
}
UriInner::Owned(arc) => arc.fragment.as_ref().map(|f| f.view()),
}
}
#[must_use]
pub fn host(&self) -> Option<crate::address::HostRef<'_>> {
match &self.inner {
UriInner::Asterisk => None,
UriInner::Lazy(arc) => arc.authority.as_ref().map(|a| (&a.host).into()),
UriInner::Owned(arc) => arc.authority.as_ref().map(|a| (&a.address.host).into()),
}
}
#[must_use]
pub fn port(&self) -> crate::address::OptPort {
match &self.inner {
UriInner::Asterisk => crate::address::OptPort::Unset,
UriInner::Lazy(arc) => arc
.authority
.as_ref()
.map(|a| a.port)
.unwrap_or(crate::address::OptPort::Unset),
UriInner::Owned(arc) => arc
.authority
.as_ref()
.map(|a| a.address.port)
.unwrap_or(crate::address::OptPort::Unset),
}
}
#[must_use]
#[inline]
pub fn port_u16(&self) -> Option<u16> {
self.port().as_u16()
}
#[must_use]
pub fn userinfo(&self) -> Option<crate::address::UserInfoRef<'_>> {
match &self.inner {
UriInner::Asterisk => None,
UriInner::Lazy(arc) => {
let auth = arc.authority.as_ref()?;
let (s, e) = auth.userinfo_range?;
Some(UserInfoRef::new(&arc.bytes[s as usize..e as usize]))
}
UriInner::Owned(arc) => arc
.authority
.as_ref()
.and_then(|a| a.user_info.as_ref())
.map(|ui| ui.view()),
}
}
#[must_use]
pub fn authority(&self) -> Option<crate::address::AuthorityRef<'_>> {
match &self.inner {
UriInner::Asterisk => None,
UriInner::Lazy(arc) => {
let auth = arc.authority.as_ref()?;
let userinfo = auth
.userinfo_range
.map(|(s, e)| UserInfoRef::new(&arc.bytes[s as usize..e as usize]));
Some(AuthorityRef::new(
userinfo,
HostRef::from(&auth.host),
auth.port,
))
}
UriInner::Owned(arc) => {
let auth = arc.authority.as_ref()?;
let userinfo = auth.user_info.as_ref().map(|ui| ui.view());
Some(AuthorityRef::new(
userinfo,
HostRef::from(&auth.address.host),
auth.address.port,
))
}
}
}
#[must_use]
pub fn path_or_root(&self) -> Cow<'_, str> {
self.path()
.and_then(|p| {
let s = p.as_encoded_str();
(!s.is_empty()).then_some(s)
})
.unwrap_or(Cow::Borrowed("/"))
}
#[must_use]
pub fn path_ref_or_root(&self) -> PathRef<'_> {
self.path()
.filter(|p| !p.is_empty())
.unwrap_or_else(|| PathRef::from_raw_str("/"))
}
#[must_use]
#[inline(always)]
pub fn is_path_empty(&self) -> bool {
self.path().is_none_or(PathRef::is_empty)
}
#[must_use]
pub fn query_or_empty(&self) -> Cow<'_, str> {
self.query()
.map(QueryRef::as_encoded_str)
.unwrap_or(Cow::Borrowed(""))
}
#[must_use]
pub fn scheme_str(&self) -> Option<&str> {
self.scheme().map(crate::Protocol::as_str)
}
#[must_use]
pub fn host_str(&self) -> Option<Cow<'_, str>> {
self.host().map(|h| h.to_str())
}
#[must_use]
pub fn request_target(&self) -> Cow<'_, str> {
if self.is_asterisk() {
return Cow::Borrowed("*");
}
match self.query() {
Some(q) => {
use core::fmt::Write as _;
let mut target = String::new();
_ = write!(&mut target, "{}?{q}", self.path_ref_or_root());
Cow::Owned(target)
}
None => self.path_or_root(),
}
}
#[must_use]
pub fn has_path_prefix(&self, prefix: impl IntoUriComponent) -> bool {
self.path().is_some_and(|p| p.has_prefix(prefix))
}
#[must_use]
pub fn has_path_prefix_with_opts(
&self,
prefix: impl IntoUriComponent,
opts: PathMatchOptions,
) -> bool {
self.path()
.is_some_and(|p| p.has_prefix_with_opts(prefix, opts))
}
#[must_use]
pub fn has_path_suffix(&self, suffix: impl IntoUriComponent) -> bool {
self.path().is_some_and(|p| p.has_suffix(suffix))
}
#[must_use]
pub fn has_path_suffix_with_opts(
&self,
suffix: impl IntoUriComponent,
opts: PathMatchOptions,
) -> bool {
self.path()
.is_some_and(|p| p.has_suffix_with_opts(suffix, opts))
}
#[must_use]
#[inline(always)]
pub fn is_pattern_match(&self, pattern: &PathPattern) -> bool {
self.path_ref_or_root().is_pattern_match(pattern)
}
#[must_use]
#[inline(always)]
pub fn pattern_captures<'a, 'b>(
&'a self,
pattern: &'b PathPattern,
) -> Option<PathCaptures<'b, 'a>> {
self.path_ref_or_root().pattern_captures(pattern)
}
#[must_use]
pub fn path_segment(&self, n: usize) -> Option<PathSegment<'_>> {
self.path().and_then(|p| p.nth_segment(n))
}
#[must_use]
#[inline(always)]
pub fn first_path_segment(&self) -> Option<PathSegment<'_>> {
self.path_segment(0)
}
#[must_use]
pub fn last_path_segment(&self) -> Option<PathSegment<'_>> {
self.path().and_then(PathRef::last_segment)
}
#[must_use]
pub fn path_segment_count(&self) -> usize {
self.path().map_or(0, PathRef::segment_count)
}
#[must_use]
pub fn path_segment_range(&self, start: usize, count: usize) -> Option<PathRef<'_>> {
self.path().and_then(|p| p.segment_range(start, count))
}
#[must_use]
pub fn contains_path_segments(&self, needle: impl IntoUriComponent) -> bool {
self.path().is_some_and(|p| p.contains_segments(needle))
}
#[must_use]
pub fn contains_path_segments_with_opts(
&self,
needle: impl IntoUriComponent,
opts: PathMatchOptions,
) -> bool {
self.path()
.is_some_and(|p| p.contains_segments_with_opts(needle, opts))
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn query_params<'de, T>(&'de self) -> Result<T, QueryDeserializeError>
where
T: serde::de::Deserialize<'de>,
{
let query = self.query().unwrap_or_else(|| QueryRef::new(b""));
query.deserialize()
}
#[must_use]
pub fn query_pairs(&self) -> QueryPairs<'_> {
self.query().unwrap_or_else(|| QueryRef::new(b"")).pairs()
}
#[must_use]
pub fn first_query_value(&self, name: impl IntoUriComponent) -> Option<Cow<'_, str>> {
self.query().and_then(|q| q.first_value(name))
}
#[must_use]
pub fn query_values(&self, name: impl IntoUriComponent) -> QueryValues<'_> {
self.query()
.unwrap_or_else(|| QueryRef::new(b""))
.values(name)
}
#[must_use]
pub fn contains_query_name(&self, name: impl IntoUriComponent) -> bool {
self.query().is_some_and(|q| q.contains_name(name))
}
pub fn ensure_path_or_root(&mut self) -> &mut Self {
if !self.is_asterisk() && self.is_path_empty() {
self.to_mut().path.extend_from_slice(b"/");
}
self
}
pub fn ensure_path_trailing_slash(&mut self) -> &mut Self {
if !self.is_asterisk() {
self.path_mut().ensure_trailing_slash();
}
self
}
pub fn trim_path_trailing_slash(&mut self) -> bool {
!self.is_asterisk() && self.path_mut().trim_trailing_slash()
}
#[must_use]
pub(crate) fn from_asterisk() -> Self {
Self {
inner: UriInner::Asterisk,
}
}
pub(crate) fn from_lazy(lazy: LazyUriRef) -> Self {
Self {
inner: UriInner::Lazy(Arc::new(lazy)),
}
}
fn to_mut(&mut self) -> &mut OwnedUriRef {
if !matches!(self.inner, UriInner::Owned(_)) {
let owned = self.as_owned_components();
self.inner = UriInner::Owned(Arc::new(owned));
}
match &mut self.inner {
UriInner::Owned(arc) => Arc::make_mut(arc),
_ => unsafe { core::hint::unreachable_unchecked() },
}
}
pub(super) fn as_owned_components(&self) -> OwnedUriRef {
match &self.inner {
UriInner::Asterisk => OwnedUriRef::default(),
UriInner::Lazy(arc) => {
let l = arc.as_ref();
let authority = l.authority.as_ref().map(|la| {
let user_info = la.userinfo_range.map(|(s, e)| {
crate::address::UserInfo::from_bytes_unchecked(
l.bytes.slice(s as usize..e as usize),
)
});
crate::address::Authority {
user_info,
address: crate::address::HostWithOptPort {
host: la.host.clone(),
port: la.port,
},
}
});
let slice = |(s, e): (u16, u16)| &l.bytes[s as usize..e as usize];
OwnedUriRef {
scheme: l.scheme.clone(),
authority,
path: BytesMut::from(slice(l.path)),
query: l.query.map(|r| Query {
bytes: BytesMut::from(slice(r)),
}),
fragment: l.fragment.map(|r| Fragment {
bytes: BytesMut::from(slice(r)),
}),
}
}
UriInner::Owned(arc) => arc.as_ref().clone(),
}
}
rama_utils::macros::generate_set_and_with! {
pub fn path(mut self, path: impl IntoUriComponent) -> Self {
self.to_mut().path = encode::encode_path(path);
self
}
}
pub fn unset_path(&mut self) -> &mut Self {
self.to_mut().path = BytesMut::new();
self
}
#[must_use]
pub fn without_path(mut self) -> Self {
self.unset_path();
self
}
pub fn path_mut(&mut self) -> PathMut<'_> {
PathMut::new(self.to_mut())
}
rama_utils::macros::generate_set_and_with! {
pub fn additional_path_segment(mut self, segment: impl IntoUriComponent) -> Self {
self.path_mut().push_segment(segment);
self
}
}
rama_utils::macros::generate_set_and_with! {
pub fn additional_path_segments(mut self, segment: impl IntoUriComponent) -> Self {
self.path_mut().push_segments(segment);
self
}
}
rama_utils::macros::generate_set_and_with! {
pub fn path_without_last_segment(mut self) -> Self {
self.path_mut().pop_segment();
self
}
}
rama_utils::macros::generate_set_and_with! {
pub fn query(mut self, query: Option<Query>) -> Self {
self.to_mut().query = query;
self
}
}
pub fn set_query_from_bytes(&mut self, query: impl IntoUriComponent) -> &mut Self {
self.to_mut().query = Some(Query {
bytes: encode::encode_query(query),
});
self
}
#[must_use]
pub fn with_query_from_bytes(mut self, query: impl IntoUriComponent) -> Self {
self.set_query_from_bytes(query);
self
}
pub fn query_mut(&mut self) -> QueryMut<'_> {
QueryMut::new(self.to_mut())
}
pub fn set_scheme(&mut self, scheme: impl Into<crate::Protocol>) -> &mut Self {
let scheme = scheme.into();
self.to_mut().scheme = Some(scheme);
self
}
#[must_use]
pub fn with_scheme(mut self, scheme: impl Into<crate::Protocol>) -> Self {
self.set_scheme(scheme);
self
}
pub fn unset_scheme(&mut self) -> &mut Self {
self.to_mut().scheme = None;
self
}
#[must_use]
pub fn without_scheme(mut self) -> Self {
self.unset_scheme();
self
}
pub fn maybe_set_scheme(&mut self, scheme: impl Into<Option<crate::Protocol>>) -> &mut Self {
self.to_mut().scheme = scheme.into();
self
}
#[must_use]
pub fn maybe_with_scheme(mut self, scheme: impl Into<Option<crate::Protocol>>) -> Self {
self.maybe_set_scheme(scheme);
self
}
#[must_use]
pub fn canonicalize(self) -> Self {
canonicalize::canonicalize_uri(self)
}
pub fn parse_canonical<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
Self::parse(input).map(Self::canonicalize)
}
pub fn parse_canonical_strict<T: IntoUriInput>(input: T) -> Result<Self, ParseError> {
Self::parse_strict(input).map(Self::canonicalize)
}
pub fn resolve(&self, reference: &Self) -> Result<Self, ResolveError> {
resolve::resolve(self, reference, resolve::ResolveMode::Graceful)
}
pub fn resolve_strict(&self, reference: &Self) -> Result<Self, ResolveError> {
resolve::resolve(self, reference, resolve::ResolveMode::Strict)
}
rama_utils::macros::generate_set_and_with! {
pub fn fragment(mut self, fragment: impl IntoUriComponent) -> Self {
self.to_mut().fragment = Some(Fragment {
bytes: encode::encode_fragment(fragment),
});
self
}
}
pub fn unset_fragment(&mut self) -> &mut Self {
self.to_mut().fragment = None;
self
}
#[must_use]
pub fn without_fragment(mut self) -> Self {
self.unset_fragment();
self
}
rama_utils::macros::generate_set_and_with! {
pub fn authority(mut self, authority: Option<crate::address::Authority>) -> Self {
self.to_mut().authority = authority;
self
}
}
pub fn set_host(&mut self, host: impl Into<crate::address::Host>) -> &mut Self {
let host = host.into();
let owned = self.to_mut();
match &mut owned.authority {
Some(authority) => {
authority.address.host = host;
}
None => {
owned.authority = Some(crate::address::Authority {
user_info: None,
address: crate::address::HostWithOptPort {
host,
port: crate::address::OptPort::Unset,
},
});
}
}
self
}
#[must_use]
pub fn with_host(mut self, host: impl Into<crate::address::Host>) -> Self {
self.set_host(host);
self
}
pub fn try_set_host<H>(&mut self, host: H) -> Result<&mut Self, UriError>
where
H: TryInto<crate::address::Host>,
H::Error: Into<rama_core::error::BoxError>,
{
let host: crate::address::Host =
host.try_into().map_err(|e| UriError::ComponentConversion {
component: Component::Host,
cause: e.into(),
})?;
Ok(self.set_host(host))
}
pub fn try_with_host<H>(mut self, host: H) -> Result<Self, UriError>
where
H: TryInto<crate::address::Host>,
H::Error: Into<rama_core::error::BoxError>,
{
self.try_set_host(host)?;
Ok(self)
}
pub fn set_port(&mut self, port: impl Into<crate::address::OptPort>) -> &mut Self {
let port = port.into();
let owned = self.to_mut();
match &mut owned.authority {
Some(authority) => {
authority.address.port = port;
}
None => {
owned.authority = Some(crate::address::Authority {
user_info: None,
address: crate::address::HostWithOptPort {
host: crate::address::Host::LOCALHOST_IPV4,
port,
},
});
}
}
self
}
#[must_use]
pub fn with_port(mut self, port: impl Into<crate::address::OptPort>) -> Self {
self.set_port(port);
self
}
pub fn unset_port(&mut self) -> &mut Self {
if self.port() != crate::address::OptPort::Unset {
self.set_port(crate::address::OptPort::Unset);
}
self
}
#[must_use]
pub fn without_port(mut self) -> Self {
self.unset_port();
self
}
pub fn set_user_info(
&mut self,
user_info: impl Into<Option<crate::address::UserInfo>>,
) -> &mut Self {
let user_info = user_info.into();
let owned = self.to_mut();
match &mut owned.authority {
Some(authority) => {
authority.user_info = user_info;
}
None => {
owned.authority = Some(crate::address::Authority {
user_info,
address: crate::address::HostWithOptPort {
host: crate::address::Host::LOCALHOST_IPV4,
port: crate::address::OptPort::Unset,
},
});
}
}
self
}
#[must_use]
pub fn with_user_info(
mut self,
user_info: impl Into<Option<crate::address::UserInfo>>,
) -> Self {
self.set_user_info(user_info);
self
}
pub fn unset_user_info(&mut self) -> &mut Self {
self.set_user_info(None)
}
#[must_use]
pub fn without_user_info(mut self) -> Self {
self.unset_user_info();
self
}
pub fn try_set_user_info<U>(&mut self, user_info: U) -> Result<&mut Self, UriError>
where
U: TryInto<crate::address::UserInfo>,
U::Error: Into<rama_core::error::BoxError>,
{
let user_info: crate::address::UserInfo =
user_info
.try_into()
.map_err(|e| UriError::ComponentConversion {
component: Component::UserInfo,
cause: e.into(),
})?;
Ok(self.set_user_info(Some(user_info)))
}
pub fn try_with_user_info<U>(mut self, user_info: U) -> Result<Self, UriError>
where
U: TryInto<crate::address::UserInfo>,
U::Error: Into<rama_core::error::BoxError>,
{
self.try_set_user_info(user_info)?;
Ok(self)
}
}
impl core::str::FromStr for Uri {
type Err = ParseError;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
macro_rules! uri_try_from {
($($t:ty),* $(,)?) => {
$(
impl TryFrom<$t> for Uri {
type Error = ParseError;
#[inline(always)]
fn try_from(input: $t) -> Result<Self, Self::Error> {
Self::parse(input)
}
}
)*
};
}
uri_try_from!(&str, String, &[u8], Vec<u8>, rama_core::bytes::Bytes,);
use rama_utils::macros::serde_str::impl_serde_str;
impl_serde_str!(display Uri);
impl Uri {
fn fmt_uri(
&self,
f: &mut core::fmt::Formatter<'_>,
redact_userinfo: bool,
) -> core::fmt::Result {
match &self.inner {
UriInner::Asterisk => f.write_str("*"),
UriInner::Lazy(arc) => {
let s = unsafe { core::str::from_utf8_unchecked(&arc.bytes) };
if redact_userinfo
&& let Some(auth) = &arc.authority
&& let Some((u_s, u_e)) = auth.userinfo_range
{
f.write_str(&s[..u_s as usize])?;
write_redacted_userinfo(&s[u_s as usize..u_e as usize], f)?;
return f.write_str(&s[u_e as usize..]);
}
f.write_str(s)
}
UriInner::Owned(arc) => {
if let Some(scheme) = &arc.scheme {
write!(f, "{scheme}:")?;
}
if let Some(auth) = &arc.authority {
f.write_str("//")?;
if let Some(ui) = &auth.user_info {
if redact_userinfo {
write_redacted_userinfo(ui.as_str(), f)?;
} else {
write!(f, "{ui}")?;
}
f.write_str("@")?;
}
write!(f, "{}", auth.address)?;
}
write!(f, "{}", PathRef::new(&arc.path))?;
if let Some(query) = &arc.query {
write!(f, "?{query}")?;
}
if let Some(fragment) = &arc.fragment {
write!(f, "#{fragment}")?;
}
Ok(())
}
}
}
}
fn write_redacted_userinfo(s: &str, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match s.bytes().position(|b| b == b':') {
Some(i) => write!(f, "{}:***", &s[..i]),
None => f.write_str(s),
}
}
impl core::fmt::Display for Uri {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.fmt_uri(f, false)
}
}
impl core::fmt::Debug for Uri {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Uri(\"")?;
self.fmt_uri(f, true)?;
f.write_str("\")")
}
}
impl PartialEq for Uri {
fn eq(&self, other: &Self) -> bool {
match (&self.inner, &other.inner) {
(UriInner::Asterisk, UriInner::Asterisk) => return true,
(UriInner::Asterisk, _) | (_, UriInner::Asterisk) => return false,
(UriInner::Lazy(a), UriInner::Lazy(b)) if Arc::ptr_eq(a, b) => return true,
(UriInner::Owned(a), UriInner::Owned(b)) if Arc::ptr_eq(a, b) => return true,
_ => {}
}
self.scheme() == other.scheme()
&& self.authority() == other.authority()
&& self.path() == other.path()
&& self.query() == other.query()
&& self.fragment() == other.fragment()
}
}
impl Eq for Uri {}
impl Default for Uri {
fn default() -> Self {
Self::from_static("/")
}
}
impl Ord for Uri {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
use core::cmp::Ordering;
match (&self.inner, &other.inner) {
(UriInner::Asterisk, UriInner::Asterisk) => return Ordering::Equal,
(UriInner::Asterisk, _) => return Ordering::Less,
(_, UriInner::Asterisk) => return Ordering::Greater,
_ => {}
}
self.scheme()
.cmp(&other.scheme())
.then_with(|| self.authority().cmp(&other.authority()))
.then_with(|| self.path().cmp(&other.path()))
.then_with(|| self.query().cmp(&other.query()))
.then_with(|| self.fragment().cmp(&other.fragment()))
}
}
impl PartialOrd for Uri {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl core::hash::Hash for Uri {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
match &self.inner {
UriInner::Asterisk => {
state.write_u8(0);
return;
}
UriInner::Lazy(_) | UriInner::Owned(_) => state.write_u8(1),
}
self.scheme().hash(state);
self.authority().hash(state);
self.path().hash(state);
match self.query() {
Some(q) => {
state.write_u8(0xff);
q.hash(state);
}
None => state.write_u8(0),
}
match self.fragment() {
Some(f) => {
state.write_u8(0xff);
f.hash(state);
}
None => state.write_u8(0),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct UriRef<'a> {
source: &'a Uri,
scheme: Option<&'a crate::Protocol>,
authority: Option<crate::address::AuthorityRef<'a>>,
path: Option<PathRef<'a>>,
query: Option<QueryRef<'a>>,
fragment: Option<FragmentRef<'a>>,
is_asterisk: bool,
}
impl<'a> UriRef<'a> {
#[must_use]
#[inline]
pub const fn scheme(&self) -> Option<&'a crate::Protocol> {
self.scheme
}
#[must_use]
#[inline]
pub const fn authority(&self) -> Option<crate::address::AuthorityRef<'a>> {
self.authority
}
#[must_use]
#[inline]
pub const fn path(&self) -> Option<PathRef<'a>> {
self.path
}
#[must_use]
#[inline]
pub const fn query(&self) -> Option<QueryRef<'a>> {
self.query
}
#[must_use]
#[inline]
pub const fn fragment(&self) -> Option<FragmentRef<'a>> {
self.fragment
}
#[must_use]
#[inline]
pub fn host(&self) -> Option<crate::address::HostRef<'a>> {
self.authority.map(|a| a.host())
}
#[must_use]
#[inline]
pub fn port(&self) -> crate::address::OptPort {
self.authority
.map(|a| a.port())
.unwrap_or(crate::address::OptPort::Unset)
}
#[must_use]
#[inline]
pub fn port_u16(&self) -> Option<u16> {
self.port().as_u16()
}
#[must_use]
#[inline]
pub fn userinfo(&self) -> Option<crate::address::UserInfoRef<'a>> {
self.authority.and_then(|a| a.userinfo())
}
#[must_use]
#[inline]
pub const fn is_asterisk(&self) -> bool {
self.is_asterisk
}
#[must_use]
#[inline]
pub const fn is_absolute(&self) -> bool {
self.scheme.is_some()
}
#[must_use]
#[inline]
pub fn into_owned(self) -> Uri {
self.source.clone()
}
}
impl core::fmt::Display for UriRef<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self.source, f)
}
}
impl Uri {
#[must_use]
pub fn view(&self) -> UriRef<'_> {
UriRef {
source: self,
scheme: self.scheme(),
authority: self.authority(),
path: self.path(),
query: self.query(),
fragment: self.fragment(),
is_asterisk: self.is_asterisk(),
}
}
}
#[cfg(test)]
mod request_target_fix_tests {
use super::*;
#[test]
fn asterisk_request_target_is_star_not_root() {
let uri = Uri::parse("*").unwrap();
assert!(uri.is_asterisk());
assert_eq!(uri.request_target(), "*");
}
#[test]
fn origin_form_request_target_unchanged() {
assert_eq!(Uri::parse("/a?b=c").unwrap().request_target(), "/a?b=c");
assert_eq!(Uri::parse("/a").unwrap().request_target(), "/a");
}
}
#[cfg(test)]
mod from_authority_form_tests {
use super::*;
use crate::address::Authority;
#[test]
fn matches_parse_authority_form() {
for s in [
"example.com:443",
"example.com",
"user:pass@example.com:8080",
"user@host.example",
"[::1]:443",
"[2001:db8::1]",
"127.0.0.1:80",
] {
let auth = Authority::try_from(s).unwrap();
let canonical = auth.to_string();
let from_ctor = Uri::from_authority_form(auth);
let from_parse = Uri::parse_authority_form(canonical.as_str()).unwrap();
assert_eq!(
from_ctor.to_string(),
from_parse.to_string(),
"wire form: {s}"
);
assert!(!from_ctor.to_string().starts_with("//"), "no `//`: {s}");
assert_eq!(from_ctor.host(), from_parse.host(), "host: {s}");
assert_eq!(from_ctor.port(), from_parse.port(), "port: {s}");
assert_eq!(
from_ctor.userinfo().map(|u| u.to_string()),
from_parse.userinfo().map(|u| u.to_string()),
"userinfo: {s}"
);
assert!(from_ctor.scheme().is_none(), "no scheme: {s}");
}
}
}
#[cfg(test)]
mod from_authority_tests {
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::*;
use crate::Protocol;
use crate::address::{Authority, Domain, Host};
#[test]
fn matches_parse_of_absolute_form() {
for (auth_str, expected) in [
("example.com", "http://example.com"),
("example.com:8080", "http://example.com:8080"),
("user@example.com", "http://user@example.com"),
(
"user:pass@example.com:8080",
"http://user:pass@example.com:8080",
),
("127.0.0.1:80", "http://127.0.0.1:80"),
("[::1]:443", "http://[::1]:443"),
("[2001:db8::1]", "http://[2001:db8::1]"),
] {
let auth = Authority::try_from(auth_str).unwrap();
let from_ctor = Uri::from_authority(Protocol::HTTP, auth);
let from_parse = Uri::parse(expected).unwrap();
assert_eq!(from_ctor.to_string(), expected, "wire form: {auth_str}");
assert_eq!(
from_ctor.to_string(),
from_parse.to_string(),
"parse parity: {auth_str}"
);
assert!(from_ctor.is_absolute(), "absolute: {auth_str}");
assert_eq!(
from_ctor.scheme_str(),
from_parse.scheme_str(),
"scheme: {auth_str}"
);
assert_eq!(
from_ctor.host_str(),
from_parse.host_str(),
"host: {auth_str}"
);
assert_eq!(from_ctor.port(), from_parse.port(), "port: {auth_str}");
assert_eq!(
from_ctor.userinfo().map(|u| u.to_string()),
from_parse.userinfo().map(|u| u.to_string()),
"userinfo: {auth_str}"
);
assert_eq!(
from_ctor.path().map(|p| p.as_encoded_str()),
from_parse.path().map(|p| p.as_encoded_str()),
"path: {auth_str}"
);
assert!(from_ctor.query().is_none(), "query: {auth_str}");
assert!(from_ctor.fragment().is_none(), "fragment: {auth_str}");
}
}
#[test]
fn accepts_into_authority_inputs() {
assert_eq!(
Uri::from_authority(Protocol::HTTP, Domain::from_static("example.com")).to_string(),
"http://example.com"
);
assert_eq!(
Uri::from_authority(Protocol::HTTPS, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
.to_string(),
"https://127.0.0.1"
);
assert_eq!(
Uri::from_authority(Protocol::HTTP, Ipv4Addr::new(10, 0, 0, 1)).to_string(),
"http://10.0.0.1"
);
assert_eq!(
Uri::from_authority(Protocol::HTTP, Ipv6Addr::LOCALHOST).to_string(),
"http://[::1]"
);
assert_eq!(
Uri::from_authority(Protocol::HTTP, Host::Name(Domain::from_static("h.example")))
.to_string(),
"http://h.example"
);
assert_eq!(
Uri::from_authority(
Protocol::HTTP,
(Domain::from_static("example.com"), 8080u16)
)
.to_string(),
"http://example.com:8080"
);
let addr: core::net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
assert_eq!(
Uri::from_authority(Protocol::HTTPS, addr).to_string(),
"https://127.0.0.1:8080"
);
}
#[test]
fn supports_custom_and_non_http_schemes() {
let redis = Protocol::try_from("redis").unwrap();
assert_eq!(
Uri::from_authority(redis, Authority::try_from("localhost:6379").unwrap()).to_string(),
"redis://localhost:6379"
);
assert_eq!(
Uri::from_authority(Protocol::WSS, Domain::from_static("example.com")).to_string(),
"wss://example.com"
);
}
#[test]
fn try_from_authority_parses_strings() {
assert_eq!(
Uri::try_from_authority(Protocol::HTTP, "example.com")
.unwrap()
.to_string(),
"http://example.com"
);
assert_eq!(
Uri::try_from_authority(Protocol::HTTPS, "user@example.com:8080")
.unwrap()
.to_string(),
"https://user@example.com:8080"
);
assert_eq!(
Uri::try_from_authority(Protocol::HTTP, String::from("[::1]:443"))
.unwrap()
.to_string(),
"http://[::1]:443"
);
let err = Uri::try_from_authority(Protocol::HTTP, ":80").unwrap_err();
assert!(
matches!(
err,
UriError::ComponentConversion {
component: Component::Authority,
..
}
),
"expected ComponentConversion(Authority), got {err:?}"
);
}
#[test]
fn is_mutable_after_construction() {
let uri = Uri::from_authority(Protocol::HTTP, Domain::from_static("example.com"))
.with_path("/v1/ping")
.with_query_from_bytes("a=1");
assert_eq!(uri.to_string(), "http://example.com/v1/ping?a=1");
}
}
#[cfg(test)]
mod encode_to_tests {
use super::*;
#[test]
fn encode_to_matches_display() {
let lazy = [
Uri::parse("/path?q=1").unwrap(),
Uri::parse("https://user@example.com:8443/a/b?c#frag").unwrap(),
Uri::from_authority_form(
crate::address::Authority::try_from("example.com:443").unwrap(),
),
Uri::parse("*").unwrap(),
Uri::from_static("http://example.com/"),
];
for uri in lazy {
let mut buf = Vec::new();
uri.encode_to(&mut buf);
assert_eq!(buf, uri.to_string().as_bytes(), "uri: {uri}");
}
let owned = Uri::parse("http://example.com/p?x#f")
.unwrap()
.with_port(8080u16);
let mut buf = Vec::new();
owned.encode_to(&mut buf);
assert_eq!(buf, owned.to_string().as_bytes());
}
}