criterium 3.1.3

Lightweigt dynamic database queries for rusqlite.
Documentation
// SPDX-FileCopyrightText: 2025 Slatian
//
// SPDX-License-Identifier: LGPL-3.0-only

//! Matches against numeric values.

mod as_integer;

#[cfg(feature = "rusqlite")]
pub mod rusqlite;

pub mod direct_match;

pub use as_integer::AsInteger;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The NumberCriterium describes a set of comparisons that
/// can be made against a numeric field.
///
/// If the field is some form of null, undefined or unset
/// only the IsNone condition is allowed to match.
///
/// Note: There is a [DirectMatch] implementation for use with a [RangeInclusive]
/// which takes the range as a possibility of values and returns wheter the
/// number criterium will certaily match or not match or if both is possible.
///
/// [DirectMatch]: crate::DirectMatch
/// [RangeInclusive]: core::ops::RangeInclusive
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum NumberCriterium<N> {
	/// The value in the field is equal to the given number (`f == N`).
	Equals(N),

	/// The value in the field is less than the given number (`f < N`).
	LessThan(N),

	/// The value in the field is less than or equal to the given number (`f <= N`).
	LessThanOrEqual(N),

	/// The value in the field is greater than the given number (`f > N`).
	GreaterThan(N),

	/// The value in the field is greater than or equal to the given number (`f >= N`).
	GreaterThanOrEqual(N),

	/// The value is in the list of given numbers
	/// (as a shorthand for chaining equals with an and)
	InList(Vec<N>),

	/// The value in the field is not set.
	IsNone,
}

// Conversion implementations

impl<N> From<N> for NumberCriterium<N> {
	/// Converts any number to a `NumberCriterium` with an `Equals` condition.
	fn from(from: N) -> Self {
		NumberCriterium::Equals(from)
	}
}

impl<N> From<Option<N>> for NumberCriterium<N> {
	/// Converts any number to a `NumberCriterium` with an `Equals` condition.
	/// If `None` is passed, the `IsNone` condition will be used.
	fn from(from: Option<N>) -> Self {
		if let Some(from) = from {
			NumberCriterium::Equals(from)
		} else {
			NumberCriterium::IsNone
		}
	}
}

impl<N> From<Vec<N>> for NumberCriterium<N> {
	/// Converts a list of numbers to a `NumberCriterium` with an `InList` condition.
	fn from(from: Vec<N>) -> Self {
		NumberCriterium::InList(from)
	}
}

impl<N> NumberCriterium<N>
where
	N: Copy,
{
	/// Apply a translation function to the number criterium,
	/// allowing from rescaling numbers to type conversions.
	pub fn translate<F, O>(&self, func: F) -> NumberCriterium<O>
	where
		F: Fn(N) -> O,
	{
		match self {
			Self::Equals(n) => NumberCriterium::Equals(func(*n)),
			Self::LessThan(n) => NumberCriterium::LessThan(func(*n)),
			Self::LessThanOrEqual(n) => NumberCriterium::LessThanOrEqual(func(*n)),
			Self::GreaterThan(n) => NumberCriterium::GreaterThan(func(*n)),
			Self::GreaterThanOrEqual(n) => NumberCriterium::GreaterThanOrEqual(func(*n)),
			Self::InList(l) => NumberCriterium::InList(l.iter().map(|n| func(*n)).collect()),
			Self::IsNone => NumberCriterium::IsNone,
		}
	}
}

#[cfg(test)]
mod test {
	use super::*;
	use crate::direct_match::DirectMatch;

	#[test]
	fn direct_match_integer_equal() {
		assert!(NumberCriterium::Equals(238 as u8).criterium_match(&(238 as i64)));
		assert!(NumberCriterium::Equals(-23 as i8).criterium_match(&(-23 as i64)));
		assert!(!NumberCriterium::Equals(-238 as i16).criterium_match(&(238 as u16)));
		assert!(!NumberCriterium::Equals(1 as u8).criterium_match(&(0 as i64)));
	}

	#[test]
	fn direct_match_float_equal() {
		assert!(NumberCriterium::Equals(1.56f32).criterium_match(&1.56f32));
		assert!(NumberCriterium::Equals(1.56f64).criterium_match(&1.56f64));
	}

	#[test]
	fn direct_match_i128_equal() {
		assert!(NumberCriterium::Equals(-67e267 as i128).criterium_match(&(-67e267 as i128)));
	}

	#[test]
	fn direct_match_integer_compare() {
		assert!(NumberCriterium::LessThan(156i32).criterium_match(&-156));
		assert!(!NumberCriterium::LessThan(156i32).criterium_match(&156));
		assert!(!NumberCriterium::LessThan(-156i32).criterium_match(&156));

		assert!(NumberCriterium::LessThanOrEqual(156i16).criterium_match(&-156));
		assert!(NumberCriterium::LessThanOrEqual(156i16).criterium_match(&156));
		assert!(!NumberCriterium::LessThanOrEqual(-156i16).criterium_match(&156));

		assert!(!NumberCriterium::GreaterThan(156i64).criterium_match(&-156));
		assert!(!NumberCriterium::GreaterThan(156i64).criterium_match(&156));
		assert!(NumberCriterium::GreaterThan(-156i64).criterium_match(&156));

		assert!(!NumberCriterium::GreaterThanOrEqual(156i64).criterium_match(&-156));
		assert!(NumberCriterium::GreaterThanOrEqual(156i64).criterium_match(&156));
		assert!(NumberCriterium::GreaterThanOrEqual(-156i64).criterium_match(&156));
	}

