Skip to content

Commit 4d3d193

Browse files
authored
Merge branch 'main' into docs-attr
2 parents 0bcc193 + b09b02f commit 4d3d193

File tree

2 files changed

+47
-12
lines changed

2 files changed

+47
-12
lines changed

CHANGELOG.next.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,9 @@ message = "All generated docs now include docsrs labels when features are requir
9191
references = ["smithy-rs#3121", "smithy-rs#3295"]
9292
meta = { "breaking" = false, "tada" = true, "bug" = false }
9393
author = "rcoh"
94+
95+
[[smithy-rs]]
96+
message = "[`Number`](https://docs.rs/aws-smithy-types/latest/aws_smithy_types/enum.Number.html) `TryInto` implementations now succesfully convert from `f64` to numeric types when no precision is lost. This fixes some deserialization issues where numbers like `25.0` were sent when `Byte` fields were expected."
97+
references = ["smithy-rs#3294"]
98+
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "all" }
99+
author = "rcoh"

rust-runtime/aws-smithy-types/src/number.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ macro_rules! to_unsigned_integer_converter {
7777
Number::NegInt(v) => {
7878
Err(TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(v).into())
7979
}
80-
Number::Float(v) => {
81-
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
82-
}
80+
Number::Float(v) => attempt_lossless!(v, $typ),
8381
}
8482
}
8583
}
@@ -102,9 +100,7 @@ macro_rules! to_signed_integer_converter {
102100
match value {
103101
Number::PosInt(v) => Ok(Self::try_from(v)?),
104102
Number::NegInt(v) => Ok(Self::try_from(v)?),
105-
Number::Float(v) => {
106-
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
107-
}
103+
Number::Float(v) => attempt_lossless!(v, $typ),
108104
}
109105
}
110106
}
@@ -115,6 +111,17 @@ macro_rules! to_signed_integer_converter {
115111
};
116112
}
117113

114+
macro_rules! attempt_lossless {
115+
($value: expr, $typ: ty) => {{
116+
let converted = $value as $typ;
117+
if (converted as f64 == $value) {
118+
Ok(converted)
119+
} else {
120+
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion($value).into())
121+
}
122+
}};
123+
}
124+
118125
/// Converts to a `u64`. The conversion fails if it is lossy.
119126
impl TryFrom<Number> for u64 {
120127
type Error = TryFromNumberError;
@@ -125,9 +132,7 @@ impl TryFrom<Number> for u64 {
125132
Number::NegInt(v) => {
126133
Err(TryFromNumberErrorKind::NegativeToUnsignedLossyConversion(v).into())
127134
}
128-
Number::Float(v) => {
129-
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
130-
}
135+
Number::Float(v) => attempt_lossless!(v, u64),
131136
}
132137
}
133138
}
@@ -142,9 +147,7 @@ impl TryFrom<Number> for i64 {
142147
match value {
143148
Number::PosInt(v) => Ok(Self::try_from(v)?),
144149
Number::NegInt(v) => Ok(v),
145-
Number::Float(v) => {
146-
Err(TryFromNumberErrorKind::FloatToIntegerLossyConversion(v).into())
147-
}
150+
Number::Float(v) => attempt_lossless!(v, i64),
148151
}
149152
}
150153
}
@@ -236,6 +239,7 @@ mod test {
236239
}
237240
));
238241
}
242+
assert_eq!($typ::try_from(Number::Float(25.0)).unwrap(), 25);
239243
};
240244
}
241245

@@ -302,6 +306,13 @@ mod test {
302306
}
303307
));
304308
}
309+
310+
let range = || ($typ::MIN..=$typ::MAX);
311+
312+
for val in range().take(1024).chain(range().rev().take(1024)) {
313+
assert_eq!(val, $typ::try_from(Number::Float(val as f64)).unwrap());
314+
$typ::try_from(Number::Float((val as f64) + 0.1)).expect_err("not equivalent");
315+
}
305316
};
306317
}
307318

@@ -318,6 +329,19 @@ mod test {
318329
}
319330
));
320331
}
332+
let range = || (i64::MIN..=i64::MAX);
333+
334+
for val in range().take(1024).chain(range().rev().take(1024)) {
335+
// if we can actually represent the value
336+
if ((val as f64) as i64) == val {
337+
assert_eq!(val, i64::try_from(Number::Float(val as f64)).unwrap());
338+
}
339+
let fval = val as f64;
340+
// at the limits of the range, we don't have this precision
341+
if (fval + 0.1).fract() != 0.0 {
342+
i64::try_from(Number::Float((val as f64) + 0.1)).expect_err("not equivalent");
343+
}
344+
}
321345
}
322346

323347
#[test]
@@ -333,6 +357,11 @@ mod test {
333357
#[test]
334358
fn to_i8() {
335359
to_signed_converter_tests!(i8);
360+
i8::try_from(Number::Float(-3200000.0)).expect_err("overflow");
361+
i8::try_from(Number::Float(32.1)).expect_err("imprecise");
362+
i8::try_from(Number::Float(i8::MAX as f64 + 0.1)).expect_err("imprecise");
363+
i8::try_from(Number::Float(f64::NAN)).expect_err("nan");
364+
i8::try_from(Number::Float(f64::INFINITY)).expect_err("nan");
336365
}
337366

338367
#[test]

0 commit comments

Comments
 (0)