caniuse_serde/
Version.rs

1// This file is part of caniuse-serde. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/caniuse-serde/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
2// Copyright © 2017 The developers of caniuse-serde. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/caniuse-serde/master/COPYRIGHT.
3
4
5/// Version "3" and "3.0" are not considered equal; "3.0" is greater than "3".
6/// Opera and iOS Safari have hyphenation ranges of versions, eg "4.0-4.2". These are converted to a version matching the lower of the range, eg "4.0".
7/// Safari also has "TP" for its latest version, which is not stable across time and is converted to the VersionPart::TechnologyPreview, and Opera Mini just has "all"; it is effectively unversioned.
8#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
9pub struct Version(VersionPart, Vec<VersionPart>);
10
11impl<'de> Deserialize<'de> for Version
12{
13	/// Deserialize using Serde
14	#[inline(always)]
15	fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
16	{
17		struct SupportVisitor;
18		
19		impl<'de> Visitor<'de> for SupportVisitor
20		{
21			type Value = Version;
22			
23			#[inline(always)]
24			fn expecting(&self, formatter: &mut Formatter) -> fmt::Result
25			{
26				formatter.write_str("a string which contains a period-delimited version")
27			}
28			
29			#[inline(always)]
30			fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E>
31			{
32				Ok(Version::parse(v))
33			}
34		}
35		
36		deserializer.deserialize_str(SupportVisitor)
37	}
38}
39
40impl<'a, I: Into<&'a str>> From<I> for Version
41{
42	/// Converts into a Version anything that can be converted into '&str'.
43	/// Use with `into()`.
44	#[inline(always)]
45	fn from(value: I) -> Self
46	{
47		Version::parse(value.into())
48	}
49}
50
51impl FromStr for Version
52{
53	type Err = ();
54	
55	/// Converts into a Version anything from '&str'.
56	/// Use with `parse()`.
57	#[inline(always)]
58	fn from_str(s: &str) -> Result<Self, Self::Err>
59	{
60		Ok(Version::parse(s))
61	}
62}
63
64impl Version
65{
66	/// Special method to construct a version representing the Opera Mini all version
67	#[inline(always)]
68	pub fn opera_mini_all() -> Self
69	{
70		Version(VersionPart::All, vec![])
71	}
72	
73	/// Special method to construct a version representing the Safari TP version
74	#[inline(always)]
75	pub fn safari_technology_preview() -> Self
76	{
77		Version(VersionPart::TechnologyPreview, vec![])
78	}
79	
80	/// Special method to construct a Version that only represents a major version, eg 10
81	#[inline(always)]
82	pub fn major(major_version: u64) -> Self
83	{
84		Version(VersionPart::Number(major_version), vec![])
85	}
86	
87	/// Special method to construct a Version that represents a major-minor version, eg 12.1
88	#[inline(always)]
89	pub fn major_minor(major_version: u64, minor_version: u64) -> Self
90	{
91		Version(VersionPart::Number(major_version), vec![VersionPart::Number(minor_version)])
92	}
93	
94	/// Special method to construct a Version that represents a major-minor-revision version, eg 4.5.6
95	#[inline(always)]
96	pub fn major_minor_revision(major_version: u64, minor_version: u64, revision_version: u64) -> Self
97	{
98		Version(VersionPart::Number(major_version), vec![VersionPart::Number(minor_version), VersionPart::Number(revision_version)])
99	}
100	
101	/// Is this version the Safari Technology Preview?
102	#[inline(always)]
103	pub fn is_safari_technology_preview(&self) -> bool
104	{
105		match self.0
106		{
107			VersionPart::TechnologyPreview => true,
108			_ => false,
109		}
110	}
111	
112	/// Is this version "0" (sometimes found in caniuse.com's Regional data) or Unknown
113	#[inline(always)]
114	pub fn is_invalid_or_unknown(&self) -> bool
115	{
116		match self.0
117		{
118			VersionPart::Number(0) => true,
119			VersionPart::Unknown(_) => true,
120			_ => false,
121		}
122	}
123	
124	#[inline(always)]
125	fn parse(v: &str) -> Self
126	{
127		use self::VersionPart::*;
128		
129		// Handle version ranges used in Opera and iOS Safari
130		if let Some(index) = v.find('-')
131		{
132			return Self::parse(&v[..index]);
133		}
134		
135		// Specialized logic to handle legacy Opera Presto ranges, Safari Technology Preview, Opera Mini and iOS Safari
136		match v
137		{
138			"TP" => return Self::safari_technology_preview(),
139			"all" => return Self::opera_mini_all(),
140			_ => (),
141		}
142		
143		let parts = v.split('.');
144		
145		let (lower, upper) = parts.size_hint();
146		let mut capacity = if let Some(upper) = upper
147		{
148			upper
149		}
150		else
151		{
152			lower
153		};
154		if capacity != 0
155		{
156			capacity -= 1;
157		}
158		
159		let mut first = None;
160		let mut subsequent = Vec::with_capacity(capacity);
161		for part in parts
162		{
163			let versionPart = match part.parse::<u64>()
164			{
165				Ok(value) => Number(value),
166				Err(_) => Unknown(part.to_owned())
167			};
168			if first.is_none()
169			{
170				first = Some(versionPart);
171			}
172			else
173			{
174				subsequent.push(versionPart);
175			}
176		}
177		
178		subsequent.shrink_to_fit();
179		Version(first.unwrap(), subsequent)
180	}
181}