use crate::{Item, LanguageFilter, Relation, Relations};
use serde::ser::SerializeStruct;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{self, Visitor},
};
use std::collections::{BTreeMap, HashSet};
#[derive(Clone, Debug, PartialEq, Default, Eq)]
pub struct Document {
pub items: Vec<Item>,
pub url: Option<url::Url>,
pub rels: Relations,
pub lang: Option<String>,
#[cfg(feature = "metaformats")]
pub meta_item: Option<Item>,
#[cfg(feature = "debug_flow")]
pub _debug_context: Option<crate::DebugContext>,
}
impl Document {
pub fn new(url: Option<url::Url>) -> Self {
Self {
url,
..Default::default()
}
}
pub fn add_relation(&mut self, url: url::Url, relation: Relation) {
if let Some(rel) = self.rels.items.get_mut(&url) {
rel.merge_with(relation);
} else {
self.rels.items.insert(url.to_owned(), relation);
}
}
}
impl Serialize for Document {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[cfg(all(feature = "metaformats", feature = "debug_flow"))]
let field_count = 6;
#[cfg(all(not(feature = "metaformats"), feature = "debug_flow"))]
let field_count = 5;
#[cfg(all(feature = "metaformats", not(feature = "debug_flow")))]
let field_count = 5;
#[cfg(all(not(feature = "metaformats"), not(feature = "debug_flow")))]
let field_count = 4;
let mut s = serializer.serialize_struct("Document", field_count)?;
s.serialize_field("items", &self.items)?;
s.serialize_field("rel-urls", &self.rels.items)?;
s.serialize_field("rels", &self.rels.by_rels())?;
if let Some(lang) = &self.lang {
s.serialize_field("lang", lang)?;
} else {
s.skip_field("lang")?;
}
#[cfg(feature = "metaformats")]
if let Some(meta_item) = &self.meta_item {
s.serialize_field("meta-item", meta_item)?;
} else {
s.skip_field("meta-item")?;
}
s.end()
}
}
#[derive(serde::Deserialize, Debug)]
#[serde(field_identifier, rename_all = "kebab-case")]
enum DocumentDeserializationFields {
Items,
RelUrls,
Rels,
Url,
Lang,
#[cfg(feature = "metaformats")]
MetaItem,
}
impl<'de> Deserialize<'de> for Document {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct DocumentVisitor;
impl<'de> Visitor<'de> for DocumentVisitor {
type Value = Document;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a Microformat document represented with the expected fields")
}
fn visit_map<V>(self, mut map: V) -> Result<Document, V::Error>
where
V: de::MapAccess<'de>,
{
let mut document = Document::default();
let mut rel_urls: Option<Relations> = None;
while let Ok(Some(key)) = map.next_key() {
match key {
DocumentDeserializationFields::Items => {
let raw_items = map.next_value::<Vec<Item>>()?;
document.items.extend(raw_items);
}
DocumentDeserializationFields::Url => {
if document.url.is_some() {
return Err(de::Error::duplicate_field("url"));
}
document.url = map.next_value()?;
}
DocumentDeserializationFields::RelUrls => {
if rel_urls.is_some() {
return Err(de::Error::duplicate_field("rel-urls"));
}
rel_urls = map.next_value()?;
}
DocumentDeserializationFields::Lang => {
if document.lang.is_some() {
return Err(de::Error::duplicate_field("lang"));
}
document.lang = map.next_value()?;
}
DocumentDeserializationFields::Rels => {
map.next_value::<BTreeMap<String, Vec<String>>>()?;
}
#[cfg(feature = "metaformats")]
DocumentDeserializationFields::MetaItem => {
if document.meta_item.is_some() {
return Err(de::Error::duplicate_field("meta-item"));
}
document.meta_item = map.next_value()?;
}
}
}
document.rels = rel_urls.unwrap_or_default();
Ok(document)
}
}
#[cfg(feature = "metaformats")]
let expected_fields = &["items", "rel-urls", "url", "lang", "meta-item"];
#[cfg(not(feature = "metaformats"))]
let expected_fields = &["items", "rel-urls", "url", "lang"];
deserializer.deserialize_struct("Document", expected_fields, DocumentVisitor)
}
}
impl IntoIterator for Document {
type Item = Item;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.items
.into_iter()
.flat_map(|i| i.into_iter())
.collect::<Vec<Item>>()
.into_iter()
}
}
impl LanguageFilter for Document {
fn matches_languages(&self, languages: &HashSet<&str>) -> bool {
self.items
.iter()
.any(|item| item.matches_languages(languages))
}
fn filter_by_languages_set(&self, languages: &HashSet<&str>) -> Option<Self> {
let filtered_items: Vec<Item> = self
.items
.iter()
.filter_map(|item| item.filter_by_languages_set(languages))
.collect();
if filtered_items.is_empty() {
return None;
}
Some(Self {
items: filtered_items,
url: self.url.clone(),
rels: self.rels.clone(),
lang: self.lang.clone(),
#[cfg(feature = "metaformats")]
meta_item: self
.meta_item
.as_ref()
.and_then(|item| item.filter_by_languages_set(languages)),
#[cfg(feature = "debug_flow")]
_debug_context: self._debug_context.clone(),
})
}
}