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 }