Skip to main content

awp_types/
version.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5/// AWP protocol version with major and minor components.
6///
7/// Versions are compatible when their major versions match.
8///
9/// # Example
10///
11/// ```
12/// use awp_types::AwpVersion;
13///
14/// let v1 = AwpVersion { major: 1, minor: 0 };
15/// let v1_1 = AwpVersion { major: 1, minor: 1 };
16/// assert!(v1.is_compatible(&v1_1));
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct AwpVersion {
20    pub major: u32,
21    pub minor: u32,
22}
23
24/// The current AWP protocol version.
25pub const CURRENT_VERSION: AwpVersion = AwpVersion { major: 1, minor: 0 };
26
27impl AwpVersion {
28    /// Returns `true` if this version is compatible with `other`.
29    ///
30    /// Compatibility is determined by matching major versions.
31    pub fn is_compatible(&self, other: &AwpVersion) -> bool {
32        self.major == other.major
33    }
34}
35
36impl fmt::Display for AwpVersion {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(f, "{}.{}", self.major, self.minor)
39    }
40}
41
42/// Error returned when parsing an [`AwpVersion`] from a string fails.
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct ParseVersionError(pub String);
45
46impl fmt::Display for ParseVersionError {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "invalid AWP version: {}", self.0)
49    }
50}
51
52impl std::error::Error for ParseVersionError {}
53
54impl FromStr for AwpVersion {
55    type Err = ParseVersionError;
56
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        let parts: Vec<&str> = s.split('.').collect();
59        if parts.len() != 2 {
60            return Err(ParseVersionError(format!("expected format 'major.minor', got '{s}'")));
61        }
62        let major = parts[0]
63            .parse::<u32>()
64            .map_err(|e| ParseVersionError(format!("invalid major version: {e}")))?;
65        let minor = parts[1]
66            .parse::<u32>()
67            .map_err(|e| ParseVersionError(format!("invalid minor version: {e}")))?;
68        Ok(AwpVersion { major, minor })
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_current_version() {
78        assert_eq!(CURRENT_VERSION.major, 1);
79        assert_eq!(CURRENT_VERSION.minor, 0);
80    }
81
82    #[test]
83    fn test_display() {
84        let v = AwpVersion { major: 1, minor: 0 };
85        assert_eq!(v.to_string(), "1.0");
86
87        let v2 = AwpVersion { major: 2, minor: 3 };
88        assert_eq!(v2.to_string(), "2.3");
89    }
90
91    #[test]
92    fn test_from_str_valid() {
93        let v: AwpVersion = "1.0".parse().unwrap();
94        assert_eq!(v, AwpVersion { major: 1, minor: 0 });
95
96        let v2: AwpVersion = "2.3".parse().unwrap();
97        assert_eq!(v2, AwpVersion { major: 2, minor: 3 });
98    }
99
100    #[test]
101    fn test_from_str_round_trip() {
102        let v = AwpVersion { major: 1, minor: 0 };
103        let s = v.to_string();
104        let parsed: AwpVersion = s.parse().unwrap();
105        assert_eq!(v, parsed);
106    }
107
108    #[test]
109    fn test_from_str_invalid() {
110        assert!("abc".parse::<AwpVersion>().is_err());
111        assert!("1".parse::<AwpVersion>().is_err());
112        assert!("1.2.3".parse::<AwpVersion>().is_err());
113        assert!("a.b".parse::<AwpVersion>().is_err());
114    }
115
116    #[test]
117    fn test_is_compatible_same_major() {
118        let a = AwpVersion { major: 1, minor: 0 };
119        let b = AwpVersion { major: 1, minor: 5 };
120        assert!(a.is_compatible(&b));
121    }
122
123    #[test]
124    fn test_is_compatible_different_major() {
125        let a = AwpVersion { major: 1, minor: 0 };
126        let b = AwpVersion { major: 2, minor: 0 };
127        assert!(!a.is_compatible(&b));
128    }
129}