Skip to main content

microformats_types/
class.rs

1//! Microformat class types.
2
3use crate::Error;
4use std::str::FromStr;
5
6/// Standard microformats2 class types recognized by this library.
7#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
8#[serde(rename_all = "kebab-case")]
9pub enum KnownClass {
10    /// A syndicatable piece of content.
11    #[serde(alias = "h-entry")]
12    Entry,
13    /// A reference to another piece of content.
14    #[serde(alias = "h-cite")]
15    Cite,
16    /// A person, organization, or venue.
17    #[serde(alias = "h-card")]
18    Card,
19    /// A stream of entries.
20    #[serde(alias = "h-feed")]
21    Feed,
22    /// An event with a date, time, and location.
23    #[serde(alias = "h-event")]
24    Event,
25    /// A product for sale or review.
26    #[serde(alias = "h-product")]
27    Product,
28    /// An address (street, city, etc.).
29    #[serde(alias = "h-adr")]
30    Adr,
31    /// Geographic coordinates.
32    #[serde(alias = "h-geo")]
33    Geo,
34    /// A resume or CV.
35    #[serde(alias = "h-resume")]
36    Resume,
37    /// A review of something.
38    #[serde(alias = "h-review")]
39    Review,
40    /// A recipe.
41    #[serde(alias = "h-recipe")]
42    Recipe,
43}
44
45impl FromStr for KnownClass {
46    type Err = Error;
47
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        match s.to_ascii_lowercase().as_str() {
50            "h-entry" | "entry" => Ok(Self::Entry),
51            "h-cite" | "cite" => Ok(Self::Cite),
52            "h-card" | "card" => Ok(Self::Card),
53            "h-event" | "event" => Ok(Self::Event),
54            "h-product" | "product" => Ok(Self::Product),
55            "h-feed" | "feed" => Ok(Self::Feed),
56            "h-geo" | "geo" => Ok(Self::Geo),
57            "h-adr" | "adr" => Ok(Self::Adr),
58            "h-resume" | "resume" => Ok(Self::Resume),
59            "h-review" | "review" => Ok(Self::Review),
60            "h-recipe" | "recipe" => Ok(Self::Recipe),
61            _ => Err(Error::NotKnownClass(s.to_string())),
62        }
63    }
64}
65
66impl std::fmt::Display for KnownClass {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        f.write_str(match self {
69            KnownClass::Entry => "h-entry",
70            KnownClass::Cite => "h-cite",
71            KnownClass::Card => "h-card",
72            KnownClass::Feed => "h-feed",
73            KnownClass::Event => "h-event",
74            KnownClass::Product => "h-product",
75            KnownClass::Adr => "h-adr",
76            KnownClass::Geo => "h-geo",
77            KnownClass::Resume => "h-resume",
78            KnownClass::Review => "h-review",
79            KnownClass::Recipe => "h-recipe",
80        })
81    }
82}
83
84/// A microformats2 class, either a known standard type or a custom extension.
85#[derive(Debug, Clone, Eq)]
86pub enum Class {
87    /// A recognized standard microformats2 class.
88    Known(KnownClass),
89    /// A custom or extension microformats class.
90    Custom(String),
91}
92
93impl PartialOrd for Class {
94    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
95        self.to_string().partial_cmp(&other.to_string())
96    }
97}
98
99impl PartialEq for Class {
100    fn eq(&self, other: &Self) -> bool {
101        self.to_string().eq(&other.to_string())
102    }
103}
104
105impl FromStr for Class {
106    type Err = std::convert::Infallible;
107
108    fn from_str(class_str: &str) -> Result<Self, Self::Err> {
109        KnownClass::from_str(class_str)
110            .or_else(|_| KnownClass::from_str(&class_str.replace("h-", "")))
111            .map(Class::Known)
112            .or_else(|_| Ok(Self::Custom(class_str.trim_start_matches("h-").to_string())))
113    }
114}
115
116impl std::fmt::Display for Class {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        match self {
119            Self::Known(class) => f.write_fmt(format_args!("{}", class)),
120            Self::Custom(class) => f.write_fmt(format_args!("h-{}", class)),
121        }
122    }
123}
124
125impl serde::Serialize for Class {
126    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127    where
128        S: serde::Serializer,
129    {
130        serializer.serialize_str(self.to_string().as_str())
131    }
132}
133
134struct ClassVisitor;
135
136impl serde::de::Visitor<'_> for ClassVisitor {
137    type Value = Class;
138
139    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        formatter.write_str(
141            "a string that follows Microformats class conventions of being prefixed by 'h-'",
142        )
143    }
144
145    fn visit_str<E>(self, class_str: &str) -> Result<Self::Value, E>
146    where
147        E: serde::de::Error,
148    {
149        Class::from_str(class_str).map_err(|e| E::custom(e.to_string()))
150    }
151}
152
153impl<'de> serde::Deserialize<'de> for Class {
154    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155    where
156        D: serde::Deserializer<'de>,
157    {
158        deserializer.deserialize_string(ClassVisitor)
159    }
160}
161
162impl Class {
163    /// Returns true if this is a recognized standard class.
164    pub fn is_recognized(&self) -> bool {
165        !matches!(self, Self::Custom(_))
166    }
167}
168
169impl From<KnownClass> for Class {
170    fn from(kc: KnownClass) -> Self {
171        Self::Known(kc)
172    }
173}