1#[cfg(feature = "browserslist")]
2use browserslist::Distrib;
3use core::num::ParseIntError;
4
5#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
6pub struct BrowserVersion(pub u16, pub u16);
7
8impl BrowserVersion {
9 pub fn from_string(version: &str) -> Result<Self, ParseIntError> {
10 let parts: Vec<&str> = version.split('.').collect();
11 if parts.is_empty() {
12 return Ok(BrowserVersion(version.parse()?, 0));
13 }
14 let major: u16 = parts[0].parse()?;
15 let minor: u16 = if parts.len() > 1 { parts[1].parse()? } else { 0 };
16 Ok(Self(major, minor))
17 }
18
19 pub fn from_string_as_range(version: &str) -> Result<(Self, Self), ParseIntError> {
20 if version == "all" {
21 return Ok((BrowserVersion(0, 0), BrowserVersion(u16::MAX, u16::MAX)));
22 }
23 let parts: Vec<&str> = version.split('-').collect();
24 if parts.is_empty() || parts.len() < 2 {
25 let res = Self::from_string(version)?;
26 return Ok((res, res));
27 }
28 let first = Self::from_string(parts[0])?;
29 let second = Self::from_string(parts[1])?;
30 if first > second {
31 return Ok((second, first));
32 }
33 Ok((first, second))
34 }
35
36 pub fn in_range(self, range: (Self, Self)) -> bool {
37 range.0 < self && self < range.1
38 }
39}
40
41#[derive(Debug, Copy, Clone, PartialEq)]
42pub enum NamedBrowserVersion {
43 Chrome(BrowserVersion),
44 ChromeAndroid(BrowserVersion),
45 Edge(BrowserVersion),
46 Firefox(BrowserVersion),
47 FirefoxAndroid(BrowserVersion),
48 Safari(BrowserVersion),
49 SafariIos(BrowserVersion),
50 Samsung(BrowserVersion),
51 Opera(BrowserVersion),
52 OperaMini(BrowserVersion),
53 OperaMobile(BrowserVersion),
54 QQ(BrowserVersion),
55 QQAndroid(BrowserVersion),
56 UCBrowser(BrowserVersion),
57 KaiOS(BrowserVersion),
58 AndroidWebView(BrowserVersion),
59 InternetExplorer(BrowserVersion),
60}
61
62#[derive(Debug, Clone, PartialEq)]
63pub enum NamedBrowserVersionErr {
64 ParseIntError(ParseIntError),
65 UnknownNameError(String),
66}
67
68#[cfg(feature = "browserslist")]
69impl TryFrom<Distrib> for NamedBrowserVersion {
70 type Error = NamedBrowserVersionErr;
71
72 fn try_from(value: Distrib) -> Result<Self, Self::Error> {
73 let ver =
74 BrowserVersion::from_string_as_range(value.version()).map_err(NamedBrowserVersionErr::ParseIntError)?.0;
75 match value.name() {
76 "chrome" => Ok(Self::Chrome(ver)),
77 "chrome_android" | "and_chr" => Ok(Self::ChromeAndroid(ver)),
78 "edge" => Ok(Self::Edge(ver)),
79 "firefox" => Ok(Self::Firefox(ver)),
80 "firefox_android" | "and_ff" => Ok(Self::FirefoxAndroid(ver)),
81 "safari" => Ok(Self::Safari(ver)),
82 "safari_ios" | "ios_saf" => Ok(Self::SafariIos(ver)),
83 "samsung" => Ok(Self::Samsung(ver)),
84 "opera" => Ok(Self::Opera(ver)),
85 "op_mini" => Ok(Self::OperaMini(ver)),
86 "op_mob" => Ok(Self::OperaMobile(ver)),
87 "qq" => Ok(Self::QQ(ver)),
88 "and_qq" => Ok(Self::QQAndroid(ver)),
89 "kaios" => Ok(Self::KaiOS(ver)),
90 "android" => Ok(Self::AndroidWebView(ver)),
91 "and_uc" => Ok(Self::UCBrowser(ver)),
92 "ie" => Ok(Self::InternetExplorer(ver)),
93 name => Err(NamedBrowserVersionErr::UnknownNameError(name.to_owned())),
94 }
95 }
96}
97
98impl NamedBrowserVersion {
99 pub fn has_support_data(self) -> bool {
101 matches!(
102 self,
103 Self::Chrome(_)
104 | Self::ChromeAndroid(_)
105 | Self::Edge(_)
106 | Self::Firefox(_)
107 | Self::FirefoxAndroid(_)
108 | Self::Safari(_)
109 | Self::SafariIos(_)
110 )
111 }
112
113 #[cfg(feature = "browserslist")]
115 pub fn from_distribs(distribs: impl IntoIterator<Item = Distrib>) -> Vec<Self> {
116 distribs
117 .into_iter()
118 .filter_map(|distrib| Self::try_from(distrib).ok())
119 .filter(|browser| browser.has_support_data())
120 .collect()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_browserversion_from_string() {
130 assert_eq!(BrowserVersion::from_string("29").unwrap(), BrowserVersion(29, 0));
131 assert_eq!(BrowserVersion::from_string("29.1").unwrap(), BrowserVersion(29, 1));
132 assert_eq!(BrowserVersion::from_string("29.12").unwrap(), BrowserVersion(29, 12));
133 assert_eq!(BrowserVersion::from_string("120").unwrap(), BrowserVersion(120, 0));
134 assert_eq!(BrowserVersion::from_string("10.0").unwrap(), BrowserVersion(10, 0));
135 assert_eq!(BrowserVersion::from_string("1.99").unwrap(), BrowserVersion(1, 99));
136 }
137
138 #[test]
139 fn test_browserversion_from_string_errors() {
140 assert!(BrowserVersion::from_string("15.2-15.3").is_err());
141 assert!(BrowserVersion::from_string("").is_err());
142 assert!(BrowserVersion::from_string("not_a_number").is_err());
143 }
144
145 #[test]
146 fn test_browserversion_from_string_as_range() {
147 assert_eq!(
148 BrowserVersion::from_string_as_range("15.2-15.3").unwrap(),
149 (BrowserVersion(15, 2), BrowserVersion(15, 3))
150 );
151 assert_eq!(
152 BrowserVersion::from_string_as_range("110.5-108.0").unwrap(),
153 (BrowserVersion(108, 0), BrowserVersion(110, 5))
154 );
155 assert!(BrowserVersion::from_string_as_range("").is_err());
156 assert!(BrowserVersion::from_string_as_range("not_a_number").is_err());
157 }
158
159 #[test]
160 fn test_browserversion_comparison() {
161 assert!(BrowserVersion(29, 0) > BrowserVersion(28, 0));
162 assert!(BrowserVersion(29, 0) < BrowserVersion(30, 0));
163 assert!(BrowserVersion(29, 1) > BrowserVersion(29, 0));
164 assert!(BrowserVersion(29, 1) < BrowserVersion(30, 1));
165 assert!(BrowserVersion(29, 1) < BrowserVersion(29, 2));
166 assert!(BrowserVersion(0, 0) < BrowserVersion(0, 1));
167 assert!(BrowserVersion(0, 1) < BrowserVersion(0, 2));
168 assert!(BrowserVersion(0, 2) > BrowserVersion(0, 1));
169 }
170
171 #[test]
172 fn test_browserversion_in_range() {
173 assert!(BrowserVersion(0, 2).in_range((BrowserVersion(0, 0), BrowserVersion(1, 0))));
174 }
175}