lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

discount.rs (4092B)


      1 use core::fmt;
      2 
      3 #[cfg(not(feature = "std"))]
      4 use alloc::string::String;
      5 #[cfg(feature = "std")]
      6 use std::string::String;
      7 
      8 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
      9 #[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
     10 #[cfg_attr(all(test, feature = "std"), dto(export))]
     11 #[derive(Clone, Debug, PartialEq, Eq)]
     12 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
     13 pub enum DiscountScope {
     14     Bin,
     15     OrderTotal,
     16 }
     17 
     18 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     19 #[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
     20 #[cfg_attr(all(test, feature = "std"), dto(export))]
     21 #[derive(Clone, Debug, PartialEq, Eq)]
     22 #[cfg_attr(
     23     feature = "serde",
     24     serde(rename_all = "snake_case", tag = "kind", content = "amount")
     25 )]
     26 pub enum DiscountThreshold {
     27     BinCount { bin_id: String, min: u32 },
     28     OrderQuantity { min: crate::Quantity },
     29 }
     30 
     31 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     32 #[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
     33 #[cfg_attr(all(test, feature = "std"), dto(export))]
     34 #[derive(Clone, Debug, PartialEq, Eq)]
     35 #[cfg_attr(
     36     feature = "serde",
     37     serde(rename_all = "snake_case", tag = "kind", content = "amount")
     38 )]
     39 pub enum DiscountValue {
     40     MoneyPerBin(crate::Money),
     41     Percent(crate::Percent),
     42 }
     43 
     44 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
     45 #[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))]
     46 #[cfg_attr(all(test, feature = "std"), dto(export))]
     47 #[derive(Clone, Debug, PartialEq, Eq)]
     48 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
     49 pub struct Discount {
     50     scope: DiscountScope,
     51     threshold: DiscountThreshold,
     52     value: DiscountValue,
     53 }
     54 
     55 #[non_exhaustive]
     56 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     57 pub enum Error {
     58     NegativeThreshold,
     59     NegativeValue,
     60 }
     61 
     62 impl fmt::Display for Error {
     63     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     64         match self {
     65             Self::NegativeThreshold => f.write_str("discount threshold must be non-negative"),
     66             Self::NegativeValue => f.write_str("discount value must be non-negative"),
     67         }
     68     }
     69 }
     70 
     71 #[cfg(feature = "std")]
     72 impl std::error::Error for Error {}
     73 
     74 impl Discount {
     75     #[inline]
     76     pub fn try_new(
     77         scope: DiscountScope,
     78         threshold: DiscountThreshold,
     79         value: DiscountValue,
     80     ) -> Result<Self, Error> {
     81         let discount = Self {
     82             scope,
     83             threshold,
     84             value,
     85         };
     86         discount.validate()?;
     87         Ok(discount)
     88     }
     89 
     90     #[inline]
     91     pub fn scope(&self) -> &DiscountScope {
     92         &self.scope
     93     }
     94 
     95     #[inline]
     96     pub fn threshold(&self) -> &DiscountThreshold {
     97         &self.threshold
     98     }
     99 
    100     #[inline]
    101     pub fn value(&self) -> &DiscountValue {
    102         &self.value
    103     }
    104 
    105     pub fn validate(&self) -> Result<(), Error> {
    106         if let DiscountThreshold::OrderQuantity { min } = &self.threshold {
    107             min.ensure_non_negative()
    108                 .map_err(|_| Error::NegativeThreshold)?;
    109         }
    110         match &self.value {
    111             DiscountValue::MoneyPerBin(money) => money
    112                 .ensure_non_negative()
    113                 .map_err(|_| Error::NegativeValue)?,
    114             DiscountValue::Percent(percent) if percent.value().is_sign_negative() => {
    115                 return Err(Error::NegativeValue);
    116             }
    117             DiscountValue::Percent(_) => {}
    118         }
    119         Ok(())
    120     }
    121 
    122     pub fn is_non_negative(&self) -> bool {
    123         self.validate().is_ok()
    124     }
    125 }
    126 
    127 #[cfg(feature = "serde")]
    128 impl<'de> serde::Deserialize<'de> for Discount {
    129     fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
    130         #[derive(serde::Deserialize)]
    131         struct Wire {
    132             scope: DiscountScope,
    133             threshold: DiscountThreshold,
    134             value: DiscountValue,
    135         }
    136 
    137         let wire = Wire::deserialize(deserializer)?;
    138         Self::try_new(wire.scope, wire.threshold, wire.value).map_err(serde::de::Error::custom)
    139     }
    140 }