#![forbid(unsafe_code)]
#![deny(
missing_docs,
missing_debug_implementations,
missing_copy_implementations
)]
pub mod scanners;
pub mod validation;
pub use validation::{validate, BasicContext};
use codespan::{FileId, Span};
use http::uri::PathAndQuery;
use std::path::PathBuf;
use url::Url;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Category {
FileSystem {
path: PathBuf,
fragment: Option<String>,
},
CurrentFile {
fragment: String,
},
Url(Url),
MailTo(String),
}
impl Category {
fn categorise(src: &str) -> Option<Self> {
if src.is_empty() {
return None;
}
if let Some(address) = src.strip_prefix("mailto:") {
return Some(Category::MailTo(address.to_string()));
}
if let Ok(url) = src.parse() {
return Some(Category::Url(url));
}
if let Some(fragment) = src.strip_prefix("#") {
return Some(Category::CurrentFile {
fragment: fragment.to_string(),
});
}
let (path, fragment) = match src.find("#") {
Some(hash) => {
let (path, rest) = src.split_at(hash);
(path, Some(String::from(&rest[1..])))
},
None => (src, None),
};
if let Ok(path_and_query) = path.parse::<PathAndQuery>() {
return Some(Category::FileSystem {
path: PathBuf::from(path_and_query.path()),
fragment,
});
}
None
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct Link {
pub href: String,
pub span: Span,
pub file: FileId,
}
impl Link {
pub fn new<S: Into<String>>(href: S, span: Span, file: FileId) -> Self {
Link {
href: href.into(),
span,
file,
}
}
pub fn category(&self) -> Option<Category> {
Category::categorise(&self.href)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_into_categories() {
let inputs = vec![
(
"https://example.com/",
Some(Category::Url(
Url::parse("https://example.com/").unwrap(),
)),
),
(
"README.md",
Some(Category::FileSystem {
path: PathBuf::from("README.md"),
fragment: None,
}),
),
(
"./README.md",
Some(Category::FileSystem {
path: PathBuf::from("./README.md"),
fragment: None,
}),
),
(
"./README.md#license",
Some(Category::FileSystem {
path: PathBuf::from("./README.md"),
fragment: Some(String::from("license")),
}),
),
(
"mailto:michael@example.com",
Some(Category::MailTo(String::from("michael@example.com"))),
),
];
for (src, should_be) in inputs {
let got = Category::categorise(src);
assert_eq!(got, should_be, "{}", src);
}
}
}