#![allow(clippy::module_name_repetitions)]
use std::fmt;
pub type Result<T> = std::result::Result<T, BloomCraftError>;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum BloomCraftError {
InvalidParameters {
message: String,
},
FalsePositiveRateOutOfBounds {
fp_rate: f64,
},
InvalidItemCount {
count: usize,
},
CapacityExceeded {
capacity: usize,
attempted: usize,
},
MaxFiltersExceeded {
max_filters: usize,
current_count: usize,
},
UnsupportedOperation {
operation: String,
variant: String,
},
IncompatibleFilters {
reason: String,
},
InvalidHashCount {
count: usize,
min: usize,
max: usize,
},
InvalidFilterSize {
size: usize,
},
SerializationError {
message: String,
},
InternalError {
message: String,
},
CounterOverflow {
max_value: u64,
},
CounterUnderflow {
min_value: u64,
},
IndexOutOfBounds {
index: usize,
length: usize,
},
InvalidRange {
start: usize,
end: usize,
length: usize,
reason: String,
},
}
impl fmt::Display for BloomCraftError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidParameters { message } => {
write!(f, "Invalid Bloom filter parameters: {message}.")
}
Self::FalsePositiveRateOutOfBounds { fp_rate } => {
write!(
f,
"False positive rate {fp_rate:.6} is out of bounds. Must be in (0, 1).",
)
}
Self::InvalidItemCount { count } => {
write!(f, "Invalid item count: {count}. Must be greater than 0.")
}
Self::CapacityExceeded {
capacity,
attempted,
} => {
write!(
f,
"Filter capacity of {capacity} items exceeded. Attempted to insert {attempted} items.",
)
}
Self::MaxFiltersExceeded {
max_filters,
current_count,
} => {
write!(
f,
"ScalableBloomFilter reached the sub-filter limit of {max_filters} \
(current: {current_count}). FPR will degrade on further inserts. \
See CapacityExhaustedBehavior for options.",
)
}
Self::UnsupportedOperation { operation, variant } => {
write!(
f,
"Operation '{operation}' is not supported by {variant} Bloom filter variant.",
)
}
Self::IncompatibleFilters { reason } => {
write!(
f,
"Cannot perform operation on incompatible filters: {reason}."
)
}
Self::InvalidHashCount { count, min, max } => {
write!(
f,
"Invalid hash function count: {count}. Must be in [{min}, {max}].",
)
}
Self::InvalidFilterSize { size } => {
write!(
f,
"Invalid filter size: {size} bits. Must be positive and within memory limits.",
)
}
Self::SerializationError { message } => {
write!(f, "Serialization error: {message}.")
}
Self::InternalError { message } => {
write!(
f,
"Internal error (this is a bug in BloomCraft): {message}."
)
}
Self::CounterOverflow { max_value } => {
write!(
f,
"Counter overflow: attempted to increment beyond maximum value {max_value}.",
)
}
Self::CounterUnderflow { min_value } => {
write!(
f,
"Counter underflow: attempted to decrement below minimum value {min_value}.",
)
}
Self::IndexOutOfBounds { index, length } => {
write!(
f,
"Index {index} out of bounds for bit vector of length {length}."
)
}
Self::InvalidRange {
start,
end,
length,
reason,
} => {
write!(
f,
"Invalid range [{start}..{end}) for bit vector of length {length}: {reason}.",
)
}
}
}
}
impl std::error::Error for BloomCraftError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[cfg(feature = "serde")]
impl From<serde_json::Error> for BloomCraftError {
fn from(e: serde_json::Error) -> Self {
BloomCraftError::serialization_error(e.to_string())
}
}
impl BloomCraftError {
#[must_use]
pub fn invalid_parameters(message: impl Into<String>) -> Self {
Self::InvalidParameters {
message: message.into(),
}
}
#[must_use]
pub fn fp_rate_out_of_bounds(fp_rate: f64) -> Self {
Self::FalsePositiveRateOutOfBounds { fp_rate }
}
#[must_use]
pub fn invalid_item_count(count: usize) -> Self {
Self::InvalidItemCount { count }
}
#[must_use]
pub fn capacity_exceeded(capacity: usize, attempted: usize) -> Self {
Self::CapacityExceeded {
capacity,
attempted,
}
}
#[must_use]
pub fn max_filters_exceeded(max_filters: usize, current_count: usize) -> Self {
Self::MaxFiltersExceeded {
max_filters,
current_count,
}
}
#[must_use]
pub fn unsupported_operation(operation: impl Into<String>, variant: impl Into<String>) -> Self {
Self::UnsupportedOperation {
operation: operation.into(),
variant: variant.into(),
}
}
#[must_use]
pub fn incompatible_filters(reason: impl Into<String>) -> Self {
Self::IncompatibleFilters {
reason: reason.into(),
}
}
#[must_use]
pub fn invalid_hash_count(count: usize, min: usize, max: usize) -> Self {
Self::InvalidHashCount { count, min, max }
}
#[must_use]
pub fn invalid_filter_size(size: usize) -> Self {
Self::InvalidFilterSize { size }
}
#[cfg(feature = "serde")]
#[must_use]
pub fn serialization_error(message: impl Into<String>) -> Self {
Self::SerializationError {
message: message.into(),
}
}
#[must_use]
pub fn internal_error(message: impl Into<String>) -> Self {
Self::InternalError {
message: message.into(),
}
}
#[must_use]
pub fn counter_overflow(max_value: u64) -> Self {
Self::CounterOverflow { max_value }
}
#[must_use]
pub fn counter_underflow(min_value: u64) -> Self {
Self::CounterUnderflow { min_value }
}
#[must_use]
pub fn index_out_of_bounds(index: usize, length: usize) -> Self {
Self::IndexOutOfBounds { index, length }
}
#[must_use]
pub fn invalid_range(
start: usize,
end: usize,
length: usize,
reason: impl Into<String>,
) -> Self {
Self::InvalidRange {
start,
end,
length,
reason: reason.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display_invalid_parameters() {
let err = BloomCraftError::invalid_parameters("test message");
let display = format!("{err}");
assert!(display.contains("Invalid Bloom filter parameters"));
assert!(display.contains("test message"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_fp_rate_out_of_bounds() {
let err = BloomCraftError::fp_rate_out_of_bounds(1.5);
let display = format!("{err}");
assert!(display.contains("1.500000"));
assert!(display.contains("out of bounds"));
assert!(display.contains("(0, 1)"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_fp_rate_special_floats() {
let _ = format!("{}", BloomCraftError::fp_rate_out_of_bounds(f64::NAN));
let _ = format!("{}", BloomCraftError::fp_rate_out_of_bounds(f64::INFINITY));
let _ = format!(
"{}",
BloomCraftError::fp_rate_out_of_bounds(f64::NEG_INFINITY)
);
}
#[test]
fn test_error_display_invalid_item_count() {
let err = BloomCraftError::invalid_item_count(0);
let display = format!("{err}");
assert!(display.contains("0"));
assert!(display.contains("greater than 0"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_capacity_exceeded() {
let err = BloomCraftError::capacity_exceeded(1000, 1500);
let display = format!("{err}");
assert!(display.contains("1000"));
assert!(display.contains("1500"));
assert!(display.contains("exceeded"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_max_filters_exceeded() {
let err = BloomCraftError::max_filters_exceeded(64, 64);
let display = format!("{err}");
assert!(display.contains("64"));
assert!(display.contains("sub-filter"));
assert!(display.contains("FPR"));
assert!(!display.contains("items exceeded"));
}
#[test]
fn test_error_display_unsupported_operation() {
let err = BloomCraftError::unsupported_operation("remove", "Standard");
let display = format!("{err}");
assert!(display.contains("remove"));
assert!(display.contains("Standard"));
assert!(display.contains("not supported"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_incompatible_filters() {
let err = BloomCraftError::incompatible_filters("different sizes");
let display = format!("{err}");
assert!(display.contains("incompatible"));
assert!(display.contains("different sizes"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_invalid_hash_count() {
let err = BloomCraftError::invalid_hash_count(0, 1, 32);
let display = format!("{err}");
assert!(display.contains("0"));
assert!(display.contains("[1, 32]"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_invalid_filter_size() {
let err = BloomCraftError::invalid_filter_size(0);
let display = format!("{err}");
assert!(display.contains("0 bits"));
assert!(display.contains("positive"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_index_out_of_bounds() {
let err = BloomCraftError::index_out_of_bounds(150, 100);
let display = format!("{err}");
assert!(display.contains("150"));
assert!(display.contains("100"));
assert!(display.contains("out of bounds"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_invalid_range() {
let err = BloomCraftError::invalid_range(50, 150, 100, "end exceeds length");
let display = format!("{err}");
assert!(display.contains("[50..150)"));
assert!(display.contains("100"));
assert!(display.contains("end exceeds length"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_counter_overflow() {
let err = BloomCraftError::counter_overflow(u64::MAX);
let display = format!("{err}");
assert!(display.contains("overflow"));
assert!(display.contains(&u64::MAX.to_string()));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_counter_underflow() {
let err = BloomCraftError::counter_underflow(0);
let display = format!("{err}");
assert!(display.contains("underflow"));
assert!(display.contains("0"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_internal_error() {
let err = BloomCraftError::internal_error("impossible state reached");
let display = format!("{err}");
assert!(display.contains("Internal error"));
assert!(display.contains("bug"));
assert!(display.contains("impossible state reached"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_display_serialization_error() {
let err = BloomCraftError::SerializationError {
message: "unexpected EOF".to_string(),
};
let display = format!("{err}");
assert!(display.contains("Serialization error"));
assert!(display.contains("unexpected EOF"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_implements_std_error() {
let _err: Box<dyn std::error::Error> =
Box::new(BloomCraftError::invalid_parameters("test"));
}
#[test]
fn test_error_downcast_via_box() {
let err: Box<dyn std::error::Error> = Box::new(BloomCraftError::invalid_parameters("test"));
assert!(err.downcast_ref::<BloomCraftError>().is_some());
}
#[test]
fn test_error_source_is_none() {
use std::error::Error;
let variants: &[BloomCraftError] = &[
BloomCraftError::invalid_parameters("x"),
BloomCraftError::fp_rate_out_of_bounds(1.5),
BloomCraftError::invalid_item_count(0),
BloomCraftError::capacity_exceeded(64, 65),
BloomCraftError::max_filters_exceeded(64, 64),
BloomCraftError::unsupported_operation("op", "variant"),
BloomCraftError::incompatible_filters("reason"),
BloomCraftError::invalid_hash_count(0, 1, 10),
BloomCraftError::invalid_filter_size(0),
BloomCraftError::SerializationError {
message: "test".to_string(),
},
BloomCraftError::internal_error("x"),
BloomCraftError::counter_overflow(u64::MAX),
BloomCraftError::counter_underflow(0),
BloomCraftError::index_out_of_bounds(100, 50),
BloomCraftError::invalid_range(50, 150, 100, "end exceeds length"),
];
for e in variants {
assert!(
e.source().is_none(),
"{e:?} unexpectedly has a source — update source() or add a test exemption"
);
}
}
#[test]
fn test_error_clone_and_eq() {
let err1 = BloomCraftError::invalid_parameters("test");
let err2 = err1.clone();
assert_eq!(err1, err2);
}
#[test]
fn test_result_type_alias() {
fn returns_result() -> Result<i32> {
Ok(42)
}
assert_eq!(returns_result().unwrap(), 42);
}
#[test]
fn test_convenience_constructors_compile() {
let _ = BloomCraftError::invalid_parameters("test");
let _ = BloomCraftError::fp_rate_out_of_bounds(1.5);
let _ = BloomCraftError::invalid_item_count(0);
let _ = BloomCraftError::capacity_exceeded(100, 200);
let _ = BloomCraftError::max_filters_exceeded(64, 64);
let _ = BloomCraftError::unsupported_operation("op", "variant");
let _ = BloomCraftError::incompatible_filters("reason");
let _ = BloomCraftError::invalid_hash_count(0, 1, 10);
let _ = BloomCraftError::invalid_filter_size(0);
let _ = BloomCraftError::index_out_of_bounds(100, 50);
let _ = BloomCraftError::invalid_range(50, 150, 100, "end exceeds length");
let _ = BloomCraftError::counter_overflow(u64::MAX);
let _ = BloomCraftError::counter_underflow(0);
let _ = BloomCraftError::internal_error("test");
}
#[cfg(feature = "serde")]
#[test]
fn test_serialization_error_constructor() {
let err = BloomCraftError::serialization_error("bincode: unexpected end of input");
let display = format!("{err}");
assert!(display.contains("Serialization error"));
assert!(display.contains("bincode"));
assert!(display.ends_with('.'));
}
#[test]
fn test_error_propagation_with_question_mark() {
fn inner() -> Result<()> {
Err(BloomCraftError::invalid_item_count(0))
}
fn outer() -> Result<()> {
inner()?;
Ok(())
}
assert!(outer().is_err());
}
#[test]
fn test_non_exhaustive_wildcard_compiles() {
let err = BloomCraftError::internal_error("test");
let _ = match err {
BloomCraftError::InternalError { .. } => "internal",
_ => "other",
};
}
}