use std::cmp::Ordering;
use snafu::{Snafu, ensure};
use crate::{pagination::PageSize, utils::ExampleData};
pub const ITEM_COUNT_MAX: ItemCount = ItemCount(MAX_VALUE);
pub const ITEM_COUNT_DEFAULT: ItemCount = ItemCount(DEFAULT_VALUE);
const DEFAULT_VALUE: i64 = 0;
const ZERO_VALUE: i64 = 0;
const MIN_VALUE: i64 = 0;
const MAX_VALUE: i64 = i64::MAX;
#[derive(Debug, Snafu)]
pub enum TryFromItemCountError {
#[snafu(display(
"Item count is larger than the maximum item count allowed ({item_count_max})."
))]
ValueTooLarge {
item_count_max: i64,
},
#[snafu(display("Item count is negative"))]
ValueNegative,
}
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
derive_more::Display,
derive_more::AsRef,
derive_more::Into,
)]
#[cfg_attr(
feature = "diesel",
derive(
opentalk_diesel_newtype::DieselNewtype,
diesel::expression::AsExpression,
diesel::deserialize::FromSqlRow
)
)]
#[cfg_attr(feature="diesel", diesel(sql_type = diesel::sql_types::BigInt))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "i64")
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(example = json!(ItemCount::example_data())))]
pub struct ItemCount(i64);
impl ItemCount {
pub const MIN: Self = Self(MIN_VALUE);
pub const MAX: Self = Self(MAX_VALUE);
pub const DEFAULT: Self = Self(DEFAULT_VALUE);
pub const ZERO: Self = Self(ZERO_VALUE);
pub const fn saturating_add(self, rhs: Self) -> Self {
Self(self.0.saturating_add(rhs.0))
}
pub const fn saturating_sub(self, rhs: Self) -> Self {
Self(self.0.saturating_sub(rhs.0))
}
pub const fn is_min(&self) -> bool {
self.0 == MIN_VALUE
}
pub const fn is_max(&self) -> bool {
self.0 == MAX_VALUE
}
pub const fn is_zero(&self) -> bool {
self.0 == ZERO_VALUE
}
pub const fn is_default(&self) -> bool {
self.0 == DEFAULT_VALUE
}
}
impl TryFrom<u64> for ItemCount {
type Error = TryFromItemCountError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let value = i64::try_from(value).map_err(|_e| TryFromItemCountError::ValueTooLarge {
item_count_max: MAX_VALUE,
})?;
Self::try_from(value)
}
}
impl TryFrom<usize> for ItemCount {
type Error = TryFromItemCountError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
let value = i64::try_from(value).map_err(|_e| TryFromItemCountError::ValueTooLarge {
item_count_max: MAX_VALUE,
})?;
Self::try_from(value)
}
}
impl TryFrom<i64> for ItemCount {
type Error = TryFromItemCountError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
ensure!(value >= 0, ValueNegativeSnafu);
Ok(Self(value))
}
}
impl From<u32> for ItemCount {
fn from(value: u32) -> Self {
Self(value.into())
}
}
impl From<ItemCount> for usize {
fn from(value: ItemCount) -> Self {
value.0 as usize
}
}
impl From<PageSize> for ItemCount {
fn from(value: PageSize) -> Self {
Self(value.into())
}
}
impl ExampleData for ItemCount {
fn example_data() -> Self {
Self(17)
}
}
impl PartialEq<PageSize> for ItemCount {
fn eq(&self, other: &PageSize) -> bool {
self.0 == i64::from(*other)
}
}
impl PartialOrd<PageSize> for ItemCount {
fn partial_cmp(&self, other: &PageSize) -> Option<Ordering> {
self.0.partial_cmp(&i64::from(*other))
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_matches};
use super::{ItemCount, MAX_VALUE, TryFromItemCountError};
#[test]
fn saturating_add() {
assert_eq!(
ItemCount::from(5423).saturating_add(ItemCount::from(3)),
ItemCount::from(5426)
);
assert_eq!(
ItemCount::MAX.saturating_add(ItemCount::from(3)),
ItemCount::MAX
);
}
#[test]
fn try_from() {
assert_eq!(ItemCount::try_from(0usize).unwrap(), ItemCount::ZERO);
assert_eq!(ItemCount::try_from(0i64).unwrap(), ItemCount::ZERO);
assert_eq!(ItemCount::try_from(0u64).unwrap(), ItemCount::ZERO);
assert_eq!(ItemCount::try_from(14usize).unwrap(), ItemCount(14));
assert_eq!(ItemCount::try_from(16i64).unwrap(), ItemCount(16));
assert_eq!(ItemCount::try_from(18u64).unwrap(), ItemCount(18));
assert_matches!(
ItemCount::try_from(-42i64),
Err(TryFromItemCountError::ValueNegative)
);
assert_matches!(
ItemCount::try_from((i64::MAX as usize) + 1),
Err(TryFromItemCountError::ValueTooLarge {
item_count_max: MAX_VALUE
})
);
assert_matches!(
ItemCount::try_from((i64::MAX as u64) + 1),
Err(TryFromItemCountError::ValueTooLarge {
item_count_max: MAX_VALUE
})
);
}
#[test]
fn from_u32() {
assert_eq!(ItemCount::from(0u32), ItemCount::ZERO);
assert_eq!(ItemCount::from(42u32), ItemCount(42));
}
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use super::ItemCount;
#[test]
fn serialize_default() {
let example = ItemCount::default();
assert_eq!(json!(example), json!(0));
}
#[test]
fn serialize() {
let example = ItemCount::from(423);
assert_eq!(json!(example), json!(423));
}
#[test]
fn deserialize_default() {
let example = ItemCount::default();
assert_eq!(
example,
serde_json::from_value::<ItemCount>(json!(0)).unwrap()
);
}
#[test]
fn deserialize() {
let example = ItemCount::from(64);
assert_eq!(
example,
serde_json::from_value::<ItemCount>(json!(64)).unwrap()
);
}
}