Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor converters to numeric types for aws_smithy_types::Number #1274

Merged
merged 16 commits into from
Aug 19, 2022

Conversation

david-perez
Copy link
Contributor

@david-perez david-perez commented Mar 23, 2022

Currently, conversions from aws_smithy_types::Number into numeric Rust
types ({i,u}{8, 16, 32, 64} and f{32, 64}) are always lossy, because
they use the as Rust keyword to cast into the target type. This means
that clients and servers are accepting lossy data: for example, if an
operation is modeled to take in a 32-bit integer as input, and a client
incorrectly sends an integer number that does not fit in 32 bits, the
server will silently accept the truncated input. There are malformed
request protocol tests that verify that servers must reject these
requests.

This commit removes the lossy to_* methods on Number and instead
implements TryFrom<Number> for $typ for the target numeric type
$typ. These converters will attempt their best to perform the
conversion safely, and fail if it is lossy.

The code-generated JSON parsers will now fail with
aws_smithy_json::deserialize::ErrorReason::InvalidNumber if the number
in the JSON document cannot be converted into the modeled integer type
without losing precision. For floating point target types, lossy
conversions are still performed, via Number::to_f32_lossy and
Number::to_f64_lossy.

Motivation and Context

Description

Testing

Checklist

  • I have updated CHANGELOG.next.toml if I made changes to the smithy-rs codegen or runtime crates
  • I have updated CHANGELOG.next.toml if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Currently, conversions from `aws_smithy_types::Number` into numeric Rust
types (`{i,u}{8, 16, 32, 64}` and `f{32, 64}`) are always lossy, because
they use the `as` Rust keyword to cast into the target type. This means
that clients and servers are accepting lossy data: for example, if an
operation is modeled to take in a 32-bit integer as input, and a client
incorrectly sends an integer number that does not fit in 32 bits, the
server will silently accept the truncated input. There are malformed
request protocol tests that verify that servers must reject these
requests.

This commit removes the lossy `to_*` methods on `Number` and instead
implements `TryFrom<$typ> for Number` for the target numeric type
`$typ`. These converters will attempt their best to perform the
conversion safely, and fail if it is lossy.

The code-generated JSON parsers will now fail with
`aws_smithy_json::deserialize::ErrorReason::InvalidNumber` if the number
in the JSON document cannot be converted into the modeled integer type
without losing precision. For floating point target types, lossy
conversions are still performed, via `Number::to_f32_lossy` and
`Number::to_f64_lossy`.
@david-perez david-perez requested review from a team as code owners March 23, 2022 15:44
@github-actions
Copy link

A new doc preview is ready to view.

@github-actions
Copy link

A new generated diff is ready to view.

@github-actions
Copy link

Rust Wrk benchmark report:

Duration: 90 sec, Connections: 32, Threads: 2

Measurement Deviation Current Old
Requests/sec 0.86% 66142.6 65578.75
Total requests 0.86% 5958657 5908013
Total errors NaN% 0 0
Total successes 0.86% 5958657 5908013
Average latency ms 15.38% 1.2 1.04
Minimum latency ms 0.00% 0.02 0.02
Maximum latency ms -6.01% 23.92 25.45
Stdev latency ms 13.87% 1.97 1.73
Transfer Mb 0.86% 619.41 614.14
Connect errors NaN% 0 0
Read errors NaN% 0 0
Write errors NaN% 0 0
Status errors (not 2xx/3xx) NaN% 0 0
Timeout errors NaN% 0 0

@github-actions
Copy link

A new generated diff is ready to view.

@github-actions
Copy link

A new doc preview is ready to view.

@github-actions
Copy link

Rust Wrk benchmark report:

Duration: 90 sec, Connections: 32, Threads: 2

Measurement Deviation Current Old
Requests/sec -0.74% 76390.22 76962.44
Total requests -0.82% 6877380 6934018
Total errors NaN% 0 0
Total successes -0.82% 6877380 6934018
Average latency ms 2.54% 1.21 1.18
Minimum latency ms 0.00% 0.02 0.02
Maximum latency ms -15.13% 25.86 30.47
Stdev latency ms 1.43% 2.13 2.1
Transfer Mb -0.82% 714.91 720.79
Connect errors NaN% 0 0
Read errors NaN% 0 0
Write errors NaN% 0 0
Status errors (not 2xx/3xx) NaN% 0 0
Timeout errors NaN% 0 0

