perma-core 0.1.0

Shared types and parsers for permalink service
Documentation
use core::fmt::{self, Display};
use core::net::IpAddr;
use core::ops::{Deref, DerefMut};

use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

pub use url::{Host, ParseError as UriParseError, Position, Url as Uri};

#[repr(transparent)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RelativeRef(Uri);

impl RelativeRef {
    const FAKE_SCHEME: &'static str = "base";
    const FAKE_RELATIVE_HOST: &'static str = ".";

    pub fn parse(input: &str) -> Result<Self, UriParseError> {
        if input.starts_with('/') {
            Uri::parse(&format!("{}://{}", Self::FAKE_SCHEME, input)).map(Self)
        } else {
            Uri::parse(&format!(
                "{}://{}/{}",
                Self::FAKE_SCHEME,
                Self::FAKE_RELATIVE_HOST,
                input,
            ))
            .map(Self)
        }
    }

    pub fn as_str(&self) -> &str {
        match self.0.domain() {
            None => &self.0[Position::BeforePath..],
            Some(".") => self.0[Position::BeforePath..].trim_start_matches('/'),
            _ => unreachable!(),
        }
    }

    pub fn path(&self) -> &str {
        match self.0.domain() {
            None => self.0.path(),
            Some(".") => self.0.path().trim_start_matches('/'),
            _ => unreachable!(),
        }
    }

    pub fn join(&self, input: &str) -> Result<Self, UriParseError> {
        self.0.join(input).map(Self)
    }

    pub fn set_ip_host(&mut self, address: IpAddr) {
        let _ = address;
    }

    pub fn set_host(&mut self, host: &str) {
        let _ = host;
    }

    pub fn authority(&self) -> &str {
        ""
    }

    pub fn scheme(&self) -> &str {
        ""
    }

    pub fn host(&self) -> Option<Host<&str>> {
        None
    }

    pub fn host_str(&self) -> Option<&str> {
        None
    }

    pub fn has_host(&self) -> bool {
        false
    }

    pub fn domain(&self) -> Option<&str> {
        None
    }
}

impl Deref for RelativeRef {
    type Target = Uri;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for RelativeRef {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Display for RelativeRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.as_str().fmt(f)
    }
}

impl Serialize for RelativeRef {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.as_str().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for RelativeRef {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Self::parse(&s).map_err(D::Error::custom)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple_reflexivity() {
        let paths = [
            "abc",
            "/abc",
            // FIXME: dot-segments are not preserved
            // "../abc",
            // "/abc/../def",
            "abc/def",
            "abc/def/",
            "abc/def/*",
            "abc?q=1#title",
        ];

        for path in paths {
            assert_eq!(path, RelativeRef::parse(path).unwrap().to_string());
        }
    }
}