1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// This is free and unencumbered software released into the public domain.
use crate::{
DecimalType,
derived::{Byte, Int, Integer, Long, Short},
primitive::Decimal,
};
use strum_macros::Display;
/// Value representation for `xsd:decimal` datatypes.
///
/// [`Display`](core::fmt::Display) writes the contained number without a datatype
/// label, including when the `alloc` feature is disabled.
///
/// See: <https://www.w3.org/TR/xmlschema-2/#built-in-datatypes>
#[derive(Clone, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(
feature = "borsh",
derive(borsh::BorshSerialize, borsh::BorshDeserialize)
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DecimalValue {
/// See: <https://www.w3.org/TR/xmlschema-2/#decimal>
#[strum(transparent)]
Decimal(Decimal),
/// See: <https://www.w3.org/TR/xmlschema-2/#integer>
#[strum(transparent)]
Integer(Integer),
/// See: <https://www.w3.org/TR/xmlschema-2/#long>
#[strum(transparent)]
Long(Long),
/// See: <https://www.w3.org/TR/xmlschema-2/#int>
#[strum(transparent)]
Int(Int),
/// See: <https://www.w3.org/TR/xmlschema-2/#short>
#[strum(transparent)]
Short(Short),
/// See: <https://www.w3.org/TR/xmlschema-2/#byte>
#[strum(transparent)]
Byte(Byte),
}
impl DecimalValue {
pub fn as_f64(&self) -> f64 {
use DecimalValue::*;
match self {
Decimal(d) => d.as_f64(),
Integer(n) => n.as_f64(),
Long(n) => *n as _,
Int(n) => *n as _,
Short(n) => *n as _,
Byte(n) => *n as _,
}
}
pub fn to_f64(&self) -> Option<f64> {
Some(self.as_f64())
}
pub fn r#type(&self) -> DecimalType {
use DecimalValue::*;
match self {
Decimal(_) => DecimalType::Decimal,
Integer(_) => DecimalType::Integer,
Long(_) => DecimalType::Long,
Int(_) => DecimalType::Int,
Short(_) => DecimalType::Short,
Byte(_) => DecimalType::Byte,
}
}
/// Upcasts this value to its immediate base type, preserving its exact number.
///
/// Returns `None` for a decimal (already the base type), or when an integer
/// cannot be represented exactly by the decimal backend. The current backend
/// supports integer magnitudes up to `79228162514264337593543950335`.
/// Although XSD decimal contains the integer value space, this bounded Rust
/// representation does not. Out-of-range widening returns `None` rather than
/// panicking. Requires neither allocation nor `std`.
///
/// ```
/// use xsd::DecimalValue;
/// assert_eq!(DecimalValue::Byte(42).widen(), Some(DecimalValue::Short(42)));
/// assert!(DecimalValue::from(i128::MAX).widen().is_none());
/// ```
pub fn widen(&self) -> Option<Self> {
use DecimalValue::*;
match self {
Decimal(_) => None, // already the widest primitive base type
Integer(n) => {
use core::fmt::Write;
// Avoid the backend's infallible integer conversion, which can
// panic. An i128 needs at most 39 digits and one minus sign.
let mut lexical = heapless::String::<40>::new();
write!(&mut lexical, "{n}").ok()?;
let decimal = lexical.parse::<crate::primitive::Decimal>().ok()?;
// Guard exactness as well as range if backend behavior changes.
let recovered = i128::try_from(&decimal).ok()?;
(crate::derived::Integer::from(recovered) == *n).then_some(Decimal(decimal))
},
Long(n) => Some(Integer(n.into())),
Int(n) => Some(Long(*n as _)),
Short(n) => Some(Int(*n as _)),
Byte(n) => Some(Short(*n as _)),
}
}
/// Downcasts the type of this value to a compatible narrower derived
/// type, if feasible.
pub fn narrow(&self) -> Option<Self> {
use DecimalValue::*;
match self {
Decimal(d) if !d.is_integer() => None,
Decimal(d) => i128::try_from(d).ok().map(|z| Self::Integer(z.into())),
Integer(n) => i64::try_from(*n).ok().map(Self::Long),
Long(n) => i32::try_from(*n).ok().map(Self::Int),
Int(n) => i16::try_from(*n).ok().map(Self::Short),
Short(n) => i8::try_from(*n).ok().map(Self::Byte),
Byte(_) => None, // already the narrowest derived type
}
}
/// Clones and converts this value using [`Self::into_json`]. Requires `serde`.
/// Always returns `Some`; decimal and large integer values use strings.
#[cfg(feature = "serde")]
pub fn to_json(&self) -> Option<serde_json::Value> {
Some(self.clone().into_json())
}
/// Converts to an untagged JSON value. Requires `serde`.
///
/// Integer-family values use exact JSON integer numbers within the `i64`
/// range; larger `Integer` values use decimal strings. This replaces the
/// former lossy `f64` encoding of `Integer`. Consumers that parse all JSON
/// numbers as binary64 may still lose precision beyond 53 bits.
/// Decimal values always use strings containing the stored number's decimal
/// spelling, preserving its precision without a binary floating-point step.
/// This replaces the former JSON number encoding of decimals. It cannot
/// recover precision already lost while parsing or constructing the value.
/// Datatype identity and original lexical spelling are not retained; retain
/// the datatype separately to parse the number or string back into XSD.
/// This differs from the enum's derived Serde serialization.
#[cfg(feature = "serde")]
pub fn into_json(self) -> serde_json::Value {
use DecimalValue::*;
use alloc::string::ToString;
match self {
Decimal(r) => serde_json::Value::String(r.to_string()),
Integer(z) => match z.to_i64() {
Some(n) => n.into(),
None => serde_json::Value::String(z.to_string()),
},
Long(z) => z.into(),
Int(z) => z.into(),
Short(z) => z.into(),
Byte(z) => z.into(),
}
}
/// Clones and converts using [`Self::into_bson`]. Requires `bson`.
/// Always returns `Some`, using strings when an integer exceeds Decimal128 precision.
#[cfg(feature = "bson")]
pub fn to_bson(&self) -> Option<bson::Bson> {
Some(self.clone().into_bson())
}
/// Converts to untagged BSON. Requires `bson` (and thus `std`).
///
/// `Byte`, `Short`, and `Int` use `Int32`; `Long` and integers fitting `i64`
/// use `Int64`. Other integers use `Decimal128` if exactly representable,
/// otherwise decimal strings. Decimal values use strings preserving the
/// stored precision. The string fallback replaces the former panic for
/// integers such as `i128::MAX`; it never rounds to fit Decimal128.
/// The `From<DecimalValue> for Bson` route uses this same policy, replacing
/// its former lossy `Double` encoding. Datatype identity is not retained.
///
/// Retain the XSD datatype separately to decode numbers and strings back
/// into XSD. This method does not retain original lexical spelling and is
/// separate from the enum's derived Serde serialization.
#[cfg(feature = "bson")]
pub fn into_bson(self) -> bson::Bson {
use DecimalValue::*;
use alloc::string::ToString;
use bson::{Bson, Decimal128};
match self {
Decimal(r) => Bson::String(r.to_string()),
Integer(z) => {
if let Some(n) = z.to_i64() {
return Bson::Int64(n);
}
let lexical = z.to_string();
match lexical.parse::<Decimal128>() {
Ok(number) => Bson::Decimal128(number),
Err(_) => Bson::String(lexical),
}
},
Long(n) => Bson::Int64(n),
Int(n) => Bson::Int32(n),
Short(n) => Bson::Int32(n.into()),
Byte(n) => Bson::Int32(n.into()),
}
}
}
impl<T> From<&T> for DecimalValue
where
T: Clone + Into<Self>,
{
fn from(t: &T) -> Self {
t.clone().into()
}
}
impl From<i8> for DecimalValue {
fn from(input: i8) -> Self {
Self::Byte(input.into())
}
}
impl From<i16> for DecimalValue {
fn from(input: i16) -> Self {
Self::Short(input.into())
}
}
impl From<i32> for DecimalValue {
fn from(input: i32) -> Self {
Self::Int(input.into())
}
}
impl From<i64> for DecimalValue {
fn from(input: i64) -> Self {
Self::Long(input.into())
}
}
impl From<i128> for DecimalValue {
fn from(input: i128) -> Self {
Self::Integer(input.into())
}
}
impl From<isize> for DecimalValue {
fn from(input: isize) -> Self {
Self::Integer((input as i128).into()) // TODO
}
}
impl From<Integer> for DecimalValue {
fn from(input: Integer) -> Self {
Self::Integer(input)
}
}
impl From<Decimal> for DecimalValue {
fn from(input: Decimal) -> Self {
Self::Decimal(input)
}
}
#[cfg(feature = "rust_decimal")]
impl From<rust_decimal::Decimal> for DecimalValue {
fn from(input: rust_decimal::Decimal) -> Self {
Self::Decimal(input.into())
}
}
impl TryFrom<f32> for DecimalValue {
type Error = valuand::DecimalError;
fn try_from(input: f32) -> Result<Self, Self::Error> {
Ok(Self::Decimal(input.try_into()?))
}
}
impl TryFrom<f64> for DecimalValue {
type Error = valuand::DecimalError;
fn try_from(input: f64) -> Result<Self, Self::Error> {
Ok(Self::Decimal(input.try_into()?))
}
}
#[cfg(feature = "serde")]
impl From<DecimalValue> for serde_json::Value {
fn from(input: DecimalValue) -> Self {
input.into_json()
}
}
#[cfg(feature = "serde")]
impl From<&DecimalValue> for serde_json::Value {
fn from(input: &DecimalValue) -> Self {
input.clone().into_json()
}
}
#[cfg(feature = "bson")]
impl From<DecimalValue> for bson::Bson {
/// Uses [`DecimalValue::into_bson`], including its exact string fallback.
fn from(input: DecimalValue) -> Self {
input.into_bson()
}
}