Copy link
Contributor

@crisidev crisidev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a couple of simple comments and suggestions. LGTM overall. Nice test suite!

Copy link
Collaborator

@rcoh rcoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM. It seems like it may be possible to refactor the converters to share macros but probably not worth it.

I may consider adding a proptest of some invariants as well.

rust-runtime/aws-smithy-types/src/lib.rs Outdated Show resolved Hide resolved
@@ -34,3 +34,9 @@ message = "Update all SDK and runtime crates to [edition 2021](https://blog.rust
references = ["aws-sdk-rust#490"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "Velfi"

[[smithy-rs]]
message = "Refactor converters to numeric types for `aws_smithy_types::Number`"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you indicate what the break is and how users should update their code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt any are performing these conversions themselves, but here's the updated changelog: c21572d

rust-runtime/aws-smithy-types/src/lib.rs Outdated Show resolved Hide resolved
fn from(_: aws_smithy_types::TryFromNumberError) -> Self {
Error {
reason: ErrorReason::InvalidNumber,
offset: None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we throw away this offset is it going to make it really hard to debug? Or do we have another mechanism to track the exact field where this was a problem?

Copy link
Contributor Author

@david-perez david-perez Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have the offset here. We only have the offset when working with the token directly in the code-generated deserializer, or when calling the expect_ functions which auto-fill it from the token in case of errors. When unescaping strings, we're also bubbling up errors without offsets. Making both cases bubble up offsets would require some refactoring in the parser generation.

Comment on lines 82 to 88
pub enum Number {
/// Unsigned 64-bit integer value
/// Unsigned 64-bit integer value.
PosInt(u64),
/// Signed 64-bit integer value
/// Signed 64-bit integer value. The wrapped value is _always_ negative.
NegInt(i64),
/// 64-bit floating-point value
/// 64-bit floating-point value.
Float(f64),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could consider making these variants #[non_exhaustive] which would prevent them from being directly instantiated so that we could insure that the invariants were held

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by invariants I mean: /// Signed 64-bit integer value. The wrapped value is _always_ negative.
We would add constructor functions, ::pos_int, ::neg_int, ::float.

We would need to refactor code to use those. neg_int could panic if you passed in a positive int (or return a result, eg.)

not a blocker, just a possibility

@david-perez
Copy link
Contributor Author

Should I add proptesting? In my tests I tested one or two values within the target type's range, as well as the values in the edges of the range (plus NaN and +-Infinity). If I add proptesting it would look like:

assert_eq!($typ::try_from(Number::PosInt(v)).unwrap(), v);

with v a random value within the range of $typ. Same with random values outside the range.

@github-actions
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Rust Wrk benchmark report:

Duration: 90 sec, Connections: 32, Threads: 2

Measurement Deviation Current Old
Requests/sec 12.81% 71424.88 63315.99
Total requests 12.87% 6434509 5701038
Total errors NaN% 0 0
Total successes 12.87% 6434509 5701038
Average latency ms 34.94% 1.12 0.83
Minimum latency ms 0.00% 0.02 0.02
Maximum latency ms 28.15% 23.72 18.51
Stdev latency ms 47.20% 1.84 1.25
Transfer Mb 12.86% 668.87 592.63
Connect errors NaN% 0 0
Read errors NaN% 0 0
Write errors NaN% 0 0
Status errors (not 2xx/3xx) NaN% 0 0
Timeout errors NaN% 0 0

@82marbag
Copy link
Contributor

Added needs-sdk-review, can we move this forward? @rcoh

@github-actions
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Copy link
Collaborator

@jdisanti jdisanti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this looks good to me. Just a couple minor things.

@github-actions
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

@github-actions
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

@david-perez
Copy link
Contributor Author

@jdisanti @rcoh I'll merge this tomorrow if there are no further comments.

@david-perez david-perez enabled auto-merge (squash) August 19, 2022 11:08
@github-actions
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

@david-perez david-perez merged commit 7e7d571 into main Aug 19, 2022
@david-perez david-perez deleted the davidpz-refactor-converters-to-numeric-types branch August 19, 2022 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants