tanoshi_lib/models/
version.rs

1use std::{fmt, marker::PhantomData, str::FromStr};
2
3use serde::{
4    de::{self, MapAccess, Visitor},
5    Deserialize, Deserializer, Serialize,
6};
7
8use crate::error::Error;
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
11pub struct Version {
12    pub major: usize,
13    pub minor: usize,
14    pub patch: usize,
15}
16
17impl FromStr for Version {
18    type Err = Error;
19
20    fn from_str(s: &str) -> Result<Self, Self::Err> {
21        let split = s.split('.').collect::<Vec<&str>>();
22        match split.as_slice() {
23            [major, minor, patch] => Ok(Version {
24                major: major.parse().map_err(|_| Error::InvalidVersion)?,
25                minor: minor.parse().map_err(|_| Error::InvalidVersion)?,
26                patch: patch.parse().map_err(|_| Error::InvalidVersion)?,
27            }),
28            _ => Err(Error::InvalidVersion),
29        }
30    }
31}
32
33impl std::fmt::Display for Version {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.write_fmt(format_args!("{}.{}.{}", self.major, self.minor, self.patch))
36    }
37}
38
39// https://serde.rs/string-or-struct.html
40#[allow(dead_code)]
41fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
42where
43    T: Deserialize<'de> + FromStr<Err = Error>,
44    D: Deserializer<'de>,
45{
46    // This is a Visitor that forwards string types to T's `FromStr` impl and
47    // forwards map types to T's `Deserialize` impl. The `PhantomData` is to
48    // keep the compiler from complaining about T being an unused generic type
49    // parameter. We need T in order to know the Value type for the Visitor
50    // impl.
51    struct StringOrStruct<T>(PhantomData<fn() -> T>);
52
53    impl<'de, T> Visitor<'de> for StringOrStruct<T>
54    where
55        T: Deserialize<'de> + FromStr<Err = Error>,
56    {
57        type Value = T;
58
59        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
60            formatter.write_str("string or map")
61        }
62
63        fn visit_str<E>(self, value: &str) -> Result<T, E>
64        where
65            E: de::Error,
66        {
67            Ok(FromStr::from_str(value).unwrap())
68        }
69
70        fn visit_map<M>(self, map: M) -> Result<T, M::Error>
71        where
72            M: MapAccess<'de>,
73        {
74            // `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
75            // into a `Deserializer`, allowing it to be used as the input to T's
76            // `Deserialize` implementation. T then deserializes itself using
77            // the entries from the map visitor.
78            Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
79        }
80    }
81
82    deserializer.deserialize_any(StringOrStruct(PhantomData))
83}