	#[test]
	fn direct_match_number_opt() {
		assert!(NumberCriterium::<i32>::IsNone.criterium_match(&None));
		assert!(!NumberCriterium::<u8>::IsNone.criterium_match(&3132));
		assert!(!NumberCriterium::IsNone.criterium_match(&Some(-31e31 as i64)));

		assert!(!NumberCriterium::GreaterThanOrEqual(156i64).criterium_match(&None));
		assert!(!NumberCriterium::GreaterThan(-156.0f64).criterium_match(&None));
		assert!(!NumberCriterium::LessThanOrEqual(15638217i32).criterium_match(&None));
		assert!(!NumberCriterium::LessThan(-156i16).criterium_match(&None));
		assert!(!NumberCriterium::Equals(0u8).criterium_match(&None));
	}

	#[test]
	fn direct_match_range() {
		// Test equals
		assert_eq!(
			NumberCriterium::Equals(2).criterium_match(&(3..=5)),
			Some(false)
		);
		assert_eq!(NumberCriterium::Equals(3).criterium_match(&(3..=5)), None);
		assert_eq!(NumberCriterium::Equals(4).criterium_match(&(3..=5)), None);
		assert_eq!(NumberCriterium::Equals(5).criterium_match(&(3..=5)), None);
		assert_eq!(
			NumberCriterium::Equals(6).criterium_match(&(3..=5)),
			Some(false)
		);

		assert_eq!(
			NumberCriterium::Equals(4).criterium_match(&(4..=4)),
			Some(true)
		);
		assert_eq!(
			NumberCriterium::Equals(4).criterium_match(&(5..=4)),
			Some(false)
		); //empty range

		// Test less than
		assert_eq!(
			NumberCriterium::LessThan(2).criterium_match(&(3..=5)),
			Some(false)
		);
		assert_eq!(
			NumberCriterium::LessThan(3).criterium_match(&(3..=5)),
			Some(false)
		);
		assert_eq!(NumberCriterium::LessThan(4).criterium_match(&(3..=5)), None);
		assert_eq!(NumberCriterium::LessThan(5).criterium_match(&(3..=5)), None);
		assert_eq!(
			NumberCriterium::LessThan(6).criterium_match(&(3..=5)),
			Some(true)
		);

		// Test less equal
		assert_eq!(
			NumberCriterium::LessThanOrEqual(2).criterium_match(&(3..=5)),
			Some(false)
		);
		assert_eq!(
			NumberCriterium::LessThanOrEqual(3).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::LessThanOrEqual(4).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::LessThanOrEqual(5).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::LessThanOrEqual(6).criterium_match(&(3..=5)),
			Some(true)
		);

		// Test greater than
		assert_eq!(
			NumberCriterium::GreaterThan(2).criterium_match(&(3..=5)),
			Some(true)
		);
		assert_eq!(
			NumberCriterium::GreaterThan(3).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::GreaterThan(4).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::GreaterThan(5).criterium_match(&(3..=5)),
			Some(false)
		);
		assert_eq!(
			NumberCriterium::GreaterThan(6).criterium_match(&(3..=5)),
			Some(false)
		);

		// Test greater equal
		assert_eq!(
			NumberCriterium::GreaterThanOrEqual(2).criterium_match(&(3..=5)),
			Some(true)
		);
		assert_eq!(
			NumberCriterium::GreaterThanOrEqual(3).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::GreaterThanOrEqual(4).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::GreaterThanOrEqual(5).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::GreaterThanOrEqual(6).criterium_match(&(3..=5)),
			Some(false)
		);

		// Test in List
		assert_eq!(
			NumberCriterium::InList(vec![]).criterium_match(&(3..=5)),
			Some(false)
		);
		assert_eq!(
			NumberCriterium::InList(vec![2, 6]).criterium_match(&(3..=5)),
			Some(false)
		);
		assert_eq!(
			NumberCriterium::InList(vec![4]).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::InList(vec![4, 5]).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::InList(vec![4, 5, 6]).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::InList(vec![4, 5, 6, 7]).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::InList(vec![3, 4, 4]).criterium_match(&(3..=5)),
			None
		);
		assert_eq!(
			NumberCriterium::InList(vec![3, 4, 5]).criterium_match(&(3..=5)),
			Some(true)
		);
		assert_eq!(
			NumberCriterium::InList(vec![5, 4, 3]).criterium_match(&(3..=5)),
			Some(true)
		);
		assert_eq!(
			NumberCriterium::InList(vec![5, 3, 7, 4, 5, 8, 10, 0]).criterium_match(&(3..=5)),
			Some(true)
		);
	}
}