Skip to main content

microformats_types/
relation.rs

1//! Link relation types for Microformats2 documents.
2
3use std::collections::BTreeMap;
4
5/// Represents a link relation with its attributes.
6#[derive(
7    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, serde::Serialize, serde::Deserialize,
8)]
9pub struct Relation {
10    /// The relation types (e.g., "alternate", "author").
11    pub rels: Vec<String>,
12
13    /// The language of the linked resource.
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub hreflang: Option<String>,
16    /// The media type for which the link is intended.
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub media: Option<String>,
19    /// The title of the link.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub title: Option<String>,
22    /// The MIME type of the linked resource.
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub r#type: Option<String>,
25    /// The text content of the link element.
26    #[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    /// Merges another relation into this one, combining rels and filling in missing attributes.
40    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    /// Converts this relation to an HTTP Link header value.
64    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    /// Converts this relation to an HTML link element.
69    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/// A collection of relations indexed by URL.
82#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
83pub struct Relations {
84    /// The relations indexed by their target URL.
85    #[serde(flatten)]
86    pub items: BTreeMap<url::Url, Relation>,
87}
88
89impl Relations {
90    /// Returns a map from relation type to URLs that have that relation.
91    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}