lr2-oxytabler 0.10.2

Table manager for Lunatic Rave 2
Documentation
use anyhow::{Context as _, Result};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedUrl(String);

impl TryFrom<&str> for ResolvedUrl {
    type Error = anyhow::Error;
    fn try_from(url: &str) -> Result<Self> {
        Self::validate(url)?;
        Ok(Self(url.trim_matches(' ').to_string()))
    }
}
impl TryFrom<String> for ResolvedUrl {
    type Error = anyhow::Error;
    fn try_from(url: String) -> Result<Self> {
        Self::try_from(url.as_str())
    }
}

impl ResolvedUrl {
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn with_last_segment(&self, raw_url: &str) -> Result<Self> {
        if let Ok(url) = raw_url.try_into() {
            return Ok(url);
        }
        let last_slash = self.0.rfind(['/', '\\']).context("No slash in URL")?;
        let url_prefix = &self.0[..=last_slash];
        Self::try_from(format!("{url_prefix}{raw_url}"))
    }

    pub fn validate(url: &str) -> Result<()> {
        let url = url.trim_matches(' ');
        anyhow::ensure!(
            url.starts_with("https://") || url.starts_with("http://") || url.starts_with("file://"),
            "Invalid protocol in URL: {url}"
        );
        Ok(())
    }
}

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

    #[test]
    fn test_json_table_header_score_url() {
        use super::ResolvedUrl;

        assert_eq!(
            ResolvedUrl::try_from("https://stellabms.xyz/fr/table.html")
                .unwrap()
                .with_last_segment("score.json")
                .ok()
                .map(|url| url.0)
                .as_deref(),
            Some("https://stellabms.xyz/fr/score.json")
        );

        assert_eq!(
            ResolvedUrl::try_from("http://flowermaster.web.fc2.com/lrnanido/gla/LN.html")
                .unwrap()
                .with_last_segment("http://flowermaster.web.fc2.com/lrnanido/gla/score.json")
                .ok()
                .map(|url| url.0)
                .as_deref(),
            Some("http://flowermaster.web.fc2.com/lrnanido/gla/score.json")
        );

        assert_eq!(
            ResolvedUrl::try_from("file:///a/b")
                .unwrap()
                .with_last_segment("file:///c")
                .ok()
                .map(|url| url.0)
                .as_deref(),
            Some("file:///c")
        );

        assert_eq!(
            ResolvedUrl::try_from("file://a/b")
                .unwrap()
                .with_last_segment("c")
                .ok()
                .map(|url| url.0)
                .as_deref(),
            Some("file://a/c")
        );

        assert_eq!(
            ResolvedUrl::try_from("file://D:\\a\\b")
                .unwrap()
                .with_last_segment("file://D:\\c")
                .ok()
                .map(|url| url.0)
                .as_deref(),
            Some("file://D:\\c")
        );

        assert_eq!(
            ResolvedUrl::try_from("file://D:\\a\\b")
                .unwrap()
                .with_last_segment("c")
                .ok()
                .map(|url| url.0)
                .as_deref(),
            Some("file://D:\\a\\c")
        );
    }

    #[test]
    fn constructs_url() {
        use super::ResolvedUrl;
        assert_eq!(ResolvedUrl::try_from("  file://  ").unwrap().0, "file://");
        assert_eq!(ResolvedUrl::try_from("  http://  ").unwrap().0, "http://");
        assert_eq!(ResolvedUrl::try_from("  https://  ").unwrap().0, "https://");
        assert_eq!(
            ResolvedUrl::try_from("  invalid://  ")
                .unwrap_err()
                .to_string(),
            "Invalid protocol in URL: invalid://"
        );
    }
}