use chrono::{DateTime, Utc};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{Error, TimeUnit};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct IntervalConfig {
bucket_count: usize,
time_unit: TimeUnit,
}
impl IntervalConfig {
#[allow(dead_code)] pub fn new(bucket_count: usize, time_unit: TimeUnit) -> crate::Result<Self> {
validate(bucket_count, time_unit)?;
Ok(Self {
bucket_count,
time_unit,
})
}
pub fn new_unchecked(bucket_count: usize, time_unit: TimeUnit) -> Self {
Self {
bucket_count,
time_unit,
}
}
pub(crate) fn validate(&self) -> crate::Result<()> {
validate(self.bucket_count, self.time_unit)
}
pub(crate) fn bucket_count(&self) -> usize {
self.bucket_count
}
pub(crate) fn time_unit(&self) -> TimeUnit {
self.time_unit
}
pub(crate) fn first_moment_ever(&self, now: DateTime<Utc>) -> DateTime<Utc> {
self.time_unit.first_moment_ever(now, self.bucket_count)
}
}
fn validate(bucket_count: usize, time_unit: TimeUnit) -> crate::Result<()> {
if bucket_count == 0 {
return Err(Error::InvalidBucketCount(
"bucket_count must be positive and non-zero".to_string(),
));
}
if bucket_count > 100_000 {
return Err(Error::InvalidBucketCount(
"bucket_count must be sane".to_string(),
));
}
if time_unit == TimeUnit::Ever {
return Err(Error::InvalidTimeUnitEver);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interval_config_creation() {
let config = IntervalConfig::new(256, TimeUnit::Days).unwrap();
assert_eq!(config.bucket_count(), 256);
assert_eq!(config.time_unit(), TimeUnit::Days);
}
#[test]
fn test_interval_config_zero_buckets_rejected() {
let result = IntervalConfig::new(0, TimeUnit::Days);
assert!(result.is_err());
if let Err(crate::Error::InvalidBucketCount(msg)) = result {
assert!(msg.contains("positive"));
} else {
panic!("Expected InvalidBucketCount error");
}
}
#[test]
fn test_interval_config_is_clone() {
let config = IntervalConfig::new(128, TimeUnit::Hours).unwrap();
let config2 = config.clone();
assert_eq!(config.bucket_count(), config2.bucket_count());
assert_eq!(config.time_unit(), config2.time_unit());
}
#[test]
fn test_interval_config_debug() {
let config = IntervalConfig::new(100, TimeUnit::Minutes).unwrap();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("bucket_count"));
assert!(debug_str.contains("time_unit"));
}
#[test]
fn test_interval_config_accessors() {
let config = IntervalConfig::new(42, TimeUnit::Weeks).unwrap();
assert_eq!(config.bucket_count(), 42);
assert_eq!(config.time_unit(), TimeUnit::Weeks);
}
#[test]
fn test_interval_config_rejects_ever() {
let result = IntervalConfig::new(256, TimeUnit::Ever);
assert!(result.is_err());
if let Err(crate::Error::InvalidTimeUnitEver) = result {
} else {
panic!("Expected InvalidTimeUnitEver error");
}
}
#[test]
fn test_interval_config_ever_error_message() {
let result = IntervalConfig::new(256, TimeUnit::Ever);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("TimeUnit::Ever"));
assert!(err_msg.contains("query-time sentinel"));
}
}