Skip to content

Commit a1a5cc0

Browse files
committed
Support rust_decimal.
1 parent 195318d commit a1a5cc0

6 files changed

Lines changed: 119 additions & 8 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ arrayvec = { version = "0.7", default-features = false, optional = true }
1818
bitcode_derive = { version = "0.6.3", path = "./bitcode_derive", optional = true }
1919
bytemuck = { version = "1.14", features = [ "min_const_generics", "must_cast" ] }
2020
glam = { version = ">=0.21", default-features = false, optional = true }
21+
rust_decimal = { version = "1.36", default-features = false, optional = true }
2122
serde = { version = "1.0", default-features = false, features = [ "alloc" ], optional = true }
2223

2324
[dev-dependencies]
@@ -37,7 +38,7 @@ zstd = "0.13.0"
3738

3839
[features]
3940
derive = [ "dep:bitcode_derive" ]
40-
std = [ "serde?/std", "glam?/std", "arrayvec?/std" ]
41+
std = [ "serde?/std", "glam?/std", "arrayvec?/std", "rust_decimal?/std" ]
4142
default = [ "derive", "std" ]
4243

4344
[package.metadata.docs.rs]

fuzz/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ cargo-fuzz = true
1010

1111
[dependencies]
1212
arrayvec = { version = "0.7", features = ["serde"] }
13-
bitcode = { path = "..", features = [ "arrayvec", "serde" ] }
13+
bitcode = { path = "..", features = [ "arrayvec", "serde", "rust_decimal" ] }
1414
libfuzzer-sys = "0.4"
15+
rust_decimal = "1.36.0"
1516
serde = { version ="1.0", features = [ "derive" ] }
1617

1718
# Prevent this from interfering with workspaces

fuzz/fuzz_targets/fuzz.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::collections::{BTreeMap, HashMap};
99
use std::fmt::Debug;
1010
use std::num::NonZeroU32;
1111
use std::time::Duration;
12+
use rust_decimal::Decimal;
1213
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddr, SocketAddrV6};
1314

1415
#[inline(never)]
@@ -209,6 +210,7 @@ fuzz_target!(|data: &[u8]| {
209210
ArrayString<70>,
210211
ArrayVec<u8, 5>,
211212
ArrayVec<u8, 70>,
213+
Decimal,
212214
Duration,
213215
Ipv4Addr,
214216
Ipv6Addr,

src/ext/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ mod arrayvec;
33
#[cfg(feature = "glam")]
44
#[rustfmt::skip] // Makes impl_struct! calls way longer.
55
mod glam;
6+
#[cfg(feature = "rust_decimal")]
7+
mod rust_decimal;
68

79
#[allow(unused)]
810
macro_rules! impl_struct {

src/ext/rust_decimal.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use crate::{
2+
convert::{self, ConvertFrom},
3+
Decode, Encode,
4+
};
5+
use bytemuck::CheckedBitPattern;
6+
use rust_decimal::Decimal;
7+
8+
type DecimalConversion = (u32, u32, u32, Flags);
9+
10+
impl ConvertFrom<&Decimal> for DecimalConversion {
11+
fn convert_from(value: &Decimal) -> Self {
12+
let unpacked = value.unpack();
13+
(
14+
unpacked.lo,
15+
unpacked.mid,
16+
unpacked.hi,
17+
Flags::new(unpacked.scale, unpacked.negative),
18+
)
19+
}
20+
}
21+
22+
impl ConvertFrom<DecimalConversion> for Decimal {
23+
fn convert_from(value: DecimalConversion) -> Self {
24+
Self::from_parts(
25+
value.0,
26+
value.1,
27+
value.2,
28+
value.3.negative(),
29+
value.3.scale(),
30+
)
31+
}
32+
}
33+
34+
impl Encode for Decimal {
35+
type Encoder = convert::ConvertIntoEncoder<DecimalConversion>;
36+
}
37+
38+
impl<'a> Decode<'a> for Decimal {
39+
type Decoder = convert::ConvertFromDecoder<'a, DecimalConversion>;
40+
}
41+
42+
impl ConvertFrom<&Flags> for u8 {
43+
fn convert_from(flags: &Flags) -> Self {
44+
flags.0
45+
}
46+
}
47+
48+
impl Encode for Flags {
49+
type Encoder = convert::ConvertIntoEncoder<u8>;
50+
}
51+
52+
/// A u8 guaranteed to satisfy (flags >> 1) <= 28. Prevents Decimal::from_parts from misbehaving.
53+
#[derive(Copy, Clone)]
54+
#[repr(transparent)]
55+
pub struct Flags(u8);
56+
57+
impl Flags {
58+
#[inline(always)]
59+
fn new(scale: u32, negative: bool) -> Self {
60+
Self((scale as u8) << 1 | negative as u8)
61+
}
62+
63+
#[inline(always)]
64+
fn scale(&self) -> u32 {
65+
(self.0 >> 1) as u32
66+
}
67+
68+
#[inline(always)]
69+
fn negative(&self) -> bool {
70+
self.0 & 1 == 1
71+
}
72+
}
73+
74+
// Safety: u8 and Flags have the same layout since Flags is #[repr(transparent)].
75+
unsafe impl CheckedBitPattern for Flags {
76+
type Bits = u8;
77+
#[inline(always)]
78+
fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
79+
(*bits >> 1) <= 28
80+
}
81+
}
82+
83+
impl<'a> Decode<'a> for Flags {
84+
type Decoder = crate::int::CheckedIntDecoder<'a, Flags, u8>;
85+
}
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use crate::{decode, encode};
90+
use rust_decimal::Decimal;
91+
92+
#[test]
93+
fn rust_decimal() {
94+
let vs = [
95+
Decimal::from(0),
96+
Decimal::from(-1),
97+
Decimal::from(1) / Decimal::from(2),
98+
Decimal::from(1),
99+
Decimal::from(999999999999999999u64),
100+
];
101+
for v in vs {
102+
assert_eq!(decode::<Decimal>(&encode(&v)).unwrap(), v);
103+
}
104+
}
105+
}

src/serde/de.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ macro_rules! specify {
120120
#[rustfmt::skip]
121121
fn cold<'de>(decoder: &mut SerdeDecoder<'de>, input: &mut &'de [u8]) -> Result<()> {
122122
let &mut SerdeDecoder::Unspecified { length } = decoder else {
123-
type_changed!()
124-
};
123+
type_changed!()
124+
};
125125
*decoder = SerdeDecoder::$variant(Default::default());
126126
decoder.populate(input, length)
127127
}
@@ -130,10 +130,10 @@ macro_rules! specify {
130130
}
131131
#[rustfmt::skip]
132132
let SerdeDecoder::$variant(d) = &mut *$self.decoder else {
133-
// Safety: `cold` gets called when decoder isn't the correct decoder. `cold` either
134-
// errors or sets lazy to the correct decoder.
135-
unsafe { core::hint::unreachable_unchecked() };
136-
};
133+
// Safety: `cold` gets called when decoder isn't the correct decoder. `cold` either
134+
// errors or sets lazy to the correct decoder.
135+
unsafe { core::hint::unreachable_unchecked() };
136+
};
137137
d
138138
}};
139139
}

0 commit comments

Comments
 (0)