use crate::Error;
use std::str::FromStr;
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
pub enum KnownClass {
#[serde(alias = "h-entry")]
Entry,
#[serde(alias = "h-cite")]
Cite,
#[serde(alias = "h-card")]
Card,
#[serde(alias = "h-feed")]
Feed,
#[serde(alias = "h-event")]
Event,
#[serde(alias = "h-product")]
Product,
#[serde(alias = "h-adr")]
Adr,
#[serde(alias = "h-geo")]
Geo,
#[serde(alias = "h-resume")]
Resume,
#[serde(alias = "h-review")]
Review,
#[serde(alias = "h-recipe")]
Recipe,
}
impl FromStr for KnownClass {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"h-entry" | "entry" => Ok(Self::Entry),
"h-cite" | "cite" => Ok(Self::Cite),
"h-card" | "card" => Ok(Self::Card),
"h-event" | "event" => Ok(Self::Event),
"h-product" | "product" => Ok(Self::Product),
"h-feed" | "feed" => Ok(Self::Feed),
"h-geo" | "geo" => Ok(Self::Geo),
"h-adr" | "adr" => Ok(Self::Adr),
"h-resume" | "resume" => Ok(Self::Resume),
"h-review" | "review" => Ok(Self::Review),
"h-recipe" | "recipe" => Ok(Self::Recipe),
_ => Err(Error::NotKnownClass(s.to_string())),
}
}
}
impl std::fmt::Display for KnownClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
KnownClass::Entry => "h-entry",
KnownClass::Cite => "h-cite",
KnownClass::Card => "h-card",
KnownClass::Feed => "h-feed",
KnownClass::Event => "h-event",
KnownClass::Product => "h-product",
KnownClass::Adr => "h-adr",
KnownClass::Geo => "h-geo",
KnownClass::Resume => "h-resume",
KnownClass::Review => "h-review",
KnownClass::Recipe => "h-recipe",
})
}
}
#[derive(Debug, Clone, Eq)]
pub enum Class {
Known(KnownClass),
Custom(String),
}
impl PartialOrd for Class {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_string().partial_cmp(&other.to_string())
}
}
impl PartialEq for Class {
fn eq(&self, other: &Self) -> bool {
self.to_string().eq(&other.to_string())
}
}
impl FromStr for Class {
type Err = std::convert::Infallible;
fn from_str(class_str: &str) -> Result<Self, Self::Err> {
KnownClass::from_str(class_str)
.or_else(|_| KnownClass::from_str(&class_str.replace("h-", "")))
.map(Class::Known)
.or_else(|_| Ok(Self::Custom(class_str.trim_start_matches("h-").to_string())))
}
}
impl std::fmt::Display for Class {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Known(class) => f.write_fmt(format_args!("{}", class)),
Self::Custom(class) => f.write_fmt(format_args!("h-{}", class)),
}
}
}
impl serde::Serialize for Class {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
struct ClassVisitor;
impl serde::de::Visitor<'_> for ClassVisitor {
type Value = Class;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(
"a string that follows Microformats class conventions of being prefixed by 'h-'",
)
}
fn visit_str<E>(self, class_str: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Class::from_str(class_str).map_err(|e| E::custom(e.to_string()))
}
}
impl<'de> serde::Deserialize<'de> for Class {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(ClassVisitor)
}
}
impl Class {
pub fn is_recognized(&self) -> bool {
!matches!(self, Self::Custom(_))
}
}
impl From<KnownClass> for Class {
fn from(kc: KnownClass) -> Self {
Self::Known(kc)
}
}