1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//! Defines the [`Tag`] type, which represents a [`crate::post::Post`] tag.

use crate::url::*;
use serde::de::{Deserialize, Deserializer};
use std::hash::{Hash, Hasher};

/// Represents a [`crate::post::Post`] tag. On parsing a post from YAML, only the
/// `name` field is parsed while the `url` field is left empty. The URL field
/// must be filled in later based on the `index_base_url` and the tag name.
#[derive(Clone, Debug)]
pub struct Tag {
    /// The tag's name. This should be slugified so e.g., `macOS` and `MacOS`
    /// resolve to the same value, and also so the field can be dropped into a
    /// [`crate::url::UrlBuf`].
    pub name: String,

    /// The URL for the tag's first index page. Given an `index_base_url`, this
    /// should look something like `{index_base_url}/{tag_name}/index.html`.
    pub url: UrlBuf,
}

impl<'de> Deserialize<'de> for Tag {
    /// Implements [`serde::de::Deserialize`] for [`Tag`]. This expects a string
    /// and will deserialize it into a [`Tag`] whose `name` is the sluggified
    /// input string and whose `url` field is left empty.
    fn deserialize<D>(deserializer: D) -> std::result::Result<Tag, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(Tag {
            name: slug::slugify(&String::deserialize(deserializer)?),
            url: UrlBuf::new(),
        })
    }
}

impl Hash for Tag {
    /// Implements [`Hash`] for [`Tag`] by delegating directly to the `name`
    /// field.
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state)
    }
}

impl PartialEq for Tag {
    /// Implements [`PartialEq`] and [`Eq`] for [`Tag`] by delegating directly to
    /// the `name` field.
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}
impl Eq for Tag {}