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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Dynasty reader's tag.

mod listing;
mod status;

pub mod sample;

use std::fmt;

use serde::{Deserialize, Serialize};

pub use listing::{ChapterTag, TagListing};
pub use status::TagStatus;

/// Dynasty reader's tag.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct Tag {
    /// The tag's kind.
    #[serde(rename(deserialize = "type"))]
    pub kind: TagKind,
    /// The tag's title.
    #[serde(rename(deserialize = "name"))]
    pub title: String,
    /// The tag's permalink.
    pub(crate) permalink: String,
}

impl Tag {
    /// Get the tag's absolute path from <https://dynasty-scans.com/>.
    pub fn path(&self) -> String {
        format!("{}/{}", self.kind.path(), self.permalink)
    }
}

/// Dynasty reader's tag kinds.
///
/// The tag can be parsed from a [`&str`](str).
///
/// | [`&str`](str) | [`TagKind`]            |
/// |---------------|------------------------|
/// | "anthologies" | [`TagKind::Anthology`] |
/// | "authors"     | [`TagKind::Author`]    |
/// | "doujins"     | [`TagKind::Doujin`]    |
/// | "tags"        | [`TagKind::General`]   |
/// | "issues"      | [`TagKind::Issue`]     |
/// | "pairings"    | [`TagKind::Pairing`]   |
/// | "scanlators"  | [`TagKind::Scanlator`] |
/// | "series"      | [`TagKind::Series`]    |
///
/// # Examples
///
/// ```
/// # use anyhow::Result;
/// # use dynasty_api::tag::TagKind;
/// # fn main() -> Result<()> {
/// assert_eq!(TagKind::Anthology, "anthologies".parse::<TagKind>()?);
/// assert_eq!(TagKind::Author, "authors".parse::<TagKind>()?);
/// assert_eq!(TagKind::Doujin, "doujins".parse::<TagKind>()?);
/// assert_eq!(TagKind::General, "tags".parse::<TagKind>()?);
/// assert_eq!(TagKind::Issue, "issues".parse::<TagKind>()?);
/// assert_eq!(TagKind::Pairing, "pairings".parse::<TagKind>()?);
/// assert_eq!(TagKind::Scanlator, "scanlators".parse::<TagKind>()?);
/// assert_eq!(TagKind::Series, "series".parse::<TagKind>()?);
///
/// // Error example
/// let invalid = "aNYtHinGELsE".parse::<TagKind>();
/// assert!(invalid.is_err());
/// let error = invalid.unwrap_err();
/// assert_eq!(error.to_string(), "`aNYtHinGELsE` is not a valid TagKind!");
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum TagKind {
    /// <https://dynasty-scans.com/anthologies>
    Anthology,
    /// <https://dynasty-scans.com/authors>
    Author,
    /// <https://dynasty-scans.com/doujins>
    Doujin,
    /// <https://dynasty-scans.com/tags>
    General,
    /// <https://dynasty-scans.com/issues>
    Issue,
    #[doc(hidden)]
    Magazine,
    /// <https://dynasty-scans.com/pairings>
    Pairing,
    /// <https://dynasty-scans.com/scanlators>
    Scanlator,
    /// <https://dynasty-scans.com/series>
    Series,
    #[doc(hidden)]
    Status,
}

impl TagKind {
    pub(crate) fn path(self) -> &'static str {
        match self {
            TagKind::Anthology => "anthologies",
            TagKind::Author => "authors",
            TagKind::Doujin => "doujins",
            TagKind::General => "tags",
            TagKind::Issue => "issues",
            TagKind::Pairing => "pairings",
            TagKind::Scanlator => "scanlators",
            TagKind::Series => "series",
            _ => unimplemented!("there is no specific path for {}", stringify!(self)),
        }
    }
}

impl fmt::Display for TagKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name: String = {
            let mut path = self.path().chars();
            if let Some(next) = path.next() {
                next.to_uppercase().chain(path).collect()
            } else {
                String::new()
            }
        };

        write!(f, "{}", name)
    }
}

impl std::str::FromStr for TagKind {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "anthologies" => Ok(TagKind::Anthology),
            "authors" => Ok(TagKind::Author),
            "doujins" => Ok(TagKind::Doujin),
            "tags" => Ok(TagKind::General),
            "issues" => Ok(TagKind::Issue),
            "pairings" => Ok(TagKind::Pairing),
            "scanlators" => Ok(TagKind::Scanlator),
            "series" => Ok(TagKind::Series),
            _ => Err(anyhow::anyhow!("`{}` is not a valid TagKind!", s)),
        }
    }
}