microformats_types/
document.rs1use crate::{Item, LanguageFilter, Relation, Relations};
4use serde::ser::SerializeStruct;
5use serde::{
6 Deserialize, Deserializer, Serialize, Serializer,
7 de::{self, Visitor},
8};
9use std::collections::{BTreeMap, HashSet};
10
11#[derive(Clone, Debug, PartialEq, Default, Eq)]
13pub struct Document {
14 pub items: Vec<Item>,
16 pub url: Option<url::Url>,
18 pub rels: Relations,
20 pub lang: Option<String>,
22 #[cfg(feature = "metaformats")]
23 pub meta_item: Option<Item>,
24 #[cfg(feature = "debug_flow")]
25 pub _debug_context: Option<crate::DebugContext>,
26}
27
28impl Document {
29 pub fn new(url: Option<url::Url>) -> Self {
31 Self {
32 url,
33 ..Default::default()
34 }
35 }
36
37 pub fn add_relation(&mut self, url: url::Url, relation: Relation) {
39 if let Some(rel) = self.rels.items.get_mut(&url) {
40 rel.merge_with(relation);
41 } else {
42 self.rels.items.insert(url.to_owned(), relation);
43 }
44 }
45}
46
47impl Serialize for Document {
48 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49 where
50 S: Serializer,
51 {
52 #[cfg(all(feature = "metaformats", feature = "debug_flow"))]
53 let field_count = 6;
54 #[cfg(all(not(feature = "metaformats"), feature = "debug_flow"))]
55 let field_count = 5;
56 #[cfg(all(feature = "metaformats", not(feature = "debug_flow")))]
57 let field_count = 5;
58 #[cfg(all(not(feature = "metaformats"), not(feature = "debug_flow")))]
59 let field_count = 4;
60
61 let mut s = serializer.serialize_struct("Document", field_count)?;
62
63 s.serialize_field("items", &self.items)?;
64 s.serialize_field("rel-urls", &self.rels.items)?;
65 s.serialize_field("rels", &self.rels.by_rels())?;
66 if let Some(lang) = &self.lang {
67 s.serialize_field("lang", lang)?;
68 } else {
69 s.skip_field("lang")?;
70 }
71 #[cfg(feature = "metaformats")]
72 if let Some(meta_item) = &self.meta_item {
73 s.serialize_field("meta-item", meta_item)?;
74 } else {
75 s.skip_field("meta-item")?;
76 }
77 s.end()
78 }
79}
80
81#[derive(serde::Deserialize, Debug)]
82#[serde(field_identifier, rename_all = "kebab-case")]
83enum DocumentDeserializationFields {
84 Items,
85 RelUrls,
86 Rels,
87 Url,
88 Lang,
89 #[cfg(feature = "metaformats")]
90 MetaItem,
91}
92
93impl<'de> Deserialize<'de> for Document {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: Deserializer<'de>,
97 {
98 struct DocumentVisitor;
99
100 impl<'de> Visitor<'de> for DocumentVisitor {
101 type Value = Document;
102
103 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 formatter.write_str("a Microformat document represented with the expected fields")
105 }
106
107 fn visit_map<V>(self, mut map: V) -> Result<Document, V::Error>
108 where
109 V: de::MapAccess<'de>,
110 {
111 let mut document = Document::default();
112 let mut rel_urls: Option<Relations> = None;
113
114 while let Ok(Some(key)) = map.next_key() {
115 match key {
116 DocumentDeserializationFields::Items => {
117 let raw_items = map.next_value::<Vec<Item>>()?;
118 document.items.extend(raw_items);
119 }
120 DocumentDeserializationFields::Url => {
121 if document.url.is_some() {
122 return Err(de::Error::duplicate_field("url"));
123 }
124
125 document.url = map.next_value()?;
126 }
127 DocumentDeserializationFields::RelUrls => {
128 if rel_urls.is_some() {
129 return Err(de::Error::duplicate_field("rel-urls"));
130 }
131
132 rel_urls = map.next_value()?;
133 }
134 DocumentDeserializationFields::Lang => {
135 if document.lang.is_some() {
136 return Err(de::Error::duplicate_field("lang"));
137 }
138
139 document.lang = map.next_value()?;
140 }
141 DocumentDeserializationFields::Rels => {
142 map.next_value::<BTreeMap<String, Vec<String>>>()?;
143 }
144 #[cfg(feature = "metaformats")]
145 DocumentDeserializationFields::MetaItem => {
146 if document.meta_item.is_some() {
147 return Err(de::Error::duplicate_field("meta-item"));
148 }
149
150 document.meta_item = map.next_value()?;
151 }
152 }
153 }
154
155 document.rels = rel_urls.unwrap_or_default();
156
157 Ok(document)
158 }
159 }
160
161 #[cfg(feature = "metaformats")]
162 let expected_fields = &["items", "rel-urls", "url", "lang", "meta-item"];
163 #[cfg(not(feature = "metaformats"))]
164 let expected_fields = &["items", "rel-urls", "url", "lang"];
165
166 deserializer.deserialize_struct("Document", expected_fields, DocumentVisitor)
167 }
168}
169
170impl IntoIterator for Document {
171 type Item = Item;
172 type IntoIter = std::vec::IntoIter<Self::Item>;
173
174 fn into_iter(self) -> Self::IntoIter {
175 self.items
176 .into_iter()
177 .flat_map(|i| i.into_iter())
178 .collect::<Vec<Item>>()
179 .into_iter()
180 }
181}
182
183impl LanguageFilter for Document {
184 fn matches_languages(&self, languages: &HashSet<&str>) -> bool {
185 self.items
186 .iter()
187 .any(|item| item.matches_languages(languages))
188 }
189
190 fn filter_by_languages_set(&self, languages: &HashSet<&str>) -> Option<Self> {
191 let filtered_items: Vec<Item> = self
192 .items
193 .iter()
194 .filter_map(|item| item.filter_by_languages_set(languages))
195 .collect();
196
197 if filtered_items.is_empty() {
198 return None;
199 }
200
201 Some(Self {
202 items: filtered_items,
203 url: self.url.clone(),
204 rels: self.rels.clone(),
205 lang: self.lang.clone(),
206 #[cfg(feature = "metaformats")]
207 meta_item: self
208 .meta_item
209 .as_ref()
210 .and_then(|item| item.filter_by_languages_set(languages)),
211 #[cfg(feature = "debug_flow")]
212 _debug_context: self._debug_context.clone(),
213 })
214 }
215}