1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{cmp::Ordering, fmt};
7use use_bound::{LowerBound, UpperBound};
8
9pub mod prelude;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum RangeError {
13 InvertedBounds,
14}
15
16impl fmt::Display for RangeError {
17 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 Self::InvertedBounds => formatter.write_str("lower bound cannot exceed upper bound"),
20 }
21 }
22}
23
24impl std::error::Error for RangeError {}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct RangeConstraint<T> {
28 lower: Option<LowerBound<T>>,
29 upper: Option<UpperBound<T>>,
30}
31
32impl<T: PartialOrd> RangeConstraint<T> {
33 pub fn new(
40 lower: Option<LowerBound<T>>,
41 upper: Option<UpperBound<T>>,
42 ) -> Result<Self, RangeError> {
43 if let (Some(lower_bound), Some(upper_bound)) = (&lower, &upper) {
44 match lower_bound.value().partial_cmp(upper_bound.value()) {
45 Some(Ordering::Greater) | None => return Err(RangeError::InvertedBounds),
46 Some(Ordering::Equal)
47 if matches!(lower_bound, LowerBound::Exclusive(_))
48 || matches!(upper_bound, UpperBound::Exclusive(_)) =>
49 {
50 return Err(RangeError::InvertedBounds);
51 },
52 Some(Ordering::Less | Ordering::Equal) => {},
53 }
54 }
55
56 Ok(Self { lower, upper })
57 }
58
59 #[must_use]
60 pub fn contains(&self, value: &T) -> bool {
61 self.lower
62 .as_ref()
63 .is_none_or(|lower_bound| lower_bound.allows(value))
64 && self
65 .upper
66 .as_ref()
67 .is_none_or(|upper_bound| upper_bound.allows(value))
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::{RangeConstraint, RangeError};
74 use use_bound::{exclusive_maximum, exclusive_minimum, maximum, minimum};
75
76 #[test]
77 fn range_contains_values_inside_the_bounds() -> Result<(), RangeError> {
78 let range = RangeConstraint::new(Some(minimum(1)), Some(maximum(10)))?;
79 assert!(range.contains(&5));
80 assert!(!range.contains(&11));
81 Ok(())
82 }
83
84 #[test]
85 fn range_rejects_empty_exclusive_intervals() {
86 assert_eq!(
87 RangeConstraint::new(Some(exclusive_minimum(1)), Some(exclusive_maximum(1))),
88 Err(RangeError::InvertedBounds)
89 );
90 }
91}