preset_env_base/
version.rs1use std::{cmp, cmp::Ordering, fmt, str::FromStr};
4
5use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize};
6use tracing::warn;
7
8use crate::Versions;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
15pub struct Version {
16 pub major: u32,
18 pub minor: u32,
20 pub patch: u32,
22}
23
24impl FromStr for Version {
25 type Err = ();
26
27 fn from_str(v: &str) -> Result<Self, Self::Err> {
28 if !v.contains('.') {
29 return Ok(Version {
30 major: v.parse().map_err(|err| {
31 warn!("failed to parse `{}` as a version: {}", v, err);
32 })?,
33 minor: 0,
34 patch: 0,
35 });
36 }
37
38 if v.split('.').count() == 2 {
39 let mut s = v.split('.');
40 return Ok(Version {
41 major: s.next().unwrap().parse().unwrap(),
42 minor: s.next().unwrap().parse().unwrap(),
43 patch: 0,
44 });
45 }
46
47 let v = v.parse::<semver::Version>().map_err(|err| {
48 warn!("failed to parse `{}` as a version: {}", v, err);
49 })?;
50
51 Ok(Version {
52 major: v.major as _,
53 minor: v.minor as _,
54 patch: v.patch as _,
55 })
56 }
57}
58
59impl cmp::PartialOrd for Version {
60 fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
61 Some(self.cmp(other))
62 }
63}
64
65impl cmp::Ord for Version {
66 fn cmp(&self, other: &Version) -> Ordering {
67 match self.major.cmp(&other.major) {
68 Ordering::Equal => {}
69 r => return r,
70 }
71
72 match self.minor.cmp(&other.minor) {
73 Ordering::Equal => {}
74 r => return r,
75 }
76
77 match self.patch.cmp(&other.patch) {
78 Ordering::Equal => {}
79 r => return r,
80 }
81
82 Ordering::Equal
83 }
84}
85
86struct SerdeVisitor;
87
88impl<'de> Visitor<'de> for SerdeVisitor {
89 type Value = Version;
90
91 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
92 formatter.write_str("a browser version")
93 }
94
95 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
96 where
97 E: de::Error,
98 {
99 Ok(Version {
100 major: v as _,
101 minor: 0,
102 patch: 0,
103 })
104 }
105
106 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
107 where
108 E: de::Error,
109 {
110 Ok(Version {
111 major: v as _,
112 minor: 0,
113 patch: 0,
114 })
115 }
116
117 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
118 where
119 E: de::Error,
120 {
121 Ok(Version {
122 major: v.floor() as _,
123 minor: v.fract() as _,
124 patch: 0,
125 })
126 }
127
128 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
129 where
130 E: de::Error,
131 {
132 v.parse()
133 .map_err(|_| de::Error::invalid_type(de::Unexpected::Str(v), &self))
134 }
135
136 #[inline]
137 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
138 where
139 E: de::Error,
140 {
141 self.visit_str(v)
142 }
143
144 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
145 where
146 E: de::Error,
147 {
148 self.visit_str(&v)
149 }
150}
151
152impl<'de> Deserialize<'de> for Version {
153 fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
154 where
155 D: Deserializer<'de>,
156 {
157 deserializer.deserialize_any(SerdeVisitor)
158 }
159}
160
161pub fn should_enable(target: &Versions, feature: &Versions, default: bool) -> bool {
162 if target
163 .iter()
164 .zip(feature.iter())
165 .all(|((_, target_version), (_, f))| target_version.is_none() && f.is_none())
166 {
167 return default;
168 }
169
170 target.iter().zip(feature.iter()).any(
171 |((target_name, maybe_target_version), (_, maybe_feature_version))| {
172 maybe_target_version.map_or(false, |target_version| {
173 let feature_or_fallback_version =
174 maybe_feature_version.or_else(|| match target_name {
175 "android" => feature.chrome,
179 _ => None,
180 });
181
182 feature_or_fallback_version.map_or(true, |v| v > target_version)
183 })
184 },
185 )
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::BrowserData;
192
193 #[test]
194 fn should_enable_android_falls_back_to_chrome() {
195 assert!(!should_enable(
196 &BrowserData {
197 android: Some("51.0.0".parse().unwrap()),
198 ..Default::default()
199 },
200 &BrowserData {
201 chrome: Some("51.0.0".parse().unwrap()),
202 ..Default::default()
203 },
204 false
205 ));
206 }
207}