Skip to content

Commit 90adbf7

Browse files
committed
feat: introduce feature to limit integer to i64
By default the IPLD Integer kind is represented internally as an `i128`. Serde has problems with untagged enums that contain i128 types. Therefore a feature flag called `integer-max-i64` is introduced, which reduces the internal integer representation to `i64`. This flag should be used with caution as e.g. not all valid DAG-CBOR data can now be represented. Closes #19.
1 parent 1a2ffc7 commit 90adbf7

9 files changed

+134
-49
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ serde = ["dep:serde", "dep:serde_bytes", "cid/serde"]
2020
arb = ["dep:quickcheck", "cid/arb"]
2121
# Enables support for the Codec trait, needs at least Rust 1.75
2222
codec = []
23+
# Makes the internal representation of an IPLD integer an `i64` instead of the default `i128`. This
24+
# is usefult to work around Serde limitations in regards to untagged enums that contain `i128`
25+
# types. **Warning** enabling this feature might break compatibility with existing data.
26+
integer-max-i64 = []
2327

2428
[dependencies]
2529
cid = { version = "0.11.1", default-features = false, features = ["alloc"] }

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Feature flags
8989
- `codec` (enabled by default): Provides the `Codec` trait, which enables encoding and decoding independent of the IPLD Codec. The minimum supported Rust version (MSRV) can significantly be reduced to 1.64 by disabling this feature.
9090
- `serde`: Enables support for Serde serialization into/deserialization from the `Ipld` enum.
9191
- `arb`: Enables support for property based testing.
92+
- `integer-max-i64`: The IPLD integer type is by default an `i128`. With this feature set it's an `i64`. This is useful to work around Serde limitations in regards to untagged enums that contain `i128` types. **Warning** enabling this feature might break compatibility with existing data.
9293

9394

9495
License

src/arb.rs

