paft_decimal/
constrained.rs1use std::fmt;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
6
7use crate::{Decimal, one, serde::canonical_str, to_canonical_string, zero};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct DecimalConstraintError {
12 type_name: &'static str,
13 expected: &'static str,
14 value: Decimal,
15}
16
17impl DecimalConstraintError {
18 #[cfg(not(feature = "bigdecimal"))]
19 const fn new(type_name: &'static str, expected: &'static str, value: &Decimal) -> Self {
20 Self {
21 type_name,
22 expected,
23 value: *value,
24 }
25 }
26
27 #[cfg(feature = "bigdecimal")]
28 fn new(type_name: &'static str, expected: &'static str, value: &Decimal) -> Self {
29 Self {
30 type_name,
31 expected,
32 value: value.clone(),
33 }
34 }
35
36 #[must_use]
38 pub const fn type_name(&self) -> &'static str {
39 self.type_name
40 }
41
42 #[must_use]
44 pub const fn expected(&self) -> &'static str {
45 self.expected
46 }
47
48 #[must_use]
50 pub const fn value(&self) -> &Decimal {
51 &self.value
52 }
53}
54
55impl fmt::Display for DecimalConstraintError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 write!(
58 f,
59 "{} expected {}, got {}",
60 self.type_name, self.expected, self.value
61 )
62 }
63}
64
65impl std::error::Error for DecimalConstraintError {}
66
67fn is_non_negative(value: &Decimal) -> bool {
68 let zero = zero();
69 value >= &zero
70}
71
72fn is_positive(value: &Decimal) -> bool {
73 let zero = zero();
74 value > &zero
75}
76
77fn is_ratio(value: &Decimal) -> bool {
78 let zero = zero();
79 let one = one();
80 value >= &zero && value <= &one
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
85#[cfg_attr(not(feature = "bigdecimal"), derive(Copy))]
86pub struct NonNegativeDecimal(Decimal);
87
88impl NonNegativeDecimal {
89 const EXPECTED: &'static str = "a decimal greater than or equal to 0";
90
91 pub fn new(value: Decimal) -> Result<Self, DecimalConstraintError> {
96 if is_non_negative(&value) {
97 Ok(Self(value))
98 } else {
99 Err(DecimalConstraintError::new(
100 "NonNegativeDecimal",
101 Self::EXPECTED,
102 &value,
103 ))
104 }
105 }
106
107 #[must_use]
109 pub const fn as_decimal(&self) -> &Decimal {
110 &self.0
111 }
112
113 #[must_use]
115 #[cfg(not(feature = "bigdecimal"))]
116 pub const fn into_inner(self) -> Decimal {
117 self.0
118 }
119
120 #[must_use]
122 #[cfg(feature = "bigdecimal")]
123 pub fn into_inner(self) -> Decimal {
124 self.0
125 }
126}
127
128impl AsRef<Decimal> for NonNegativeDecimal {
129 fn as_ref(&self) -> &Decimal {
130 self.as_decimal()
131 }
132}
133
134impl TryFrom<Decimal> for NonNegativeDecimal {
135 type Error = DecimalConstraintError;
136
137 fn try_from(value: Decimal) -> Result<Self, Self::Error> {
138 Self::new(value)
139 }
140}
141
142impl From<NonNegativeDecimal> for Decimal {
143 fn from(value: NonNegativeDecimal) -> Self {
144 value.into_inner()
145 }
146}
147
148impl fmt::Display for NonNegativeDecimal {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.write_str(&to_canonical_string(&self.0))
151 }
152}
153
154impl Serialize for NonNegativeDecimal {
155 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156 where
157 S: Serializer,
158 {
159 canonical_str::serialize(&self.0, serializer)
160 }
161}
162
163impl<'de> Deserialize<'de> for NonNegativeDecimal {
164 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165 where
166 D: Deserializer<'de>,
167 {
168 let value = canonical_str::deserialize(deserializer)?;
169 Self::new(value).map_err(de::Error::custom)
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
175#[cfg_attr(not(feature = "bigdecimal"), derive(Copy))]
176pub struct PositiveDecimal(Decimal);
177
178impl PositiveDecimal {
179 const EXPECTED: &'static str = "a decimal greater than 0";
180
181 pub fn new(value: Decimal) -> Result<Self, DecimalConstraintError> {
186 if is_positive(&value) {
187 Ok(Self(value))
188 } else {
189 Err(DecimalConstraintError::new(
190 "PositiveDecimal",
191 Self::EXPECTED,
192 &value,
193 ))
194 }
195 }
196
197 #[must_use]
199 pub const fn as_decimal(&self) -> &Decimal {
200 &self.0
201 }
202
203 #[must_use]
205 #[cfg(not(feature = "bigdecimal"))]
206 pub const fn into_inner(self) -> Decimal {
207 self.0
208 }
209
210 #[must_use]
212 #[cfg(feature = "bigdecimal")]
213 pub fn into_inner(self) -> Decimal {
214 self.0
215 }
216}
217
218impl AsRef<Decimal> for PositiveDecimal {
219 fn as_ref(&self) -> &Decimal {
220 self.as_decimal()
221 }
222}
223
224impl TryFrom<Decimal> for PositiveDecimal {
225 type Error = DecimalConstraintError;
226
227 fn try_from(value: Decimal) -> Result<Self, Self::Error> {
228 Self::new(value)
229 }
230}
231
232impl From<PositiveDecimal> for Decimal {
233 fn from(value: PositiveDecimal) -> Self {
234 value.into_inner()
235 }
236}
237
238impl From<PositiveDecimal> for NonNegativeDecimal {
239 fn from(value: PositiveDecimal) -> Self {
240 Self(value.into_inner())
241 }
242}
243
244impl fmt::Display for PositiveDecimal {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 f.write_str(&to_canonical_string(&self.0))
247 }
248}
249
250impl Serialize for PositiveDecimal {
251 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
252 where
253 S: Serializer,
254 {
255 canonical_str::serialize(&self.0, serializer)
256 }
257}
258
259impl<'de> Deserialize<'de> for PositiveDecimal {
260 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
261 where
262 D: Deserializer<'de>,
263 {
264 let value = canonical_str::deserialize(deserializer)?;
265 Self::new(value).map_err(de::Error::custom)
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
271#[cfg_attr(not(feature = "bigdecimal"), derive(Copy))]
272pub struct Ratio(Decimal);
273
274impl Ratio {
275 const EXPECTED: &'static str = "a decimal between 0 and 1 inclusive";
276
277 pub fn new(value: Decimal) -> Result<Self, DecimalConstraintError> {
282 if is_ratio(&value) {
283 Ok(Self(value))
284 } else {
285 Err(DecimalConstraintError::new("Ratio", Self::EXPECTED, &value))
286 }
287 }
288
289 #[must_use]
291 pub const fn as_decimal(&self) -> &Decimal {
292 &self.0
293 }
294
295 #[must_use]
297 #[cfg(not(feature = "bigdecimal"))]
298 pub const fn into_inner(self) -> Decimal {
299 self.0
300 }
301
302 #[must_use]
304 #[cfg(feature = "bigdecimal")]
305 pub fn into_inner(self) -> Decimal {
306 self.0
307 }
308}
309
310impl AsRef<Decimal> for Ratio {
311 fn as_ref(&self) -> &Decimal {
312 self.as_decimal()
313 }
314}
315
316impl TryFrom<Decimal> for Ratio {
317 type Error = DecimalConstraintError;
318
319 fn try_from(value: Decimal) -> Result<Self, Self::Error> {
320 Self::new(value)
321 }
322}
323
324impl From<Ratio> for Decimal {
325 fn from(value: Ratio) -> Self {
326 value.into_inner()
327 }
328}
329
330impl From<Ratio> for NonNegativeDecimal {
331 fn from(value: Ratio) -> Self {
332 Self(value.into_inner())
333 }
334}
335
336impl fmt::Display for Ratio {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 f.write_str(&to_canonical_string(&self.0))
339 }
340}
341
342impl Serialize for Ratio {
343 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
344 where
345 S: Serializer,
346 {
347 canonical_str::serialize(&self.0, serializer)
348 }
349}
350
351impl<'de> Deserialize<'de> for Ratio {
352 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
353 where
354 D: Deserializer<'de>,
355 {
356 let value = canonical_str::deserialize(deserializer)?;
357 Self::new(value).map_err(de::Error::custom)
358 }
359}