1use crate::error::Error;
2use crate::prelude::RepoResult;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::borrow::{Borrow, Cow};
5use std::collections::HashMap;
6use std::fmt::{Debug, Display, Formatter};
7use std::hash::{Hash, Hasher};
8use std::ops::Deref;
9use std::str::FromStr;
10use toml::Value;
11
12#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
14pub struct TagRef<'a> {
15    name: Cow<'a, str>,
17    #[serde(rename = "type")]
19    tag_type: TagType,
20}
21
22impl<'a> TagRef<'a> {
23    pub fn new<N>(name: N, tag_type: TagType) -> Self
24    where
25        N: Into<Cow<'a, str>>,
26    {
27        TagRef {
28            name: name.into(),
29            tag_type,
30        }
31    }
32
33    pub fn from_cow_str<S>(name: S) -> Self
34    where
35        S: Into<Cow<'a, str>>,
36    {
37        let tag: Cow<'a, str> = name.into();
38        let (tag_type, name) = tag
39            .split_once(':')
40            .and_then(|(tag_type, tag_name)| {
41                let tag_type = TagType::from_str(tag_type);
43                match tag_type {
44                    Ok(tag_type) => Some((tag_type, Cow::Owned(tag_name.trim().to_string()))),
46                    Err(_) => None,
48                }
49            })
50            .unwrap_or((TagType::Unknown, tag));
51        TagRef { name, tag_type }
52    }
53
54    pub fn name(&self) -> &str {
55        self.name.deref()
56    }
57
58    pub fn tag_type(&self) -> &TagType {
59        &self.tag_type
60    }
61
62    fn full_clone(&self) -> TagRef<'static> {
63        TagRef {
64            name: Cow::Owned(self.name.to_string()),
65            tag_type: self.tag_type.clone(),
66        }
67    }
68}
69
70impl<'a> TagRef<'a> {
71    pub fn into_full(self, parents: Vec<TagString>) -> Tag {
72        Tag {
73            inner: self.full_clone(),
74            names: Default::default(),
75            parents,
76            children: Vec::new(),
77        }
78    }
79}
80
81impl<'a> Display for TagRef<'a> {
82    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83        match &self.tag_type {
84            TagType::Unknown => write!(f, "{}", self.name()),
85            ty => write!(f, "{}:{}", ty, self.name()),
86        }
87    }
88}
89
90impl<'tag> Borrow<TagRef<'tag>> for Tag {
91    fn borrow(&self) -> &TagRef<'tag> {
92        &self.inner
93    }
94}
95
96impl<'tag> Borrow<TagRef<'tag>> for TagString {
97    fn borrow(&self) -> &TagRef<'tag> {
98        &self.0
99    }
100}
101
102#[derive(Debug, Eq, Clone)]
108pub struct TagString(pub(crate) TagRef<'static>);
109
110impl TagString {
111    pub fn new(name: String, tag_type: TagType) -> Self {
112        Self(TagRef::new(name, tag_type))
113    }
114
115    pub fn name(&self) -> &str {
116        self.0.name()
117    }
118
119    pub fn tag_type(&self) -> &TagType {
120        self.0.tag_type()
121    }
122
123    pub(crate) fn resolve(
124        &mut self,
125        tags: &HashMap<String, HashMap<TagType, Tag>>,
126    ) -> RepoResult<()> {
127        if let TagType::Unknown = self.tag_type {
128            if let Some(tags) = tags.get(self.name()) {
129                if tags.len() > 1 {
130                    return Err(Error::RepoTagDuplicated(self.full_clone()));
131                }
132
133                let actual_type = tags.values().next().unwrap().tag_type().clone();
134                self.0.tag_type = actual_type;
135            }
136        }
137
138        Ok(())
139    }
140}
141
142impl Deref for TagString {
143    type Target = TagRef<'static>;
144
145    fn deref(&self) -> &Self::Target {
146        &self.0
147    }
148}
149
150impl Serialize for TagString {
151    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
152    where
153        S: Serializer,
154    {
155        Value::String(format!("{}", self.0)).serialize(serializer)
156    }
157}
158
159impl<'de> Deserialize<'de> for TagString {
160    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161    where
162        D: Deserializer<'de>,
163    {
164        use serde::de::Error;
165
166        let value = Value::deserialize(deserializer)?;
167        if let Value::String(tag) = value {
168            let tag = TagRef::from_cow_str(tag);
169            Ok(Self(tag))
170        } else {
171            Err(Error::custom("Tag must be a string"))
172        }
173    }
174}
175
176impl Display for TagString {
177    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
178        Display::fmt(&self.0, f)
179    }
180}
181
182impl Hash for TagString {
183    fn hash<H: Hasher>(&self, state: &mut H) {
184        self.0.hash(state)
185    }
186}
187
188impl PartialEq for TagString {
189    fn eq(&self, other: &Self) -> bool {
190        self.0 == other.0
191    }
192}
193
194impl From<TagRef<'static>> for TagString {
195    fn from(value: TagRef<'static>) -> Self {
196        Self(value)
197    }
198}
199
200#[derive(Serialize, Deserialize, Debug, Eq)]
201#[serde(deny_unknown_fields)]
202pub struct Tag {
203    #[serde(flatten)]
204    inner: TagRef<'static>,
205    #[serde(default)]
207    names: HashMap<String, String>,
208    #[serde(default)]
210    #[serde(rename = "included-by")]
211    parents: Vec<TagString>,
212    #[serde(default)]
214    #[serde(rename = "includes")]
215    children: Vec<TagString>,
217}
218
219#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
220#[serde(rename_all = "lowercase")]
221pub enum TagType {
222    Artist,
223    Group,
224    Animation,
225    Series,
226    Project,
227    Radio,
228    Game,
229    Organization,
230    Category,
231    Unknown,
232}
233
234impl AsRef<str> for TagType {
235    fn as_ref(&self) -> &str {
236        match self {
237            TagType::Artist => "artist",
238            TagType::Group => "group",
239            TagType::Animation => "animation",
240            TagType::Series => "series",
241            TagType::Project => "project",
242            TagType::Radio => "radio",
243            TagType::Game => "game",
244            TagType::Organization => "organization",
245            TagType::Unknown => "unknown",
246            TagType::Category => "category",
247        }
248    }
249}
250
251impl FromStr for TagType {
252    type Err = Error;
253
254    fn from_str(s: &str) -> Result<Self, Self::Err> {
255        Ok(match s {
256            "artist" => Self::Artist,
257            "group" => Self::Group,
258            "animation" => Self::Animation,
259            "series" => Self::Series,
260            "project" => Self::Project,
261            "radio" => Self::Radio,
262            "game" => Self::Game,
263            "organization" => Self::Organization,
264            "category" => Self::Category,
265            "unknown" => Self::Unknown,
266            _ => return Err(Error::RepoTagUnknownType(s.to_string())),
267        })
268    }
269}
270
271impl Display for TagType {
272    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
273        f.write_str(self.as_ref())
274    }
275}
276
277impl Hash for Tag {
278    fn hash<H: Hasher>(&self, state: &mut H) {
279        self.inner.hash(state)
280    }
281}
282
283impl PartialEq for Tag {
284    fn eq(&self, other: &Self) -> bool {
285        self.inner == other.inner
286    }
287}
288
289impl Display for Tag {
290    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
291        Display::fmt(&self.inner, f)
292    }
293}
294
295impl Tag {
296    pub fn name(&self) -> &str {
297        self.inner.name()
298    }
299
300    pub fn names(&self) -> &HashMap<String, String> {
301        &self.names
302    }
303
304    pub fn tag_type(&self) -> &TagType {
305        self.inner.tag_type()
306    }
307
308    pub fn parents(&self) -> &[TagString] {
309        &self.parents
310    }
311
312    pub fn simple_children<'me, 'tag>(&'me self) -> impl Iterator<Item = &'me TagRef<'tag>>
313    where
314        'tag: 'me,
315    {
316        self.children.iter().map(|i| &i.0)
317    }
318
319    pub fn get_owned_ref(&self) -> TagRef<'static> {
320        TagRef {
321            name: self.inner.name.clone(),
322            tag_type: self.inner.tag_type.clone(),
323        }
324    }
325}
326
327impl AsRef<TagRef<'static>> for Tag {
328    fn as_ref(&self) -> &TagRef<'static> {
329        &self.inner
330    }
331}
332
333#[derive(Serialize, Deserialize)]
334pub struct Tags {
335    tag: Vec<Tag>,
336}
337
338impl Tags {
339    pub fn into_inner(self) -> Vec<Tag> {
340        self.tag
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use crate::models::{TagRef, TagString, TagType};
347
348    #[test]
349    fn test_tag_string_serialize() {
350        let tag = TagString(TagRef::new("Test", TagType::Artist));
351        assert_eq!(tag.to_string(), "artist:Test".to_string());
352    }
353
354    #[test]
355    fn test_tag_string_deserialize() {
356        #[derive(serde::Deserialize)]
357        struct TestStruct {
358            tags: Vec<TagString>,
359        }
360
361        let TestStruct { tags } = toml::from_str(
362            r#"
363tags = [
364  "artist:123",
365  "group:456",
366  "implicit-tag-type",
367  "implicit:tag-type with :",
368]
369"#,
370        )
371        .unwrap();
372        assert_eq!(tags.len(), 4);
373
374        assert_eq!(tags[0].name, "123");
375        assert_eq!(tags[0].tag_type, TagType::Artist);
376
377        assert_eq!(tags[1].name, "456");
378        assert_eq!(tags[1].tag_type, TagType::Group);
379
380        assert_eq!(tags[2].name, "implicit-tag-type");
381        assert_eq!(tags[2].tag_type, TagType::Unknown);
382
383        assert_eq!(tags[3].name, "implicit:tag-type with :");
384        assert_eq!(tags[3].tag_type, TagType::Unknown);
385    }
386}