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
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_browserversion_from_string() {
104 assert_eq!(BrowserVersion::from_string("29").unwrap(), BrowserVersion(29, 0));
105 assert_eq!(BrowserVersion::from_string("29.1").unwrap(), BrowserVersion(29, 1));
106 assert_eq!(BrowserVersion::from_string("29.12").unwrap(), BrowserVersion(29, 12));
107 assert_eq!(BrowserVersion::from_string("120").unwrap(), BrowserVersion(120, 0));
108 assert_eq!(BrowserVersion::from_string("10.0").unwrap(), BrowserVersion(10, 0));
109 assert_eq!(BrowserVersion::from_string("1.99").unwrap(), BrowserVersion(1, 99));
110 }
111
112 #[test]
113 fn test_browserversion_from_string_errors() {
114 assert!(BrowserVersion::from_string("15.2-15.3").is_err());
115 assert!(BrowserVersion::from_string("").is_err());
116 assert!(BrowserVersion::from_string("not_a_number").is_err());
117 }
118
119 #[test]
120 fn test_browserversion_from_string_as_range() {
121 assert_eq!(
122 BrowserVersion::from_string_as_range("15.2-15.3").unwrap(),
123 (BrowserVersion(15, 2), BrowserVersion(15, 3))
124 );
125 assert_eq!(
126 BrowserVersion::from_string_as_range("110.5-108.0").unwrap(),
127 (BrowserVersion(108, 0), BrowserVersion(110, 5))
128 );
129 assert!(BrowserVersion::from_string_as_range("").is_err());
130 assert!(BrowserVersion::from_string_as_range("not_a_number").is_err());
131 }
132
133 #[test]
134 fn test_browserversion_comparison() {
135 assert!(BrowserVersion(29, 0) > BrowserVersion(28, 0));
136 assert!(BrowserVersion(29, 0) < BrowserVersion(30, 0));
137 assert!(BrowserVersion(29, 1) > BrowserVersion(29, 0));
138 assert!(BrowserVersion(29, 1) < BrowserVersion(30, 1));
139 assert!(BrowserVersion(29, 1) < BrowserVersion(29, 2));
140 assert!(BrowserVersion(0, 0) < BrowserVersion(0, 1));
141 assert!(BrowserVersion(0, 1) < BrowserVersion(0, 2));
142 assert!(BrowserVersion(0, 2) > BrowserVersion(0, 1));
143 }
144
145 #[test]
146 fn test_browserversion_in_range() {
147 assert!(BrowserVersion(0, 2).in_range((BrowserVersion(0, 0), BrowserVersion(1, 0))));
148 }
149}