use std::str::FromStr; use anyhow::{anyhow, ensure, Error}; use hyper::Uri; use relative_path::RelativePath; #[derive(Clone, Debug)] pub struct Repository { pub path: RepoPath, pub description: String, } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RepoPath { pub site: RepoSite, pub qual: RepoQualifier, pub name: RepoName, } impl RepoPath { pub fn from_parts(site: &str, qual: &str, name: &str) -> Result { Ok(RepoPath { site: site.parse()?, qual: qual.parse()?, name: name.parse()?, }) } pub fn to_usercontent_file_uri(&self, path: &RelativePath) -> Result { let url = format!( "{}/{}/{}/{}/{}", self.site.to_usercontent_base_uri(), self.qual.as_ref(), self.name.as_ref(), self.site.to_usercontent_repo_suffix(), path.normalize() ); Ok(url.parse::()?) } } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum RepoSite { Github, Gitlab, Bitbucket, } impl RepoSite { pub fn to_base_uri(&self) -> &'static str { match self { &RepoSite::Github => "https://github.com", &RepoSite::Gitlab => "https://gitlab.com", &RepoSite::Bitbucket => "https://bitbucket.org", } } pub fn to_usercontent_base_uri(&self) -> &'static str { match self { RepoSite::Github => "https://raw.githubusercontent.com", RepoSite::Gitlab => "https://gitlab.com", RepoSite::Bitbucket => "https://bitbucket.org", } } pub fn to_usercontent_repo_suffix(&self) -> &'static str { match self { RepoSite::Github => "HEAD", RepoSite::Gitlab | RepoSite::Bitbucket => "raw/HEAD", } } } impl FromStr for RepoSite { type Err = Error; fn from_str(input: &str) -> Result { match input { "github" => Ok(RepoSite::Github), "gitlab" => Ok(RepoSite::Gitlab), "bitbucket" => Ok(RepoSite::Bitbucket), _ => Err(anyhow!("unknown repo site identifier")), } } } impl AsRef for RepoSite { fn as_ref(&self) -> &str { match self { &RepoSite::Github => "github", &RepoSite::Gitlab => "gitlab", &RepoSite::Bitbucket => "bitbucket", } } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RepoQualifier(String); impl FromStr for RepoQualifier { type Err = Error; fn from_str(input: &str) -> Result { let is_valid = input .chars() .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_'); ensure!(is_valid, "invalid repo qualifier"); Ok(RepoQualifier(input.to_string())) } } impl AsRef for RepoQualifier { fn as_ref(&self) -> &str { self.0.as_ref() } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RepoName(String); impl FromStr for RepoName { type Err = Error; fn from_str(input: &str) -> Result { let is_valid = input .chars() .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_'); ensure!(is_valid, "invalid repo name"); Ok(RepoName(input.to_string())) } } impl AsRef for RepoName { fn as_ref(&self) -> &str { self.0.as_ref() } } #[cfg(test)] mod tests { use super::*; #[test] fn correct_raw_url_generation() { let paths = [ ("Cargo.toml", "Cargo.toml"), ("/Cargo.toml", "Cargo.toml"), ("libs/badge/Cargo.toml", "libs/badge/Cargo.toml"), ("/libs/badge/Cargo.toml", "libs/badge/Cargo.toml"), ("src/../libs/badge/Cargo.toml", "libs/badge/Cargo.toml"), ("/src/../libs/badge/Cargo.toml", "libs/badge/Cargo.toml"), ]; for (input, expected) in &paths { let repo = RepoPath::from_parts("github", "deps-rs", "deps.rs").unwrap(); let out = repo .to_usercontent_file_uri(RelativePath::new(input)) .unwrap(); let exp = format!( "https://raw.githubusercontent.com/deps-rs/deps.rs/HEAD/{}", expected ); assert_eq!(out.to_string(), exp); } for (input, expected) in &paths { let repo = RepoPath::from_parts("gitlab", "deps-rs", "deps.rs").unwrap(); let out = repo .to_usercontent_file_uri(RelativePath::new(input)) .unwrap(); let exp = format!("https://gitlab.com/deps-rs/deps.rs/raw/HEAD/{}", expected); assert_eq!(out.to_string(), exp); } for (input, expected) in &paths { let repo = RepoPath::from_parts("bitbucket", "deps-rs", "deps.rs").unwrap(); let out = repo .to_usercontent_file_uri(RelativePath::new(input)) .unwrap(); let exp = format!( "https://bitbucket.org/deps-rs/deps.rs/raw/HEAD/{}", expected ); assert_eq!(out.to_string(), exp); } } }