later_operator/
cmp.rs

1/*!
2# Later Operator: Comparison Operator.
3*/
4
5use crate::Error;
6use std::{
7	borrow::Borrow,
8	fmt,
9	str::FromStr,
10};
11
12
13
14#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
15/// # Comparison Operators.
16///
17/// This enum holds a comparison operator — `!=`, `<`, `<=`, `==`, `>=`, `>` —
18/// that can be used to programmatically compare two values.
19///
20/// Variants can be parsed from string slices using [`FromStr`](#impl-FromStr-for-ComparisonOperator) or [`TryFrom<&str>`](#impl-TryFrom<%26str>-for-ComparisonOperator), or
21/// byte slices via [`TryFrom<&[u8]>`](#impl-TryFrom<%26[u8]>-for-ComparisonOperator), and converted to a string slice or byte slice
22/// using [`ComparisonOperator::as_str`] or [`ComparisonOperator::as_bytes`] respectively.
23///
24/// If the `serde` crate feature is enabled, it can also be de/serialized
25/// from/to its string representations.
26///
27/// Use [`ComparisonOperator::compare`] to thusly compare any two values (so
28/// long as the type implements [`PartialOrd`](std::cmp::PartialOrd)).
29///
30/// ## Examples
31///
32/// ```
33/// use later_operator::ComparisonOperator;
34///
35/// // Parse from a string, then compare two arbitrary values.
36/// let op = ComparisonOperator::try_from("<=").unwrap();
37/// assert!(op.compare(&3_u8, &u8::MAX)); // 3 <= 255
38///
39/// // Re-stringify the operator for printing or whatever.
40/// assert_eq!(
41///     format!("3 {op} 255"),
42///     "3 <= 255",
43/// );
44///
45/// // Leading/trailing whitespace is ignored when parsing.
46/// assert_eq!(
47///     ComparisonOperator::try_from("==").unwrap(),
48///     ComparisonOperator::try_from(" ==\n").unwrap(),
49/// );
50///
51/// // But the value has to make sense or it will fail.
52/// assert!(ComparisonOperator::try_from("~").is_err());
53/// ```
54#[cfg_attr(feature = "serde", doc = include_str!("../skel/serde.txt"))]
55pub enum ComparisonOperator {
56	/// # Not Equal To.
57	Ne,
58
59	/// # Less Than.
60	Lt,
61
62	/// # Less Than or Equal To.
63	Le,
64
65	/// # Equal To.
66	Eq,
67
68	/// # Greater Than or Equal To.
69	Ge,
70
71	/// # Greater Than.
72	Gt,
73}
74
75impl AsRef<[u8]> for ComparisonOperator {
76	#[inline]
77	fn as_ref(&self) -> &[u8] { self.as_bytes() }
78}
79
80impl AsRef<str> for ComparisonOperator {
81	#[inline]
82	fn as_ref(&self) -> &str { self.as_str() }
83}
84
85impl Borrow<str> for ComparisonOperator {
86	#[inline]
87	fn borrow(&self) -> &str { self.as_str() }
88}
89
90impl fmt::Display for ComparisonOperator {
91	#[inline]
92	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93		<str as fmt::Display>::fmt(self.as_str(), f)
94	}
95}
96
97#[cfg(feature = "serde")]
98#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
99impl<'de> ::serde_core::Deserialize<'de> for ComparisonOperator {
100	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101	where D: ::serde_core::de::Deserializer<'de> {
102		/// # Visitor.
103		struct Visitor;
104
105		impl ::serde_core::de::Visitor<'_> for Visitor {
106			type Value = ComparisonOperator;
107
108			#[inline]
109			fn expecting(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
110				f.write_str("string")
111			}
112
113			#[inline]
114			fn visit_str<S>(self, src: &str) -> Result<ComparisonOperator, S>
115			where S: ::serde_core::de::Error {
116				<ComparisonOperator>::try_from(src).map_err(::serde_core::de::Error::custom)
117			}
118
119			#[inline]
120			fn visit_bytes<S>(self, src: &[u8]) -> Result<ComparisonOperator, S>
121			where S: ::serde_core::de::Error {
122				<ComparisonOperator>::try_from(src).map_err(::serde_core::de::Error::custom)
123			}
124		}
125
126		deserializer.deserialize_str(Visitor)
127	}
128}
129
130impl FromStr for ComparisonOperator {
131	type Err = Error;
132
133	#[inline]
134	fn from_str(src: &str) -> Result<Self, Self::Err> {
135		Self::try_from(src.as_bytes())
136	}
137}
138
139impl PartialEq<str> for ComparisonOperator {
140	#[inline]
141	fn eq(&self, other: &str) -> bool { self.as_str() == other }
142}
143
144impl PartialEq<ComparisonOperator> for str {
145	#[inline]
146	fn eq(&self, other: &ComparisonOperator) -> bool { self == other.as_str() }
147}
148
149#[cfg(feature = "serde")]
150#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
151impl ::serde_core::Serialize for ComparisonOperator {
152	#[inline]
153	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154	where S: ::serde_core::ser::Serializer { self.as_str().serialize(serializer) }
155}
156
157impl TryFrom<&[u8]> for ComparisonOperator {
158	type Error = Error;
159
160	#[inline]
161	fn try_from(src: &[u8]) -> Result<Self, Self::Error> {
162		match src.trim_ascii() {
163			b"!=" =>        Ok(Self::Ne),
164			b"<"  =>        Ok(Self::Lt),
165			b"<=" =>        Ok(Self::Le),
166			b"==" | b"=" => Ok(Self::Eq),
167			b">=" =>        Ok(Self::Ge),
168			b">"  =>        Ok(Self::Gt),
169			_     =>        Err(Error),
170		}
171	}
172}
173
174impl TryFrom<&str> for ComparisonOperator {
175	type Error = Error;
176
177	#[inline]
178	fn try_from(src: &str) -> Result<Self, Self::Error> { Self::try_from(src.as_bytes()) }
179}
180
181impl ComparisonOperator {
182	#[must_use]
183	/// # As Bytes.
184	///
185	/// Return the operator as a byte slice.
186	///
187	/// ## Examples
188	///
189	/// ```
190	/// use later_operator::ComparisonOperator;
191	///
192	/// assert_eq!(ComparisonOperator::Eq.as_bytes(), b"==");
193	/// ```
194	pub const fn as_bytes(self) -> &'static [u8] {
195		match self {
196			Self::Lt => b"<",
197			Self::Le => b"<=",
198			Self::Eq => b"==",
199			Self::Ge => b">=",
200			Self::Gt => b">",
201			Self::Ne => b"!=",
202		}
203	}
204
205	#[must_use]
206	/// # As Str.
207	///
208	/// Return the operator as a string slice.
209	///
210	/// ## Examples
211	///
212	/// ```
213	/// use later_operator::ComparisonOperator;
214	///
215	/// assert_eq!(ComparisonOperator::Eq.as_str(), "==");
216	/// ```
217	pub const fn as_str(self) -> &'static str {
218		match self {
219			Self::Lt => "<",
220			Self::Le => "<=",
221			Self::Eq => "==",
222			Self::Ge => ">=",
223			Self::Gt => ">",
224			Self::Ne => "!=",
225		}
226	}
227
228	#[must_use]
229	/// # Is Empty.
230	///
231	/// This method is added only for consistency; it always returns `false`.
232	pub const fn is_empty(self) -> bool { false }
233
234	#[must_use]
235	/// # Length.
236	///
237	/// Return the byte length of the operator's string representation.
238	pub const fn len(self) -> usize {
239		match self {
240			Self::Lt | Self::Gt => 1,
241			_ => 2,
242		}
243	}
244}
245
246/// # Helper: Generate Is Equal/Less/Etc. Methods.
247macro_rules! is {
248	($name:literal, $fn:ident, $ty:ident) => (
249		#[must_use]
250		#[doc = concat!("# Is ", $name, "?")]
251		#[doc = ""]
252		#[doc = concat!("Returns `true` for [`ComparisonOperator::", stringify!($ty), "`].")]
253		pub const fn $fn(self) -> bool { matches!(self, Self::$ty) }
254	);
255}
256
257impl ComparisonOperator {
258	is!("Not Equal",             is_ne, Ne);
259	is!("Less Than",             is_lt, Lt);
260	is!("Less Than/Equal To",    is_le, Le);
261	is!("Equal",                 is_eq, Eq);
262	is!("Greater Than/Equal To", is_ge, Ge);
263	is!("Greater Than",          is_gt, Gt);
264}
265
266impl ComparisonOperator {
267	/// # Compare.
268	///
269	/// Compare `lhs` to `rhs` using a given operator.
270	///
271	/// ## Examples
272	///
273	/// ```
274	/// use later_operator::ComparisonOperator;
275	///
276	/// let a: u32 = 50;
277	/// let b: u32 = 60;
278	///
279	/// assert_eq!(ComparisonOperator::Ne.compare(&a, &b), true);  // a != b
280	/// assert_eq!(ComparisonOperator::Lt.compare(&a, &b), true);  // a < b
281	/// assert_eq!(ComparisonOperator::Le.compare(&a, &b), true);  // a <= b
282	/// assert_eq!(ComparisonOperator::Eq.compare(&a, &b), false); // a == b
283	/// assert_eq!(ComparisonOperator::Ge.compare(&a, &b), false); // a >= b
284	/// assert_eq!(ComparisonOperator::Gt.compare(&a, &b), false); // a > b
285	/// ```
286	pub fn compare<T: PartialOrd>(self, lhs: &T, rhs: &T) -> bool {
287		match self {
288			Self::Ne => lhs != rhs,
289			Self::Lt => lhs < rhs,
290			Self::Le => lhs <= rhs,
291			Self::Eq => lhs == rhs,
292			Self::Ge => lhs >= rhs,
293			Self::Gt => lhs > rhs,
294		}
295	}
296}
297
298
299
300#[cfg(test)]
301mod tests {
302	use super::*;
303	use serde as _; // For doc tests.
304	use serde_json as _;
305
306	#[test]
307	fn t_parse() {
308		macro_rules! test {
309			($ty:ident, $val:literal) => (
310				// TryFrom<str>.
311				assert_eq!(
312					Ok(ComparisonOperator::$ty),
313					ComparisonOperator::try_from($val)
314				);
315
316				// FromStr<str>.
317				assert_eq!(
318					Ok(ComparisonOperator::$ty),
319					ComparisonOperator::from_str($val)
320				);
321
322				// TryFrom<[u8]>
323				assert_eq!(
324					Ok(ComparisonOperator::$ty),
325					ComparisonOperator::try_from($val.as_bytes())
326				);
327
328				// TryFroms with leading/trailing whitespace.
329				assert_eq!(
330					Ok(ComparisonOperator::$ty),
331					ComparisonOperator::try_from(concat!(" ", $val, " "))
332				);
333				assert_eq!(
334					Ok(ComparisonOperator::$ty),
335					ComparisonOperator::try_from(concat!(" ", $val, " ").as_bytes())
336				);
337			)
338		}
339
340		test!(Ne, "!=");
341		test!(Lt, "<");
342		test!(Le, "<=");
343		test!(Eq, "==");
344		test!(Eq, "=");
345		test!(Ge, ">=");
346		test!(Gt, ">");
347	}
348
349	#[test]
350	fn t_as() {
351		// Redundantly check as_str, as_bytes sanity.
352		for (op, v) in [
353			(ComparisonOperator::Ne, "!="),
354			(ComparisonOperator::Lt, "<"),
355			(ComparisonOperator::Le, "<="),
356			(ComparisonOperator::Eq, "=="),
357			(ComparisonOperator::Ge, ">="),
358			(ComparisonOperator::Gt, ">"),
359		] {
360			assert_eq!(op.as_str(), v);
361			assert_eq!(op.as_bytes(), v.as_bytes());
362			assert_eq!(op.as_bytes().len(), op.len());
363		}
364	}
365
366	#[cfg(feature = "serde")]
367	#[test]
368	fn t_serde() {
369		for op in [
370			ComparisonOperator::Ne,
371			ComparisonOperator::Lt,
372			ComparisonOperator::Le,
373			ComparisonOperator::Eq,
374			ComparisonOperator::Ge,
375			ComparisonOperator::Gt,
376		] {
377			// Serialization should look like as_str with extra quotes.
378			let s = serde_json::to_string(&op).expect("Serialization failed.");
379			assert_eq!(s, format!("{:?}", op.as_str()));
380
381			// Deserialization should give us the original value.
382			let d: ComparisonOperator = serde_json::from_str(&s).expect("Deserialization failed.");
383			assert_eq!(op, d);
384		}
385	}
386
387	#[test]
388	fn t_compare() {
389		const SET: [u8; 7] = [0, 1, 2, 3, 4, 5, 6];
390
391		macro_rules! count {
392			($op:ident, $lhs:literal) => (
393				SET.iter().filter(|v| ComparisonOperator::$op.compare(&$lhs, v)).count()
394			);
395		}
396
397		// Compare the middle value against the ordered set.
398		assert_eq!(count!(Ne, 3), 6);
399		assert_eq!(count!(Lt, 3), 3);
400		assert_eq!(count!(Le, 3), 4);
401		assert_eq!(count!(Eq, 3), 1);
402		assert_eq!(count!(Ge, 3), 4);
403		assert_eq!(count!(Gt, 3), 3);
404
405		// Check highs and lows to make sure we implemented the less/more
406		// comparisons in the *correct* order. ;)
407		assert_eq!(count!(Lt, 0), 6);
408		assert_eq!(count!(Le, 0), 7);
409		assert_eq!(count!(Ge, 6), 7);
410		assert_eq!(count!(Gt, 6), 6);
411	}
412}
413