use serde::{Deserialize, Serialize};
use urlencoding::encode;
use crate::api::DATATRACKER_BASE_URL;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DocumentType {
Rfc(u32),
Draft(String),
}
impl DocumentType {
pub fn from_user_input(s: &str) -> Self {
let s = s.trim().to_lowercase();
if let Some(num_str) = s.strip_prefix("rfc") {
if let Ok(num) = num_str.trim().parse::<u32>() {
return DocumentType::Rfc(num);
}
}
if let Ok(num) = s.parse::<u32>() {
return DocumentType::Rfc(num);
}
if s.starts_with("draft-") {
DocumentType::Draft(s)
} else {
DocumentType::Draft(format!("draft-{}", s))
}
}
pub fn from_canonical_name(name: &str) -> Self {
if let Some(num_str) = name.strip_prefix("rfc") {
if let Ok(num) = num_str.parse::<u32>() {
return DocumentType::Rfc(num);
}
}
DocumentType::Draft(name.to_string())
}
pub fn name(&self) -> String {
match self {
DocumentType::Rfc(num) => format!("rfc{}", num),
DocumentType::Draft(name) => name.clone(),
}
}
pub fn display_name(&self) -> String {
match self {
DocumentType::Rfc(num) => format!("RFC {}", num),
DocumentType::Draft(name) => name.clone(),
}
}
pub fn datatracker_url(&self) -> String {
match self {
DocumentType::Rfc(num) => format!("{}/doc/rfc{}/", DATATRACKER_BASE_URL, num),
DocumentType::Draft(name) => {
format!("{}/doc/{}/", DATATRACKER_BASE_URL, encode(name))
}
}
}
}
impl std::fmt::Display for DocumentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display_name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Format {
Html,
Text,
}
impl Format {
pub fn extension(&self) -> &'static str {
match self {
Format::Html => "html",
Format::Text => "txt",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
pub name: String,
pub title: String,
pub doc_type: DocumentType,
}
impl Document {
pub fn new(name: String, title: String, doc_type: DocumentType) -> Self {
Self {
name,
title,
doc_type,
}
}
pub fn short_title(&self, max_len: usize) -> String {
if self.title.chars().count() <= max_len {
self.title.clone()
} else {
let truncated: String = self.title.chars().take(max_len.saturating_sub(3)).collect();
format!("{}...", truncated)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_user_input_rfc() {
assert_eq!(
DocumentType::from_user_input("9000"),
DocumentType::Rfc(9000)
);
assert_eq!(
DocumentType::from_user_input("rfc9000"),
DocumentType::Rfc(9000)
);
assert_eq!(
DocumentType::from_user_input("RFC9000"),
DocumentType::Rfc(9000)
);
assert_eq!(
DocumentType::from_user_input("RFC 9000"),
DocumentType::Rfc(9000)
);
assert_eq!(
DocumentType::from_user_input(" rfc9000 "),
DocumentType::Rfc(9000)
);
}
#[test]
fn test_from_user_input_draft() {
assert_eq!(
DocumentType::from_user_input("draft-ietf-quic-transport-34"),
DocumentType::Draft("draft-ietf-quic-transport-34".to_string())
);
assert_eq!(
DocumentType::from_user_input("ietf-quic-transport"),
DocumentType::Draft("draft-ietf-quic-transport".to_string())
);
}
#[test]
fn test_from_canonical_name() {
assert_eq!(
DocumentType::from_canonical_name("rfc9000"),
DocumentType::Rfc(9000)
);
assert_eq!(
DocumentType::from_canonical_name("draft-ietf-quic-transport-34"),
DocumentType::Draft("draft-ietf-quic-transport-34".to_string())
);
assert_eq!(
DocumentType::from_canonical_name("rfcfoo"),
DocumentType::Draft("rfcfoo".to_string())
);
}
#[test]
fn test_document_type_display() {
assert_eq!(DocumentType::Rfc(9000).to_string(), "RFC 9000");
assert_eq!(
DocumentType::Draft("draft-ietf-quic-transport".to_string()).to_string(),
"draft-ietf-quic-transport"
);
}
#[test]
fn test_datatracker_url() {
assert_eq!(
DocumentType::Rfc(9000).datatracker_url(),
"https://datatracker.ietf.org/doc/rfc9000/"
);
assert_eq!(
DocumentType::Draft("draft-ietf-quic-transport".to_string()).datatracker_url(),
"https://datatracker.ietf.org/doc/draft-ietf-quic-transport/"
);
assert_eq!(
DocumentType::Draft("draft with spaces/and/slashes".to_string()).datatracker_url(),
"https://datatracker.ietf.org/doc/draft%20with%20spaces%2Fand%2Fslashes/"
);
}
#[test]
fn test_short_title() {
let doc = Document::new(
"rfc9000".to_string(),
"A Very Long Title That Needs Truncation".to_string(),
DocumentType::Rfc(9000),
);
assert_eq!(
doc.short_title(100),
"A Very Long Title That Needs Truncation"
);
assert_eq!(doc.short_title(20), "A Very Long Title...");
assert_eq!(doc.short_title(3), "...");
assert_eq!(doc.short_title(0), "...");
}
#[test]
fn test_short_title_utf8() {
let doc = Document::new(
"rfc1234".to_string(),
"Café résumé naïve".to_string(),
DocumentType::Rfc(1234),
);
let result = doc.short_title(10);
assert!(result.ends_with("..."));
assert!(result.chars().count() <= 10);
}
}