#![cfg_attr(
feature = "document-features",
cfg_attr(doc, doc = ::document_features::document_features!())
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![deny(rust_2018_idioms, missing_docs)]
#![forbid(unsafe_code)]
use bstr::{BStr, BString};
pub mod parse;
#[doc(inline)]
pub use parse::parse;
pub mod expand_path;
#[doc(inline)]
pub use expand_path::expand_path;
mod scheme;
pub use scheme::Scheme;
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Url {
pub scheme: Scheme,
user: Option<String>,
host: Option<String>,
serialize_alternative_form: bool,
pub port: Option<u16>,
pub path: bstr::BString,
}
impl Url {
pub fn from_parts(
scheme: Scheme,
user: Option<String>,
host: Option<String>,
port: Option<u16>,
path: BString,
) -> Result<Self, parse::Error> {
parse(
Url {
scheme,
user,
host,
port,
path,
serialize_alternative_form: false,
}
.to_bstring()
.as_ref(),
)
}
pub fn from_parts_as_alternative_form(
scheme: Scheme,
user: Option<String>,
host: Option<String>,
port: Option<u16>,
path: BString,
) -> Result<Self, parse::Error> {
parse(
Url {
scheme,
user,
host,
port,
path,
serialize_alternative_form: true,
}
.to_bstring()
.as_ref(),
)
}
}
impl Url {
pub fn set_user(&mut self, user: Option<String>) -> Option<String> {
let prev = self.user.take();
self.user = user;
prev
}
}
impl Url {
pub fn serialize_alternate_form(mut self, use_alternate_form: bool) -> Self {
self.serialize_alternative_form = use_alternate_form;
self
}
pub fn canonicalize(&mut self) -> Result<(), git_path::realpath::Error> {
if self.scheme == Scheme::File {
let path = git_path::from_bstr(self.path.as_ref());
let abs_path = git_path::realpath(path)?;
self.path = git_path::into_bstr(abs_path).into_owned();
}
Ok(())
}
}
impl Url {
pub fn user(&self) -> Option<&str> {
self.user.as_deref()
}
pub fn host(&self) -> Option<&str> {
self.host.as_deref()
}
pub fn path_is_root(&self) -> bool {
self.path == "/"
}
pub fn port_or_default(&self) -> Option<u16> {
self.port.or_else(|| {
use Scheme::*;
Some(match self.scheme {
Http => 80,
Https => 443,
Ssh => 22,
Git => 9418,
File | Ext(_) => return None,
})
})
}
}
impl Url {
pub fn canonicalized(&self) -> Result<Self, git_path::realpath::Error> {
let mut res = self.clone();
res.canonicalize()?;
Ok(res)
}
}
impl Url {
pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
if !(self.serialize_alternative_form && (self.scheme == Scheme::File || self.scheme == Scheme::Ssh)) {
out.write_all(self.scheme.as_str().as_bytes())?;
out.write_all(b"://")?;
}
match (&self.user, &self.host) {
(Some(user), Some(host)) => {
out.write_all(user.as_bytes())?;
out.write_all(&[b'@'])?;
out.write_all(host.as_bytes())?;
}
(None, Some(host)) => {
out.write_all(host.as_bytes())?;
}
(None, None) => {}
(Some(_user), None) => unreachable!("BUG: should not be possible to have a user but no host"),
};
if let Some(port) = &self.port {
write!(&mut out, ":{port}")?;
}
if self.serialize_alternative_form && self.scheme == Scheme::Ssh {
out.write_all(b":")?;
}
out.write_all(&self.path)?;
Ok(())
}
pub fn to_bstring(&self) -> bstr::BString {
let mut buf = Vec::with_capacity(
(5 + 3)
+ self.user.as_ref().map(|n| n.len()).unwrap_or_default()
+ 1
+ self.host.as_ref().map(|h| h.len()).unwrap_or_default()
+ self.port.map(|_| 5).unwrap_or_default()
+ self.path.len(),
);
self.write_to(&mut buf).expect("io cannot fail in memory");
buf.into()
}
}
impl Url {
pub fn from_bytes(bytes: &BStr) -> Result<Self, parse::Error> {
parse(bytes)
}
}
mod impls;