use http::Uri;
use std::fmt;
use std::path::Path;
use std::str::Split;
use url::{Origin, PathSegmentsMut, UrlQuery};
use url::{ParseError, Url, form_urlencoded::Serializer};
#[derive(Debug, Clone, PartialEq)]
pub struct UnixUrl {
inner: Url,
}
impl UnixUrl {
pub fn new<P>(socket: P, path: &str) -> Result<Self, ParseError>
where
P: AsRef<Path>,
{
let encoded_socket = hex::encode(socket.as_ref().to_string_lossy().as_bytes());
let normalized_path = if path.starts_with('/') {
path.to_string()
} else {
format!("/{path}",)
};
let url_string = format!("unix://{encoded_socket}{normalized_path}");
let url = Url::parse(&url_string)?;
Ok(Self { inner: url })
}
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
pub fn as_url(&self) -> &Url {
&self.inner
}
pub fn fragment(&self) -> Option<&str> {
self.inner.fragment()
}
pub fn into_inner(self) -> Url {
self.inner
}
pub fn origin(&self) -> Origin {
self.inner.origin()
}
pub fn path(&self) -> &str {
self.inner.path()
}
pub fn path_segments(&self) -> Option<Split<'_, char>> {
self.inner.path_segments()
}
pub fn path_segments_mut(&mut self) -> Option<PathSegmentsMut<'_>> {
self.inner.path_segments_mut().ok()
}
pub fn query(&self) -> Option<&str> {
self.inner.query()
}
pub fn query_pairs_mut(&mut self) -> Serializer<'_, UrlQuery<'_>> {
self.inner.query_pairs_mut()
}
pub fn set_path(&mut self, path: &str) {
self.inner.set_path(path);
}
pub fn set_query(&mut self, query: Option<&str>) {
self.inner.set_query(query);
}
}
impl fmt::Display for UnixUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
impl From<UnixUrl> for Url {
fn from(value: UnixUrl) -> Self {
value.inner
}
}
impl TryFrom<UnixUrl> for Uri {
type Error = http::Error;
fn try_from(value: UnixUrl) -> Result<Self, Self::Error> {
let uri = value.inner.as_str().parse()?;
Ok(uri)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_and_to_string() {
let url = UnixUrl::new("/tmp/my.socket", "/").unwrap();
assert_eq!(url.as_str(), "unix://2f746d702f6d792e736f636b6574/");
}
#[test]
fn test_path_normalization() {
let url = UnixUrl::new("/tmp/my.socket", "v1/status").unwrap();
assert_eq!(url.path(), "/v1/status");
}
#[test]
fn test_query_and_fragment() {
let url = UnixUrl::new("/tmp/my.socket", "/hello/world?debug=true#frag").unwrap();
assert_eq!(url.query(), Some("debug=true"));
assert_eq!(url.fragment(), Some("frag"));
}
#[test]
fn test_query_mutation() {
let mut url = UnixUrl::new("/tmp/my.socket", "/foo").unwrap();
url.set_query(Some("x=1"));
assert_eq!(url.query(), Some("x=1"));
let mut qp = url.query_pairs_mut();
qp.append_pair("y", "2");
drop(qp);
assert!(url.query().unwrap().contains("x=1"));
assert!(url.query().unwrap().contains("y=2"));
}
}