indieweb/traits/
mod.rs

1use async_trait::async_trait;
2use http::header::ACCEPT;
3use url::Url;
4
5use crate::{
6    algorithms::ptd::Type,
7    mf2::{self, types::Document},
8};
9
10/// Provides a facade for determining the post type of an value.
11pub trait ItemPostTypeResolutionExt {
12    /// Computes the distinct post type of this item.
13    fn post_type(&self) -> Type;
14}
15
16impl ItemPostTypeResolutionExt for crate::mf2::types::Item {
17    fn post_type(&self) -> Type {
18        crate::algorithms::ptd::resolve_from_object(self.clone()).unwrap_or(Type::Note)
19    }
20}
21
22#[test]
23fn item_post_type_resultion_ext() {
24    let item: crate::mf2::types::Item = serde_json::from_value(serde_json::json!({
25        "type": ["h-entry"],
26        "properties": {
27            "photo": [
28                "http://example.com/image.jpg"
29            ]
30        }
31    }))
32    .unwrap();
33
34    assert_eq!(item.post_type(), Type::Photo);
35}
36
37/// Provides a facade for resolving the Microformats2 document for a value.
38#[async_trait]
39pub trait FetchMF2FromExt {
40    /// With the provided [Client][crate::http::Client], obtain the Microformats document of this value.
41    async fn fetch_mf2_from<C: crate::http::Client>(
42        &self,
43        client: C,
44    ) -> Result<Document, crate::Error>;
45}
46
47#[async_trait]
48impl FetchMF2FromExt for Url {
49    async fn fetch_mf2_from<C: crate::http::Client>(
50        &self,
51        client: C,
52    ) -> Result<Document, crate::Error> {
53        let req = http::Request::get(self.as_str())
54            .header(
55                ACCEPT,
56                "text/html; charset=utf-8, text/mf2+html; charset=utf-8, */*; q=0.6",
57            )
58            .body(crate::http::Body::default())
59            .map_err(crate::Error::Http)?;
60
61        let response = client
62            .send_request(req)
63            .await?
64            .map(|body| body.as_bytes().to_vec());
65        Ok(mf2::parser::http::to_mf2_document(response, self.as_str())
66            .map_err(mf2::Error::Parser)?)
67    }
68}
69
70/// Used to convert a value or a list of values into a normalized list of values.
71pub mod as_string_or_list {
72
73    use std::{fmt, str::FromStr};
74
75    use serde::{
76        de::{self, Visitor},
77        ser::SerializeSeq,
78        Deserialize, Deserializer, Serialize,
79    };
80
81    pub fn deserialize<'de, Item, D, E>(deserializer: D) -> Result<Vec<Item>, D::Error>
82    where
83        Item: Deserialize<'de> + FromStr<Err = E> + fmt::Debug,
84        E: std::error::Error,
85        D: Deserializer<'de>,
86    {
87        struct CocereIntoList;
88
89        impl<'de> Visitor<'de> for CocereIntoList {
90            type Value = Vec<String>;
91
92            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
93                formatter.write_str(
94                    "expecting no value, a string or a list of values, all to be made into a list",
95                )
96            }
97
98            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
99            where
100                E: de::Error,
101            {
102                if v.is_empty() {
103                    Ok(Vec::default())
104                } else {
105                    Ok(vec![v.to_string()])
106                }
107            }
108
109            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
110            where
111                A: de::SeqAccess<'de>,
112            {
113                let mut values = Vec::with_capacity(seq.size_hint().unwrap_or_default());
114
115                while let Some(value) = seq.next_element()? {
116                    values.push(value);
117                }
118
119                Ok(values)
120            }
121        }
122
123        deserializer
124            .deserialize_any(CocereIntoList)
125            .and_then(|strings| {
126                strings.into_iter().try_fold(
127                    Vec::default(),
128                    |mut acc, string| match Item::from_str(&string) {
129                        Ok(v) => {
130                            acc.push(v);
131                            Ok(acc)
132                        }
133                        Err(e) => Err(de::Error::custom(e)),
134                    },
135                )
136            })
137    }
138
139    pub fn serialize<Item, S>(list: &[Item], serializer: S) -> Result<S::Ok, S::Error>
140    where
141        Item: Serialize,
142        S: serde::Serializer,
143    {
144        if let Some(value) = list.first().filter(|_| list.len() == 1) {
145            value.serialize(serializer)
146        } else {
147            let mut seq = serializer.serialize_seq(Some(list.len()))?;
148
149            for v in list {
150                seq.serialize_element(v)?;
151            }
152
153            seq.end()
154        }
155    }
156}
157
158mod test;