mod authority;
mod errors;
mod path;
mod query;
mod scheme;
pub use authority::*;
pub use errors::*;
pub use path::*;
pub use query::*;
pub use scheme::*;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Uri {
pub scheme: Scheme,
pub authority: Option<Authority>,
pub path_and_query: PathAndQuery,
pub fragment: Option<String>,
}
impl Uri {
pub fn parse(uri: &str) -> Result<Self, InvalidUri> {
if uri.starts_with('/') {
let path_and_query = PathAndQuery::parse(uri)?;
return Ok(Uri {
scheme: Scheme::HTTP,
authority: None,
path_and_query,
fragment: None,
});
}
let (scheme, rest) = Scheme::parse_from_uri(uri)?;
let (authority, rest) = if let Some(after_slashes) = rest.strip_prefix("//") {
Authority::parse_with_remainder(after_slashes)?
} else {
(None, rest)
};
let (rest, fragment) = if let Some(hash_pos) = rest.find('#') {
let frag = &rest[hash_pos + 1..];
let fragment = if frag.is_empty() {
None
} else {
Some(frag.to_string())
};
(&rest[..hash_pos], fragment)
} else {
(rest, None)
};
let path_and_query = PathAndQuery::parse(rest)?;
Ok(Uri {
scheme,
authority,
path_and_query,
fragment,
})
}
#[must_use]
pub fn scheme(&self) -> &Scheme {
&self.scheme
}
#[must_use]
pub fn host_str(&self) -> Option<String> {
self.authority.as_ref().map(|a| match a.host() {
Host::RegName(name) => name.clone(),
Host::Ipv4(addr) => addr.to_string(),
Host::Ipv6(addr) => addr.to_string(), })
}
#[must_use]
pub fn host_enum(&self) -> Option<&Host> {
self.authority.as_ref().map(authority::Authority::host)
}
#[must_use]
pub fn port(&self) -> Option<u16> {
self.authority.as_ref().and_then(authority::Authority::port)
}
#[must_use]
pub fn port_or_default(&self) -> u16 {
self.port().unwrap_or_else(|| self.scheme.default_port())
}
#[must_use]
pub fn path(&self) -> &str {
self.path_and_query.path()
}
#[must_use]
pub fn query(&self) -> Option<String> {
self.path_and_query.query()
}
#[must_use]
pub fn query_params(&self) -> &Query {
self.path_and_query.query_params()
}
#[must_use]
pub fn query_params_mut(&mut self) -> &mut Query {
self.path_and_query.query_params_mut()
}
#[must_use]
pub fn with_query_params(&self, query: Query) -> Self {
let path_and_query = PathAndQuery {
path: self.path_and_query.path().to_string(),
query,
};
Uri {
scheme: self.scheme.clone(),
authority: self.authority.clone(),
path_and_query,
fragment: self.fragment.clone(),
}
}
pub fn append_query(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.path_and_query.query.append(key, value);
}
pub fn set_query(&mut self, key: impl Into<String>, value: impl Into<String>) {
let k = key.into();
self.path_and_query.query.retain(|existing, _| existing != k);
self.path_and_query.query.append(k, value);
}
pub fn remove_query(&mut self, key: &str) {
self.path_and_query.query.retain(|k, _| k != key);
}
#[must_use]
pub fn fragment(&self) -> Option<&str> {
self.fragment.as_deref()
}
#[must_use]
pub fn authority(&self) -> Option<&Authority> {
self.authority.as_ref()
}
#[must_use]
pub fn path_and_query(&self) -> &PathAndQuery {
&self.path_and_query
}
#[must_use]
pub fn with_query(&self, query: impl Into<String>) -> Self {
let query_str = query.into();
let parsed = Query::parse(&query_str).unwrap_or_default();
let path_and_query = PathAndQuery {
path: self.path_and_query.path().to_string(),
query: parsed,
};
Uri {
scheme: self.scheme.clone(),
authority: self.authority.clone(),
path_and_query,
fragment: self.fragment.clone(),
}
}
#[must_use]
pub fn with_path(&self, path: impl Into<String>) -> Self {
let new_path = path.into();
let path_and_query = PathAndQuery {
path: if new_path.starts_with('/') {
new_path
} else {
format!("/{new_path}")
},
query: self.path_and_query.query.clone(),
};
Uri {
scheme: self.scheme.clone(),
authority: self.authority.clone(),
path_and_query,
fragment: self.fragment.clone(),
}
}
#[must_use]
pub fn with_authority(&self, authority: impl Into<Option<Authority>>) -> Self {
Uri {
scheme: self.scheme.clone(),
authority: authority.into(),
path_and_query: self.path_and_query.clone(),
fragment: self.fragment.clone(),
}
}
#[must_use]
pub fn with_scheme(&self, scheme: Scheme) -> Self {
Uri {
scheme,
authority: self.authority.clone(),
path_and_query: self.path_and_query.clone(),
fragment: self.fragment.clone(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct UriBuilder {
scheme: Option<Scheme>,
authority: Option<Authority>,
path: String,
query: Option<String>,
fragment: Option<String>,
}
impl UriBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn scheme(mut self, scheme: Scheme) -> Self {
self.scheme = Some(scheme);
self
}
#[must_use]
pub fn authority(mut self, authority: Authority) -> Self {
self.authority = Some(authority);
self
}
pub fn authority_str(mut self, s: impl Into<String>) -> Result<Self, String> {
let s = s.into();
let authority = Authority::parse(&s).map_err(|e| e.to_string())?;
self.authority = Some(authority);
Ok(self)
}
#[must_use]
pub fn path(mut self, path: impl Into<String>) -> Self {
let p = path.into();
self.path = if p.starts_with('/') { p } else { format!("/{p}") };
self
}
#[must_use]
pub fn query_str(mut self, query: impl Into<String>) -> Self {
let q = query.into();
self.query = if q.is_empty() { None } else { Some(q) };
self
}
#[must_use]
pub fn query(mut self, query: Query) -> Self {
let q = query.to_string();
self.query = if q.is_empty() { None } else { Some(q) };
self
}
#[must_use]
pub fn fragment(mut self, fragment: impl Into<String>) -> Self {
let f = fragment.into();
self.fragment = if f.is_empty() { None } else { Some(f) };
self
}
pub fn build(self) -> Result<Uri, InvalidUri> {
let scheme = self.scheme.ok_or_else(|| {
InvalidUri::new("cannot build Uri without a scheme — call .scheme() first")
})?;
let path_and_query = PathAndQuery {
path: if self.path.is_empty() {
"/".to_string()
} else if self.path.starts_with('/') {
self.path
} else {
format!("/{}", self.path)
},
query: self
.query
.as_deref()
.map(Query::parse)
.transpose()
.unwrap_or_default()
.unwrap_or_default(),
};
Ok(Uri {
scheme,
authority: self.authority,
path_and_query,
fragment: self.fragment,
})
}
}
#[allow(clippy::from_over_into)]
impl Into<String> for Uri {
fn into(self) -> String {
format!("{self:}")
}
}
impl fmt::Display for Uri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:", self.scheme)?;
if let Some(auth) = &self.authority {
write!(f, "//{auth}")?;
}
write!(f, "{}", self.path_and_query)?;
if let Some(frag) = &self.fragment {
write!(f, "#{frag}")?;
}
Ok(())
}
}