use std::{cmp::Ordering, fmt};
use serde::{Deserialize, Serialize, de};
use super::{
error::{PercentageCappedValidationError, PercentageValidationError, PriceValidationError},
serde_util,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PercentageCapped(f64);
impl PercentageCapped {
pub const MIN: Self = Self(0.);
pub const MAX: Self = Self(100.);
pub fn bounded<T>(value: T) -> Self
where
T: Into<f64>,
{
let as_f64: f64 = value.into();
let bounded = as_f64.clamp(Self::MIN.0, Self::MAX.0);
Self(bounded)
}
pub fn as_f64(&self) -> f64 {
self.0
}
}
impl TryFrom<u8> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u16> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u32> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u64> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i8> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: i8) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i16> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i32> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i64> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<usize> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<isize> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: isize) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<f32> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<f64> for PercentageCapped {
type Error = PercentageCappedValidationError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
if value < Self::MIN.0 {
return Err(PercentageCappedValidationError::BelowMinimum { value });
}
if value > Self::MAX.0 {
return Err(PercentageCappedValidationError::AboveMaximum { value });
}
Ok(Self(value))
}
}
impl From<PercentageCapped> for f64 {
fn from(value: PercentageCapped) -> f64 {
value.0
}
}
impl Eq for PercentageCapped {}
impl Ord for PercentageCapped {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0
.partial_cmp(&other.0)
.expect("`PercentageCapped` must be finite")
}
}
impl PartialOrd for PercentageCapped {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for PercentageCapped {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Percentage(f64);
impl Percentage {
pub const MIN: Self = Self(0.);
pub const MAX: Self = Self(10_000.);
pub fn bounded<T>(value: T) -> Self
where
T: Into<f64>,
{
let as_f64: f64 = value.into();
let bounded = as_f64.clamp(Self::MIN.0, Self::MAX.0);
Self(bounded)
}
pub fn as_f64(&self) -> f64 {
self.0
}
}
impl TryFrom<u8> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u16> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u32> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u64> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i8> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: i8) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i16> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i32> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i64> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<usize> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<isize> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: isize) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<f32> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<f64> for Percentage {
type Error = PercentageValidationError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
if value < Self::MIN.0 {
return Err(PercentageValidationError::BelowMinimum { value });
}
if value > Self::MAX.0 {
return Err(PercentageValidationError::AboveMaximum { value });
}
Ok(Self(value))
}
}
impl From<Percentage> for f64 {
fn from(value: Percentage) -> f64 {
value.0
}
}
impl Eq for Percentage {}
impl Ord for Percentage {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0
.partial_cmp(&other.0)
.expect("`Percentage` must be finite")
}
}
impl PartialOrd for Percentage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<PercentageCapped> for Percentage {
fn from(value: PercentageCapped) -> Self {
Self(value.0)
}
}
impl fmt::Display for Percentage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Price(f64);
impl Price {
pub const MIN: Self = Self(1.);
pub const MAX: Self = Self(100_000_000.);
pub const TICK: f64 = 0.5;
pub fn round_down<T>(value: T) -> Result<Self, PriceValidationError>
where
T: Into<f64>,
{
let as_f64: f64 = value.into();
let rounded_down = (as_f64 / Self::TICK).floor() * Self::TICK;
Self::try_from(rounded_down)
}
pub fn round_up<T>(value: T) -> Result<Self, PriceValidationError>
where
T: Into<f64>,
{
let as_f64: f64 = value.into();
let rounded_up = (as_f64 / Self::TICK).ceil() * Self::TICK;
Self::try_from(rounded_up)
}
pub fn round<T>(value: T) -> Result<Self, PriceValidationError>
where
T: Into<f64>,
{
let as_f64: f64 = value.into();
let rounded = (as_f64 / Self::TICK).round() * Self::TICK;
Self::try_from(rounded)
}
pub fn bounded<T>(value: T) -> Self
where
T: Into<f64>,
{
let as_f64: f64 = value.into();
let value = as_f64.clamp(Self::MIN.0, Self::MAX.0);
let rounded = (value / Self::TICK).round() * Self::TICK;
Self(rounded)
}
pub fn as_f64(&self) -> f64 {
self.0
}
pub fn apply_discount(
&self,
percentage: PercentageCapped,
) -> Result<Self, PriceValidationError> {
let target_price = self.0 - self.0 * percentage.as_f64() / 100.0;
Self::round(target_price)
}
pub fn apply_gain(&self, percentage: Percentage) -> Result<Self, PriceValidationError> {
let target_price = self.0 + self.0 * percentage.as_f64() / 100.0;
Self::round(target_price)
}
}
impl From<Price> for f64 {
fn from(value: Price) -> f64 {
value.0
}
}
impl TryFrom<u8> for Price {
type Error = PriceValidationError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u16> for Price {
type Error = PriceValidationError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u32> for Price {
type Error = PriceValidationError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<u64> for Price {
type Error = PriceValidationError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i8> for Price {
type Error = PriceValidationError;
fn try_from(value: i8) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i16> for Price {
type Error = PriceValidationError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i32> for Price {
type Error = PriceValidationError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<i64> for Price {
type Error = PriceValidationError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<usize> for Price {
type Error = PriceValidationError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<isize> for Price {
type Error = PriceValidationError;
fn try_from(value: isize) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<f32> for Price {
type Error = PriceValidationError;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<f64> for Price {
type Error = PriceValidationError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
if value < Self::MIN.0 {
return Err(PriceValidationError::TooLow { value });
}
if value > Self::MAX.0 {
return Err(PriceValidationError::TooHigh { value });
}
let calc = value / Self::TICK;
if (calc.round() - calc).abs() > 1e-10 {
return Err(PriceValidationError::NotMultipleOfTick { value });
}
Ok(Price(value))
}
}
impl Eq for Price {}
impl Ord for Price {
fn cmp(&self, other: &Self) -> Ordering {
self.0
.partial_cmp(&other.0)
.expect("`Price` must be finite")
}
}
impl PartialOrd for Price {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Price {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Serialize for Price {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde_util::float_without_decimal::serialize(&self.0, serializer)
}
}
impl<'de> Deserialize<'de> for Price {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let price_f64 = f64::deserialize(deserializer)?;
Price::try_from(price_f64).map_err(|e| de::Error::custom(e.to_string()))
}
}