use nutype::nutype;
use std::num::NonZeroUsize;
#[nutype(
validate(greater = 0),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Display,
TryFrom,
AsRef,
Deref,
Serialize,
Deserialize
)
)]
#[doc(alias = "pool_size")]
#[doc(alias = "connection_limit")]
pub struct MaxConnections(usize);
impl MaxConnections {
pub const DEFAULT: usize = 10;
#[inline]
#[must_use]
pub fn get(&self) -> usize {
self.into_inner()
}
}
#[nutype(
validate(greater = 0),
derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Display,
TryFrom,
AsRef,
Deref,
Serialize,
Deserialize
)
)]
pub struct MaxErrors(u32);
impl MaxErrors {
pub const DEFAULT: u32 = 3;
#[inline]
#[must_use]
pub fn get(&self) -> u32 {
self.into_inner()
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ThreadCount(NonZeroUsize);
impl ThreadCount {
pub const DEFAULT: Self = Self(NonZeroUsize::new(1).unwrap());
#[must_use]
pub const fn new(value: usize) -> Option<Self> {
if value == 0 {
None
} else {
match NonZeroUsize::new(value) {
Some(nz) => Some(Self(nz)),
None => None,
}
}
}
#[must_use]
pub fn from_value(value: usize) -> Option<Self> {
if value == 0 {
Some(Self::num_cpus())
} else {
NonZeroUsize::new(value).map(Self)
}
}
#[must_use]
#[inline]
pub const fn get(&self) -> usize {
self.0.get()
}
fn num_cpus() -> Self {
let count = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
Self(NonZeroUsize::new(count).unwrap())
}
}
impl Default for ThreadCount {
fn default() -> Self {
Self(NonZeroUsize::new(1).unwrap())
}
}
impl std::fmt::Display for ThreadCount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.get())
}
}
impl From<ThreadCount> for usize {
fn from(val: ThreadCount) -> Self {
val.get()
}
}
impl serde::Serialize for ThreadCount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u64(self.get() as _)
}
}
impl<'de> serde::Deserialize<'de> for ThreadCount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = usize::deserialize(deserializer)?;
Self::from_value(value).ok_or_else(|| {
serde::de::Error::custom("ThreadCount must be a positive integer (0 means auto-detect)")
})
}
}
impl std::str::FromStr for ThreadCount {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = s.parse::<usize>()?;
Ok(Self::from_value(value).unwrap_or_else(Self::default))
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_max_connections_valid_range(value in 1usize..=10000) {
let max = MaxConnections::try_new(value).unwrap();
prop_assert_eq!(max.get(), value);
}
#[test]
fn prop_max_connections_display(value in 1usize..=10000) {
let max = MaxConnections::try_new(value).unwrap();
prop_assert_eq!(max.to_string(), value.to_string());
}
#[test]
fn prop_max_connections_serde_json(value in 1usize..=10000) {
let max = MaxConnections::try_new(value).unwrap();
let json = serde_json::to_string(&max).unwrap();
let parsed: MaxConnections = serde_json::from_str(&json).unwrap();
prop_assert_eq!(parsed.get(), value);
}
#[test]
fn prop_max_connections_clone(value in 1usize..=10000) {
let max = MaxConnections::try_new(value).unwrap();
let cloned = max;
prop_assert_eq!(max, cloned);
}
}
#[test]
fn test_max_connections_zero_rejected() {
assert!(MaxConnections::try_new(0).is_err());
}
#[test]
fn test_max_connections_default() {
assert_eq!(MaxConnections::DEFAULT, 10);
}
#[test]
fn test_max_connections_serde_json_zero_rejected() {
assert!(serde_json::from_str::<MaxConnections>("0").is_err());
}
proptest! {
#[test]
fn prop_max_errors_valid_range(value in 1u32..=1000) {
let max = MaxErrors::try_new(value).unwrap();
prop_assert_eq!(max.get(), value);
}
#[test]
fn prop_max_errors_display(value in 1u32..=1000) {
let max = MaxErrors::try_new(value).unwrap();
prop_assert_eq!(max.to_string(), value.to_string());
}
#[test]
fn prop_max_errors_serde_json(value in 1u32..=1000) {
let max = MaxErrors::try_new(value).unwrap();
let json = serde_json::to_string(&max).unwrap();
let parsed: MaxErrors = serde_json::from_str(&json).unwrap();
prop_assert_eq!(parsed.get(), value);
}
#[test]
fn prop_max_errors_clone(value in 1u32..=1000) {
let max = MaxErrors::try_new(value).unwrap();
let cloned = max;
prop_assert_eq!(max, cloned);
}
}
#[test]
fn test_max_errors_zero_rejected() {
assert!(MaxErrors::try_new(0).is_err());
}
#[test]
fn test_max_errors_default() {
assert_eq!(MaxErrors::DEFAULT, 3);
}
#[test]
fn test_max_errors_serde_json_zero_rejected() {
assert!(serde_json::from_str::<MaxErrors>("0").is_err());
}
proptest! {
#[test]
fn prop_thread_count_valid_range(value in 1usize..=128) {
let threads = ThreadCount::new(value).unwrap();
prop_assert_eq!(threads.get(), value);
}
#[test]
fn prop_thread_count_from_value(value in 1usize..=128) {
let threads = ThreadCount::from_value(value).unwrap();
prop_assert_eq!(threads.get(), value);
}
#[test]
fn prop_thread_count_display(value in 1usize..=128) {
let threads = ThreadCount::new(value).unwrap();
prop_assert_eq!(threads.to_string(), value.to_string());
}
#[test]
fn prop_thread_count_into_usize(value in 1usize..=128) {
let threads = ThreadCount::new(value).unwrap();
let converted: usize = threads.into();
prop_assert_eq!(converted, value);
}
#[test]
fn prop_thread_count_from_str(value in 1usize..=128) {
let s = value.to_string();
let threads: ThreadCount = s.parse().unwrap();
prop_assert_eq!(threads.get(), value);
}
#[test]
fn prop_thread_count_serde_json(value in 1usize..=128) {
let threads = ThreadCount::new(value).unwrap();
let json = serde_json::to_string(&threads).unwrap();
let parsed: ThreadCount = serde_json::from_str(&json).unwrap();
prop_assert_eq!(parsed.get(), value);
}
#[test]
fn prop_thread_count_clone(value in 1usize..=128) {
let threads = ThreadCount::new(value).unwrap();
let cloned = threads;
prop_assert_eq!(threads, cloned);
}
}
#[test]
fn test_thread_count_default() {
assert_eq!(ThreadCount::default().get(), 1);
}
#[test]
fn test_thread_count_new_zero_rejected() {
assert!(ThreadCount::new(0).is_none());
}
#[test]
fn test_thread_count_from_value_zero_auto_detects() {
let threads = ThreadCount::from_value(0).unwrap();
assert!(threads.get() >= 1);
}
#[test]
fn test_thread_count_from_str_zero_auto_detects() {
let threads: ThreadCount = "0".parse().unwrap();
assert!(threads.get() >= 1);
}
#[test]
fn test_thread_count_from_str_invalid() {
assert!("not_a_number".parse::<ThreadCount>().is_err());
}
#[test]
fn test_thread_count_serde_json_zero_auto_detects() {
let parsed: ThreadCount = serde_json::from_str("0").unwrap();
assert!(parsed.get() >= 1);
}
}