#![doc = include_str!("../README.md")]
#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::wildcard_imports)]
#![warn(clippy::enum_glob_use)]
#[cfg(feature = "std")]
extern crate std;
pub trait CheckedClamp: PartialOrd + Sized {
fn checked_clamp(
self,
min: impl Into<Self>,
max: impl Into<Self>,
) -> Result<Self, CheckedClampError> {
checked_clamp_mono(self, min.into(), max.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CheckedClampError {
MinGreaterThanMax,
ComparisonFailed,
}
impl<T> CheckedClamp for T where T: PartialOrd + Sized {}
impl core::fmt::Display for CheckedClampError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::MinGreaterThanMax => f.write_str("Min is greater than max"),
Self::ComparisonFailed => f.write_str("Failed to compare values"),
}
}
}
fn checked_clamp_mono<T>(value: T, min: T, max: T) -> Result<T, CheckedClampError>
where
T: PartialOrd,
{
use core::cmp::Ordering as O;
use CheckedClampError as E;
match min.partial_cmp(&max) {
None => Err(E::ComparisonFailed),
Some(O::Greater) => Err(E::MinGreaterThanMax),
_ => Ok(()),
}?;
let (min_cmp, max_cmp) = (
value.partial_cmp(&min).ok_or(E::ComparisonFailed)?,
value.partial_cmp(&max).ok_or(E::ComparisonFailed)?,
);
match (min_cmp, max_cmp) {
(O::Less, _) => Ok(min),
(_, O::Greater) => Ok(max),
(_, _) => Ok(value),
}
}
#[cfg(feature = "std")]
impl std::error::Error for CheckedClampError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_clamp() {
assert_eq!(0u8.checked_clamp(5, 15), Ok(5));
assert_eq!(5u8.checked_clamp(5, 15), Ok(5));
assert_eq!(10u8.checked_clamp(5, 15), Ok(10));
assert_eq!(15u8.checked_clamp(5, 15), Ok(15));
assert_eq!(20u8.checked_clamp(5, 15), Ok(15));
assert_eq!(f64::NEG_INFINITY.checked_clamp(5, 15), Ok(5.0));
assert_eq!((-100.0).checked_clamp(5, 15), Ok(5.0));
assert_eq!(0.0.checked_clamp(5, 15), Ok(5.0));
assert_eq!(100.0.checked_clamp(5, 15), Ok(15.0));
assert_eq!(f64::INFINITY.checked_clamp(5, 15), Ok(15.0));
}
#[test]
fn invalid_clamp() {
use CheckedClampError as E;
assert_eq!(0u8.checked_clamp(15, 5), Err(E::MinGreaterThanMax));
assert_eq!(f64::NAN.checked_clamp(15, 5), Err(E::MinGreaterThanMax));
assert_eq!(f64::NAN.checked_clamp(5, 15), Err(E::ComparisonFailed));
}
}