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}