bambu_rs/core/
firmware.rs1use std::cmp::Ordering;
10use std::fmt;
11
12#[derive(Debug, Clone)]
18pub struct FirmwareVersion {
19 raw: String,
21 components: Vec<u32>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct ParseFirmwareError {
28 input: String,
29}
30
31impl fmt::Display for ParseFirmwareError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write!(f, "invalid firmware version: {:?}", self.input)
34 }
35}
36
37impl std::error::Error for ParseFirmwareError {}
38
39impl FirmwareVersion {
40 pub fn parse(s: &str) -> Result<Self, ParseFirmwareError> {
43 let raw = s.trim();
44 let err = || ParseFirmwareError {
45 input: s.to_string(),
46 };
47 let body = raw.strip_prefix(['v', 'V']).unwrap_or(raw);
48 if body.is_empty() {
49 return Err(err());
50 }
51 let mut components = body
52 .split('.')
53 .map(|part| part.parse::<u32>().map_err(|_| err()))
54 .collect::<Result<Vec<u32>, _>>()?;
55 while components.last() == Some(&0) {
57 components.pop();
58 }
59 Ok(Self {
60 raw: raw.to_string(),
61 components,
62 })
63 }
64
65 pub fn components(&self) -> &[u32] {
67 &self.components
68 }
69}
70
71impl PartialEq for FirmwareVersion {
72 fn eq(&self, other: &Self) -> bool {
73 self.components == other.components
74 }
75}
76
77impl Eq for FirmwareVersion {}
78
79impl PartialOrd for FirmwareVersion {
80 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
81 Some(self.cmp(other))
82 }
83}
84
85impl Ord for FirmwareVersion {
86 fn cmp(&self, other: &Self) -> Ordering {
87 self.components.cmp(&other.components)
90 }
91}
92
93impl std::hash::Hash for FirmwareVersion {
94 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
95 self.components.hash(state);
96 }
97}
98
99impl serde::Serialize for FirmwareVersion {
100 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
103 serializer.serialize_str(&self.raw)
104 }
105}
106
107impl fmt::Display for FirmwareVersion {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 f.write_str(&self.raw)
110 }
111}
112
113impl std::str::FromStr for FirmwareVersion {
114 type Err = ParseFirmwareError;
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 Self::parse(s)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 fn fw(s: &str) -> FirmwareVersion {
125 FirmwareVersion::parse(s).expect("should parse")
126 }
127
128 #[test]
129 fn parses_four_component_version() {
130 let v = fw("01.05.00.00");
131 assert_eq!(v.components(), &[1, 5]); }
133
134 #[test]
135 fn accepts_optional_v_prefix() {
136 assert_eq!(fw("v01.05.00"), fw("01.05.00"));
137 assert_eq!(fw("V01.05.00"), fw("01.05.00"));
138 }
139
140 #[test]
141 fn trailing_zero_components_are_equal() {
142 assert_eq!(fw("01.05"), fw("01.05.00.00"));
143 assert_eq!(fw("01.05.00"), fw("01.05.00.00"));
144 }
145
146 #[test]
147 fn orders_by_numeric_component() {
148 assert!(fw("01.04.99.00") < fw("01.05.00.00"));
149 assert!(fw("01.05.00.00") < fw("01.05.01.00"));
150 assert!(fw("01.05.00.00") < fw("01.05.00.02"));
151 assert!(fw("01.05.01") > fw("01.05.00.02"));
152 }
153
154 #[test]
155 fn developer_mode_threshold_comparison() {
156 let threshold = fw("01.05.00");
158 assert!(fw("01.04.00.00") < threshold);
159 assert!(fw("01.05.00.00") >= threshold);
160 assert!(fw("01.06.02.00") >= threshold);
161 }
162
163 #[test]
164 fn display_round_trips_original_string() {
165 assert_eq!(fw("01.05.00.00").to_string(), "01.05.00.00");
166 }
167
168 #[test]
169 fn rejects_non_numeric() {
170 assert!(FirmwareVersion::parse("not-a-version").is_err());
171 assert!(FirmwareVersion::parse("01.x.00").is_err());
172 assert!(FirmwareVersion::parse("").is_err());
173 }
174}