use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "api", derive(utoipa::ToSchema))]
pub struct Uri {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u32>,
pub kind: UriKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "api", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum UriKind {
Hyperlink,
Image,
Anchor,
Citation,
Reference,
Email,
}
pub fn classify_uri(url: &str) -> UriKind {
if url.starts_with("mailto:") {
UriKind::Email
} else if url.starts_with('#') {
UriKind::Anchor
} else {
UriKind::Hyperlink
}
}
impl Uri {
pub fn hyperlink(url: impl Into<String>, label: Option<String>) -> Self {
let url = url.into();
let kind = classify_uri(&url);
Self {
url,
label,
page: None,
kind,
}
}
pub fn image(url: impl Into<String>, label: Option<String>) -> Self {
Self {
url: url.into(),
label,
page: None,
kind: UriKind::Image,
}
}
pub fn citation(url: impl Into<String>, label: Option<String>) -> Self {
Self {
url: url.into(),
label,
page: None,
kind: UriKind::Citation,
}
}
pub fn anchor(url: impl Into<String>, label: Option<String>) -> Self {
Self {
url: url.into(),
label,
page: None,
kind: UriKind::Anchor,
}
}
pub fn email(url: impl Into<String>, label: Option<String>) -> Self {
Self {
url: url.into(),
label,
page: None,
kind: UriKind::Email,
}
}
pub fn reference(url: impl Into<String>, label: Option<String>) -> Self {
Self {
url: url.into(),
label,
page: None,
kind: UriKind::Reference,
}
}
#[must_use]
pub fn with_page(mut self, page: u32) -> Self {
self.page = Some(page);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uri_hyperlink() {
let uri = Uri::hyperlink("https://example.com", Some("Example".to_string()));
assert_eq!(uri.kind, UriKind::Hyperlink);
assert_eq!(uri.url, "https://example.com");
assert_eq!(uri.label, Some("Example".to_string()));
}
#[test]
fn test_uri_mailto_auto_detects_email() {
let uri = Uri::hyperlink("mailto:test@example.com", None);
assert_eq!(uri.kind, UriKind::Email);
}
#[test]
fn test_uri_citation() {
let uri = Uri::citation("10.1234/test", Some("Smith 2024".to_string()));
assert_eq!(uri.kind, UriKind::Citation);
}
#[test]
fn test_uri_with_page() {
let uri = Uri::hyperlink("https://example.com", None).with_page(5);
assert_eq!(uri.page, Some(5));
}
#[test]
fn test_uri_serialization() {
let uri = Uri::hyperlink("https://example.com", Some("Example".to_string()));
let json = serde_json::to_string(&uri).unwrap();
assert!(json.contains("\"url\":\"https://example.com\""));
assert!(json.contains("\"kind\":\"hyperlink\""));
let deserialized: Uri = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, uri);
}
}