use std::collections::BTreeMap;
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, serde::Serialize, serde::Deserialize,
)]
pub struct Relation {
pub rels: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hreflang: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub media: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
fn expand_text<T: std::fmt::Display>(value: &Option<T>, attr: &str) -> String {
if let Some(value) = value {
format!(" {attr}=\"{value}\"")
} else {
Default::default()
}
}
impl Relation {
pub fn merge_with(&mut self, other: Self) {
self.rels.extend_from_slice(&other.rels);
self.rels.sort();
self.rels.dedup();
if self.hreflang.is_none() {
self.hreflang = other.hreflang;
}
if self.media.is_none() {
self.media = other.media;
}
if self.title.is_none() {
self.title = other.title;
}
if self.r#type.is_none() {
self.r#type = other.r#type;
}
if self.text.is_none() {
self.text = other.text;
}
}
pub fn to_header_value(&self, base_url: &url::Url) -> String {
todo!("convert this relation to a header value with its URLs resolved to {base_url}")
}
pub fn to_html(&self, rel_url: &str) -> String {
format!(
"<link href=\"{rel_url}\"{hreflang}{rel}{type_}{title}{media} />",
hreflang = expand_text(&self.hreflang, "hreflang"),
rel = expand_text(&Some(self.rels.join(" ")), "rel"),
type_ = expand_text(&self.r#type, "type"),
title = expand_text(&self.title, "title"),
media = expand_text(&self.media, "media")
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
pub struct Relations {
#[serde(flatten)]
pub items: BTreeMap<url::Url, Relation>,
}
impl Relations {
pub fn by_rels(&self) -> BTreeMap<String, Vec<url::Url>> {
let mut rels: BTreeMap<String, Vec<url::Url>> = BTreeMap::default();
self.items
.iter()
.flat_map(|(u, rel)| {
rel.rels
.iter()
.map(move |rel_name| (rel_name.to_owned(), u.to_owned()))
})
.for_each(|(rel_name, url)| {
if let Some(rel_urls) = rels.get_mut(&rel_name) {
rel_urls.push(url);
} else {
rels.insert(rel_name, vec![url]);
}
});
rels.iter_mut().for_each(|(_, urls)| {
urls.dedup();
urls.sort()
});
rels
}
}