+3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ impl Ipld {
3636
match index {
3737
0 => Ipld::Null,
3838
1 => Ipld::Bool(bool::arbitrary(g)),
39+
#[cfg(not(feature = "integer-max-i64"))]
3940
2 => Ipld::Integer(i128::arbitrary(g)),
41+
#[cfg(feature = "integer-max-i64")]
42+
2 => Ipld::Integer(i64::arbitrary(g)),
4043
3 => Ipld::Float(f64::arbitrary(g)),
4144
4 => Ipld::String(String::arbitrary(g)),
4245
5 => Ipld::Bytes(Vec::arbitrary(g)),

src/convert.rs

+29-18
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,21 @@ mod tests {
217217

218218
use crate::ipld::Ipld;
219219

220+
#[cfg(not(feature = "integer-max-i64"))]
221+
type Integer = i128;
222+
#[cfg(feature = "integer-max-i64")]
223+
type Integer = i64;
224+
220225
#[test]
221226
#[should_panic]
222227
fn try_into_wrong_type() {
223-
let _boolean: bool = Ipld::Integer(u8::MAX as i128).try_into().unwrap();
228+
let _boolean: bool = Ipld::Integer(u8::MAX as Integer).try_into().unwrap();
224229
}
225230

226231
#[test]
227232
#[should_panic]
228233
fn try_into_wrong_range() {
229-
let int: u128 = Ipld::Integer(-1i128).try_into().unwrap();
234+
let int: u128 = Ipld::Integer(-1 as Integer).try_into().unwrap();
230235
assert_eq!(int, u128::MIN);
231236
}
232237

@@ -241,41 +246,47 @@ mod tests {
241246

242247
#[test]
243248
fn try_into_ints() {
244-
let int: u8 = Ipld::Integer(u8::MAX as i128).try_into().unwrap();
249+
let int: u8 = Ipld::Integer(u8::MAX as Integer).try_into().unwrap();
245250
assert_eq!(int, u8::MAX);
246251

247-
let int: u16 = Ipld::Integer(u16::MAX as i128).try_into().unwrap();
252+
let int: u16 = Ipld::Integer(u16::MAX as Integer).try_into().unwrap();
248253
assert_eq!(int, u16::MAX);
249254

250-
let int: u32 = Ipld::Integer(u32::MAX as i128).try_into().unwrap();
255+
let int: u32 = Ipld::Integer(u32::MAX as Integer).try_into().unwrap();
251256
assert_eq!(int, u32::MAX);
252257

253-
let int: u64 = Ipld::Integer(u64::MAX as i128).try_into().unwrap();
254-
assert_eq!(int, u64::MAX);
258+
#[cfg(not(feature = "integer-max-i64"))]
259+
{
260+
let int: u64 = Ipld::Integer(u64::MAX as i128).try_into().unwrap();
261+
assert_eq!(int, u64::MAX);
255262

256-
let int: usize = Ipld::Integer(usize::MAX as i128).try_into().unwrap();
257-
assert_eq!(int, usize::MAX);
263+
let int: usize = Ipld::Integer(usize::MAX as i128).try_into().unwrap();
264+
assert_eq!(int, usize::MAX);
258265

259-
let int: u128 = Ipld::Integer(i128::MAX).try_into().unwrap();
260-
assert_eq!(int, i128::MAX as u128);
266+
let int: u128 = Ipld::Integer(i128::MAX).try_into().unwrap();
267+
assert_eq!(int, i128::MAX as u128);
268+
}
261269

262-
let int: i8 = Ipld::Integer(i8::MIN as i128).try_into().unwrap();
270+
let int: i8 = Ipld::Integer(i8::MIN as Integer).try_into().unwrap();
263271
assert_eq!(int, i8::MIN);
264272

265-
let int: i16 = Ipld::Integer(i16::MIN as i128).try_into().unwrap();
273+
let int: i16 = Ipld::Integer(i16::MIN as Integer).try_into().unwrap();
266274
assert_eq!(int, i16::MIN);
267275

268-
let int: i32 = Ipld::Integer(i32::MIN as i128).try_into().unwrap();
276+
let int: i32 = Ipld::Integer(i32::MIN as Integer).try_into().unwrap();
269277
assert_eq!(int, i32::MIN);
270278

271-
let int: i64 = Ipld::Integer(i64::MIN as i128).try_into().unwrap();
279+
let int: i64 = Ipld::Integer(i64::MIN as Integer).try_into().unwrap();
272280
assert_eq!(int, i64::MIN);
273281

274-
let int: isize = Ipld::Integer(isize::MIN as i128).try_into().unwrap();
282+
let int: isize = Ipld::Integer(isize::MIN as Integer).try_into().unwrap();
275283
assert_eq!(int, isize::MIN);
276284

277-
let int: i128 = Ipld::Integer(i128::MIN).try_into().unwrap();
278-
assert_eq!(int, i128::MIN);
285+
#[cfg(not(feature = "integer-max-i64"))]
286+
{
287+
let int: i128 = Ipld::Integer(i128::MIN).try_into().unwrap();
288+
assert_eq!(int, i128::MIN);
289+
}
279290

280291
let int: Option<i32> = Ipld::Null.try_into().unwrap();
281292
assert_eq!(int, Option::None)

src/ipld.rs

+4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ pub enum Ipld {
4141
/// Represents a boolean value.
4242
Bool(bool),
4343
/// Represents an integer.
44+
#[cfg(not(feature = "integer-max-i64"))]
4445
Integer(i128),
46+
/// Represents an integer.
47+
#[cfg(feature = "integer-max-i64")]
48+
Integer(i64),
4549
/// Represents a floating point value.
4650
Float(f64),
4751
/// Represents an UTF-8 string.

src/serde/de.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,41 @@ impl<'de> de::Deserialize<'de> for Ipld {
9898
where
9999
E: de::Error,
100100
{
101-
Ok(Ipld::Integer(v.into()))
101+
#[cfg(not(feature = "integer-max-i64"))]
102+
let integer = v.into();
103+
#[cfg(feature = "integer-max-i64")]
104+
let integer = v
105+
.try_into()
106+
.map_err(|_| de::Error::custom("integer out of i64 bounds"))?;
107+
Ok(Ipld::Integer(integer))
102108
}
103109

104110
#[inline]
105111
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
106112
where
107113
E: de::Error,
108114
{
109-
Ok(Ipld::Integer(v.into()))
115+
#[cfg(not(feature = "integer-max-i64"))]
116+
let integer = v.into();
117+
#[cfg(feature = "integer-max-i64")]
118+
let integer = v;
119+
Ok(Ipld::Integer(integer))
110120
}
111121

112122
#[inline]
123+
#[cfg_attr(feature = "integer-max-i64", allow(unused_variables))]
113124
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
114125
where
115126
E: de::Error,
116127
{
117-
Ok(Ipld::Integer(v))
128+
#[cfg(not(feature = "integer-max-i64"))]
129+
{
130+
Ok(Ipld::Integer(v))
131+
}
132+
#[cfg(feature = "integer-max-i64")]
133+
{
134+
Err(de::Error::custom("integer out of i64 bounds"))
135+
}
118136
}
119137

120138
#[inline]
@@ -266,7 +284,10 @@ impl<'de> de::Deserializer<'de> for Ipld {
266284
match self {
267285
Self::Null => visitor.visit_none(),
268286
Self::Bool(bool) => visitor.visit_bool(bool),
287+
#[cfg(not(feature = "integer-max-i64"))]
269288
Self::Integer(i128) => visitor.visit_i128(i128),
289+
#[cfg(feature = "integer-max-i64")]
290+
Self::Integer(i64) => visitor.visit_i64(i64),
270291
Self::Float(f64) => visitor.visit_f64(f64),
271292
Self::String(string) => visitor.visit_str(&string),
272293
Self::Bytes(bytes) => visitor.visit_bytes(&bytes),

src/serde/ser.rs

+33-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ impl ser::Serialize for Ipld {
8787
match &self {
8888
Self::Null => serializer.serialize_none(),
8989
Self::Bool(value) => serializer.serialize_bool(*value),
90+
#[cfg(not(feature = "integer-max-i64"))]
9091
Self::Integer(value) => serializer.serialize_i128(*value),
92+
#[cfg(feature = "integer-max-i64")]
93+
Self::Integer(value) => serializer.serialize_i64(*value),
9194
Self::Float(value) => serializer.serialize_f64(*value),
9295
Self::String(value) => serializer.serialize_str(value),
9396
Self::Bytes(value) => serializer.serialize_bytes(value),
@@ -135,31 +138,55 @@ impl serde::Serializer for Serializer {
135138

136139
#[inline]
137140
fn serialize_i64(self, value: i64) -> Result<Self::Ok, Self::Error> {
138-
self.serialize_i128(i128::from(value))
141+
#[cfg(not(feature = "integer-max-i64"))]
142+
{
143+
self.serialize_i128(i128::from(value))
144+
}
145+
#[cfg(feature = "integer-max-i64")]
146+
{
147+
Ok(Self::Ok::Integer(value))
148+
}
139149
}
140150

151+
#[cfg_attr(feature = "integer-max-i64", allow(unused_variables))]
141152
fn serialize_i128(self, value: i128) -> Result<Self::Ok, Self::Error> {
142-
Ok(Self::Ok::Integer(value))
153+
#[cfg(not(feature = "integer-max-i64"))]
154+
{
155+
Ok(Self::Ok::Integer(value))
156+
}
157+
#[cfg(feature = "integer-max-i64")]
158+
{
159+
Err(ser::Error::custom("integer out of i64 bounds"))
160+
}
143161
}
144162

145163
#[inline]
146164
fn serialize_u8(self, value: u8) -> Result<Self::Ok, Self::Error> {
147-
self.serialize_i128(value.into())
165+
self.serialize_i64(value.into())
148166
}
149167

150168
#[inline]
151169
fn serialize_u16(self, value: u16) -> Result<Self::Ok, Self::Error> {
152-
self.serialize_i128(value.into())
170+
self.serialize_i64(value.into())
153171
}
154172

155173
#[inline]
156174
fn serialize_u32(self, value: u32) -> Result<Self::Ok, Self::Error> {
157-
self.serialize_i128(value.into())
175+
self.serialize_i64(value.into())
158176
}
159177

160178
#[inline]
161179
fn serialize_u64(self, value: u64) -> Result<Self::Ok, Self::Error> {
162-
self.serialize_i128(value.into())
180+
#[cfg(not(feature = "integer-max-i64"))]
181+
{
182+
self.serialize_i128(value.into())
183+
}
184+
#[cfg(feature = "integer-max-i64")]
185+
{
186+
Ok(Self::Ok::Integer(value.try_into().map_err(|_| {
187+
ser::Error::custom("integer out of i64 bounds")
188+
})?))
189+
}
163190
}
164191

165192
#[inline]

tests/serde_deserializer.rs

+33-21
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ use serde_json::json;
1212
use ipld_core::cid::Cid;
1313
use ipld_core::ipld::Ipld;
1414

15+
#[cfg(not(feature = "integer-max-i64"))]
16+
type Integer = i128;
17+
#[cfg(feature = "integer-max-i64")]
18+
type Integer = i64;
19+
1520
/// This function is to test that all IPLD kinds except the given one errors, when trying to
1621
/// deserialize to the given Rust type.
1722
fn error_except<'de, T>(_input: T, except: &Ipld)
@@ -98,9 +103,9 @@ fn ipld_deserializer_u8() {
98103
"Correctly deserialize Ipld::Integer to u8."
99104
);
100105

101-
let too_large = u8::deserialize(Ipld::Integer((u8::MAX as i128) + 10));
106+
let too_large = u8::deserialize(Ipld::Integer((u8::MAX as Integer) + 10));
102107
assert!(too_large.is_err(), "Number must be within range.");
103-
let too_small = u8::deserialize(Ipld::Integer((u8::MIN as i128) - 10));
108+
let too_small = u8::deserialize(Ipld::Integer((u8::MIN as Integer) - 10));
104109
assert!(too_small.is_err(), "Number must be within range.");
105110
}
106111

@@ -116,9 +121,9 @@ fn ipld_deserializer_u16() {
116121
"Correctly deserialize Ipld::Integer to u16."
117122
);
118123

119-
let too_large = u16::deserialize(Ipld::Integer((u16::MAX as i128) + 10));
124+
let too_large = u16::deserialize(Ipld::Integer((u16::MAX as Integer) + 10));
120125
assert!(too_large.is_err(), "Number must be within range.");
121-
let too_small = u16::deserialize(Ipld::Integer((u16::MIN as i128) - 10));
126+
let too_small = u16::deserialize(Ipld::Integer((u16::MIN as Integer) - 10));
122127
assert!(too_small.is_err(), "Number must be within range.");
123128
}
124129

@@ -134,16 +139,17 @@ fn ipld_deserializer_u32() {
134139
"Correctly deserialize Ipld::Integer to u32."
135140
);
136141

137-
let too_large = u32::deserialize(Ipld::Integer((u32::MAX as i128) + 10));
142+
let too_large = u32::deserialize(Ipld::Integer((u32::MAX as Integer) + 10));
138143
assert!(too_large.is_err(), "Number must be within range.");
139-
let too_small = u32::deserialize(Ipld::Integer((u32::MIN as i128) - 10));
144+
let too_small = u32::deserialize(Ipld::Integer((u32::MIN as Integer) - 10));
140145
assert!(too_small.is_err(), "Number must be within range.");
141146
}
142147

143148
#[test]
149+
#[allow(clippy::unnecessary_fallible_conversions)]
144150
fn ipld_deserializer_u64() {
145151
let integer = 34567890123u64;
146-
let ipld = Ipld::Integer(integer.into());
152+
let ipld = Ipld::Integer(integer.try_into().unwrap());
147153
error_except(integer, &ipld);
148154

149155
let deserialized = u64::deserialize(ipld).unwrap();
@@ -152,10 +158,13 @@ fn ipld_deserializer_u64() {
152158
"Correctly deserialize Ipld::Integer to u64."
153159
);
154160

155-
let too_large = u64::deserialize(Ipld::Integer((u64::MAX as i128) + 10));
156-
assert!(too_large.is_err(), "Number must be within range.");
157-
let too_small = u64::deserialize(Ipld::Integer((u64::MIN as i128) - 10));
158-
assert!(too_small.is_err(), "Number must be within range.");
161+
#[cfg(not(feature = "integer-max-i64"))]
162+
{
163+
let too_large = u64::deserialize(Ipld::Integer((u64::MAX as Integer) + 10));
164+
assert!(too_large.is_err(), "Number must be within range.");
165+
let too_small = u64::deserialize(Ipld::Integer((u64::MIN as Integer) - 10));
166+
assert!(too_small.is_err(), "Number must be within range.");
167+
}
159168
}
160169

161170
#[test]
@@ -170,9 +179,9 @@ fn ipld_deserializer_i8() {
170179
"Correctly deserialize Ipld::Integer to i8."
171180
);
172181

173-
let too_large = i8::deserialize(Ipld::Integer((i8::MAX as i128) + 10));
182+
let too_large = i8::deserialize(Ipld::Integer((i8::MAX as Integer) + 10));
174183
assert!(too_large.is_err(), "Number must be within range.");
175-
let too_small = i8::deserialize(Ipld::Integer((i8::MIN as i128) - 10));
184+
let too_small = i8::deserialize(Ipld::Integer((i8::MIN as Integer) - 10));
176185
assert!(too_small.is_err(), "Number must be within range.");
177186
}
178187

@@ -188,9 +197,9 @@ fn ipld_deserializer_i16() {
188197
"Correctly deserialize Ipld::Integer to i16."
189198
);
190199

191-
let too_large = i16::deserialize(Ipld::Integer((i16::MAX as i128) + 10));
200+
let too_large = i16::deserialize(Ipld::Integer((i16::MAX as Integer) + 10));
192201
assert!(too_large.is_err(), "Number must be within range.");
193-
let too_small = i16::deserialize(Ipld::Integer((i16::MIN as i128) - 10));
202+
let too_small = i16::deserialize(Ipld::Integer((i16::MIN as Integer) - 10));
194203
assert!(too_small.is_err(), "Number must be within range.");
195204
}
196205

@@ -206,9 +215,9 @@ fn ipld_deserializer_i32() {
206215
"Correctly deserialize Ipld::Integer to i32."
207216
);
208217

209-
let too_large = i32::deserialize(Ipld::Integer((i32::MAX as i128) + 10));
218+
let too_large = i32::deserialize(Ipld::Integer((i32::MAX as Integer) + 10));
210219
assert!(too_large.is_err(), "Number must be within range.");
211-
let too_small = i32::deserialize(Ipld::Integer((i32::MIN as i128) - 10));
220+
let too_small = i32::deserialize(Ipld::Integer((i32::MIN as Integer) - 10));
212221
assert!(too_small.is_err(), "Number must be within range.");
213222
}
214223

@@ -224,10 +233,13 @@ fn ipld_deserializer_i64() {
224233
"Correctly deserialize Ipld::Integer to i64."
225234
);
226235

227-
let too_large = i64::deserialize(Ipld::Integer((i64::MAX as i128) + 10));
228-
assert!(too_large.is_err(), "Number must be within range.");
229-
let too_small = i64::deserialize(Ipld::Integer((i64::MIN as i128) - 10));
230-
assert!(too_small.is_err(), "Number must be within range.");
236+
#[cfg(not(feature = "integer-max-i64"))]
237+
{
238+
let too_large = i64::deserialize(Ipld::Integer((i64::MAX as i128) + 10));
239+
assert!(too_large.is_err(), "Number must be within range.");
240+
let too_small = i64::deserialize(Ipld::Integer((i64::MIN as i128) - 10));
241+
assert!(too_small.is_err(), "Number must be within range.");
242+
}
231243
}
232244

233245
#[test]

0 commit comments

Comments
 (0)