css_feature_data/
browser_version.rs

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}