microformats_types/
relation.rs1use std::collections::BTreeMap;
4
5#[derive(
7 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, serde::Serialize, serde::Deserialize,
8)]
9pub struct Relation {
10 pub rels: Vec<String>,
12
13 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub hreflang: Option<String>,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub media: Option<String>,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub title: Option<String>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub r#type: Option<String>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub text: Option<String>,
28}
29
30fn expand_text<T: std::fmt::Display>(value: &Option<T>, attr: &str) -> String {
31 if let Some(value) = value {
32 format!(" {attr}=\"{value}\"")
33 } else {
34 Default::default()
35 }
36}
37
38impl Relation {
39 pub fn merge_with(&mut self, other: Self) {
41 self.rels.extend_from_slice(&other.rels);
42 self.rels.sort();
43 self.rels.dedup();
44
45 if self.hreflang.is_none() {
46 self.hreflang = other.hreflang;
47 }
48
49 if self.media.is_none() {
50 self.media = other.media;
51 }
52 if self.title.is_none() {
53 self.title = other.title;
54 }
55 if self.r#type.is_none() {
56 self.r#type = other.r#type;
57 }
58 if self.text.is_none() {
59 self.text = other.text;
60 }
61 }
62
63 pub fn to_header_value(&self, base_url: &url::Url) -> String {
65 todo!("convert this relation to a header value with its URLs resolved to {base_url}")
66 }
67
68 pub fn to_html(&self, rel_url: &str) -> String {
70 format!(
71 "<link href=\"{rel_url}\"{hreflang}{rel}{type_}{title}{media} />",
72 hreflang = expand_text(&self.hreflang, "hreflang"),
73 rel = expand_text(&Some(self.rels.join(" ")), "rel"),
74 type_ = expand_text(&self.r#type, "type"),
75 title = expand_text(&self.title, "title"),
76 media = expand_text(&self.media, "media")
77 )
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
83pub struct Relations {
84 #[serde(flatten)]
86 pub items: BTreeMap<url::Url, Relation>,
87}
88
89impl Relations {
90 pub fn by_rels(&self) -> BTreeMap<String, Vec<url::Url>> {
92 let mut rels: BTreeMap<String, Vec<url::Url>> = BTreeMap::default();
93 self.items
94 .iter()
95 .flat_map(|(u, rel)| {
96 rel.rels
97 .iter()
98 .map(move |rel_name| (rel_name.to_owned(), u.to_owned()))
99 })
100 .for_each(|(rel_name, url)| {
101 if let Some(rel_urls) = rels.get_mut(&rel_name) {
102 rel_urls.push(url);
103 } else {
104 rels.insert(rel_name, vec![url]);
105 }
106 });
107
108 rels.iter_mut().for_each(|(_, urls)| {
109 urls.dedup();
110 urls.sort()
111 });
112
113 rels
114 }
115}