criterium 3.1.3

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

//! A Boolean type for Criterium

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

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

pub mod direct_match;

/// The BooleanCriterium describes the comparisons
/// that can be made against a boolean field.
///
/// *Warning:* inverting an Equals(true) and an Equals(false)
/// are not equivalent as they behave direfferently on null values.
///
/// ```
/// use criterium::BooleanCriterium;
/// use criterium::CriteriumChain;
/// use criterium::DirectMatch;
///
/// let c_true = BooleanCriterium::Equals(true);
/// let c_false = BooleanCriterium::Equals(false);
/// let c_false_inverted = CriteriumChain::Not(c_false.clone());
///
/// // True and inverted false agree on plain `true` and `false` matching
/// assert_eq!(
/// 	c_true.criterium_match(&true),
/// 	c_false_inverted.criterium_match(&true)
/// );
/// assert_eq!(
/// 	c_true.criterium_match(&false),
/// 	c_false_inverted.criterium_match(&false)
/// );
///
/// // But they disagree on wheter `None` is a match
/// assert_ne!(
/// 	c_true.criterium_match(&(None as Option<bool>)),
/// 	c_false_inverted.criterium_match(&(None as Option<bool>))
/// );
///
/// // Because the non-inverrted versions agree,
/// // that they don't match `None`.
/// assert!(!c_true.criterium_match(&(None as Option<bool>)));
/// assert!(!c_false.criterium_match(&(None as Option<bool>)));
/// ```
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum BooleanCriterium {
	/// Is the given boolean number and not undefined
	Equals(bool),

	/// Is undefined
	IsNone,
}

impl From<bool> for BooleanCriterium {
	fn from(b: bool) -> Self {
		Self::Equals(b)
	}
}

impl From<Option<bool>> for BooleanCriterium {
	fn from(b: Option<bool>) -> Self {
		if let Some(b) = b {
			Self::Equals(b)
		} else {
			Self::IsNone
		}
	}
}

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

	#[test]
	fn direct_match_bool() {
		let c_true = BooleanCriterium::Equals(true);
		let c_false = BooleanCriterium::Equals(false);

		assert!(c_true.criterium_match(&true));
		assert!(!c_false.criterium_match(&true));

		assert!(!c_true.criterium_match(&false));
		assert!(c_false.criterium_match(&false));

		assert!(!c_true.criterium_match(&(None as Option<bool>)));
		assert!(!c_false.criterium_match(&(None as Option<bool>)));

		assert!(c_true.criterium_match(&Some(true)));
		assert!(!c_false.criterium_match(&Some(true)));

		assert!(!c_true.criterium_match(&Some(false)));
		assert!(c_false.criterium_match(&Some(false)));
	}
}