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
use lazy_static::lazy_static;
use regex::Regex;
use std::str::FromStr;
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};

lazy_static! {
    static ref VERSION_RE: Regex = Regex::new("([0-9]+).([0-9]+).([0-9]+)").unwrap();
}

/// Version in a SemVer **like** way.
///
/// ## Example:
/// ```
/// # use fast_version_core::version::Version;
/// use std::str::FromStr;
///
/// const version_str: &'static str = "1.2.3";
///
/// let version = Version::from_str(version_str).unwrap();
/// assert_eq!(version.major, 1);
/// assert_eq!(version.minor, 2);
/// assert_eq!(version.patch, 3);
///
/// let version_out_str = version.to_string();
/// assert_eq!(&version_out_str, version_str);
/// ```
///
/// This crate is heavily optimized to allow compile-time evaluation:
/// ```
/// # use fast_version_core::version::Version;
/// const VERSION: Version = Version::new(1, 2, 3);
///
/// assert_eq!(VERSION.major, 1);
/// assert_eq!(VERSION.minor, 2);
/// assert_eq!(VERSION.patch, 3);
/// ```
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Version {
    pub major: u64,
    pub minor: u64,
    pub patch: u64,
}


impl Version {
    /// Create a new version from major, minor and patch.
    /// ```
    /// # use fast_version_core::version::Version;
    ///
    /// let version = Version::new(1, 2, 3);
    /// 
    /// assert_eq!(version.major, 1);
    /// assert_eq!(version.minor, 2);
    /// assert_eq!(version.patch, 3);
    /// ```
    #[inline]
    pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
        Version {
            major,
            minor,
            patch,
        }
    }
}

#[derive(Error, Debug)]
pub enum VersionParseError {
    #[error("Format of version string is wrong")]
    FormatWrong,
    #[error("Parsing error in major")]
    MajorParseError,
    #[error("Major element was not found")]
    MajorNotFound,
    #[error("Minor Parse Error")]
    MinorParseError,
    #[error("Minor element was not found")]
    MinorNotFound,
    #[error("Patch Parse Error")]
    PatchParseError,
    #[error("Patch element was not found")]
    PatchNotFound,
}

impl FromStr for Version {
    type Err = VersionParseError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let match_result = VERSION_RE
            .captures(s)
            .ok_or(VersionParseError::FormatWrong)?;
        let major_str = match_result
            .get(1)
            .ok_or(VersionParseError::MajorNotFound)?
            .as_str();
        let minor_str = match_result
            .get(2)
            .ok_or(VersionParseError::MinorNotFound)?
            .as_str();
        let patch_str = match_result
            .get(3)
            .ok_or(VersionParseError::PatchNotFound)?
            .as_str();
        let major_num = u64::from_str(major_str).map_err(|_| VersionParseError::MajorParseError)?;
        let minor_num = u64::from_str(minor_str).map_err(|_| VersionParseError::MinorParseError)?;
        let patch_num = u64::from_str(patch_str).map_err(|_| VersionParseError::PatchParseError)?;
        let ret = Version::new(major_num, minor_num, patch_num);
        Ok(ret)
    }
}

#[cfg(feature = "alloc")]
impl ToString for Version {
    fn to_string(&self) -> String {
        format!("{}.{}.{}", self.major, self.minor, self.patch)
    }
}