-
Notifications
You must be signed in to change notification settings - Fork 224
Add support for Smithy bigInteger and bigDecimal types as string wrappers in aws-smithy-types, allowing users to parse with their preferred big number library. #4418
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --- | ||
| applies_to: | ||
| - client | ||
| - aws-sdk-rust | ||
| authors: | ||
| - AmitKulkarni23 | ||
| references: | ||
| - smithy-rs#312 | ||
| breaking: false | ||
| new_feature: true | ||
| bug_fix: false | ||
| --- | ||
| Add support for Smithy bigInteger and bigDecimal types as string wrappers in aws-smithy-types, allowing users to parse with their preferred big number library. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| $version: "2.0" | ||
|
|
||
| namespace com.amazonaws.bignumbers | ||
|
|
||
| use aws.protocols#restJson1 | ||
| use smithy.test#httpRequestTests | ||
| use smithy.test#httpResponseTests | ||
|
|
||
| @restJson1 | ||
| service BigNumberService { | ||
| version: "2023-01-01" | ||
| operations: [ProcessBigNumbers] | ||
| } | ||
|
|
||
| @http(uri: "/process", method: "POST") | ||
| @httpRequestTests([ | ||
| { | ||
| id: "BigNumbersInJsonRequest", | ||
| protocol: restJson1, | ||
| method: "POST", | ||
| uri: "/process", | ||
| body: "{\"bigInt\":123456789,\"bigDec\":123.456789}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| bigInt: 123456789, | ||
| bigDec: 123.456789 | ||
| } | ||
| }, | ||
| { | ||
| id: "NegativeBigNumbersInJsonRequest", | ||
| protocol: restJson1, | ||
| method: "POST", | ||
| uri: "/process", | ||
| body: "{\"bigInt\":-987654321,\"bigDec\":-0.000000001}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| bigInt: -987654321, | ||
| bigDec: -0.000000001 | ||
| } | ||
| }, | ||
| { | ||
| id: "ZeroBigNumbersInJsonRequest", | ||
| protocol: restJson1, | ||
| method: "POST", | ||
| uri: "/process", | ||
| body: "{\"bigInt\":0,\"bigDec\":0.0}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| bigInt: 0, | ||
| bigDec: 0.0 | ||
| } | ||
| }, | ||
| { | ||
| id: "VeryLargeBigNumbersInJsonRequest", | ||
| protocol: restJson1, | ||
| method: "POST", | ||
| uri: "/process", | ||
| body: "{\"bigInt\":9007199254740991,\"bigDec\":123456.789}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| bigInt: 9007199254740991, | ||
| bigDec: 123456.789 | ||
| } | ||
| } | ||
| ]) | ||
| @httpResponseTests([ | ||
| { | ||
| id: "BigNumbersInJsonResponse", | ||
| protocol: restJson1, | ||
| code: 200, | ||
| body: "{\"result\":999999999,\"ratio\":0.123456789}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| result: 999999999, | ||
| ratio: 0.123456789 | ||
| } | ||
| }, | ||
| { | ||
| id: "NegativeBigNumbersInJsonResponse", | ||
| protocol: restJson1, | ||
| code: 200, | ||
| body: "{\"result\":-123456789,\"ratio\":-999.999}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| result: -123456789, | ||
| ratio: -999.999 | ||
| } | ||
| }, | ||
| { | ||
| id: "VeryLargeBigNumbersInJsonResponse", | ||
| protocol: restJson1, | ||
| code: 200, | ||
| body: "{\"result\":9007199254740991,\"ratio\":123456.789}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| result: 9007199254740991, | ||
| ratio: 123456.789 | ||
| } | ||
| }, | ||
| { | ||
| id: "ZeroBigNumbersInJsonResponse", | ||
| protocol: restJson1, | ||
| code: 200, | ||
| body: "{\"result\":0,\"ratio\":0.0}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: { | ||
| result: 0, | ||
| ratio: 0.0 | ||
| } | ||
| }, | ||
| { | ||
| id: "NullBigNumbersInJsonResponse", | ||
| protocol: restJson1, | ||
| code: 200, | ||
| body: "{\"result\":null,\"ratio\":null}", | ||
| bodyMediaType: "application/json", | ||
| headers: {"Content-Type": "application/json"}, | ||
| params: {} | ||
| } | ||
| ]) | ||
| operation ProcessBigNumbers { | ||
| input: BigNumberInput | ||
| output: BigNumberOutput | ||
| } | ||
| structure BigNumberInput { | ||
| bigInt: BigInteger | ||
| bigDec: BigDecimal | ||
| } | ||
| structure BigNumberOutput { | ||
| result: BigInteger | ||
| ratio: BigDecimal | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,10 @@ | |
|
|
||
| package software.amazon.smithy.rust.codegen.core.smithy.protocols.parse | ||
|
|
||
| import software.amazon.smithy.codegen.core.CodegenException | ||
| import software.amazon.smithy.codegen.core.Symbol | ||
| import software.amazon.smithy.model.shapes.BigDecimalShape | ||
| import software.amazon.smithy.model.shapes.BigIntegerShape | ||
| import software.amazon.smithy.model.shapes.BlobShape | ||
| import software.amazon.smithy.model.shapes.BooleanShape | ||
| import software.amazon.smithy.model.shapes.ByteShape | ||
|
|
@@ -579,6 +582,18 @@ class CborParserGenerator( | |
|
|
||
| is TimestampShape -> rust("decoder.timestamp()") | ||
|
|
||
| // BigInteger/BigDecimal are not supported with CBOR. | ||
| // The Smithy RPC v2 CBOR spec requires these to be encoded using CBOR tags 2/3/4 | ||
| // (binary bignum representation), but aws-smithy-cbor doesn't implement these tags yet. | ||
| is BigIntegerShape -> | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to reviewers: I have added serialization and parsing logic for the following protocols:
Let me know if there are any other protocols. |
||
| throw CodegenException( | ||
| "BigInteger is not supported with Concise Binary Object Representation (CBOR) protocol", | ||
| ) | ||
| is BigDecimalShape -> | ||
| throw CodegenException( | ||
| "BigDecimal is not supported with Concise Binary Object Representation (CBOR) protocol", | ||
| ) | ||
|
|
||
| // Aggregate shapes: https://smithy.io/2.0/spec/aggregate-types.html | ||
| is StructureShape -> deserializeStruct(target) | ||
| is CollectionShape -> deserializeCollection(target) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,8 @@ | |
| package software.amazon.smithy.rust.codegen.core.smithy.protocols.parse | ||
|
|
||
| import software.amazon.smithy.codegen.core.Symbol | ||
| import software.amazon.smithy.model.shapes.BigDecimalShape | ||
| import software.amazon.smithy.model.shapes.BigIntegerShape | ||
| import software.amazon.smithy.model.shapes.BlobShape | ||
| import software.amazon.smithy.model.shapes.BooleanShape | ||
| import software.amazon.smithy.model.shapes.CollectionShape | ||
|
|
@@ -296,6 +298,8 @@ class JsonParserGenerator( | |
| when (val target = model.expectShape(memberShape.target)) { | ||
| is StringShape -> deserializeString(target) | ||
| is BooleanShape -> rustTemplate("#{expect_bool_or_null}(tokens.next())?", *codegenScope) | ||
| is BigIntegerShape -> deserializeBigInteger() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we probably need to support this for more than just json protocols. also need protocol tests. does smithy have any protocol tests for these yet?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. I will add serialization and deserialization code for XML, CBOR protocols in the next revision.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Protocol tests exist in
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed this in the latest revision of PR |
||
| is BigDecimalShape -> deserializeBigDecimal() | ||
| is NumberShape -> deserializeNumber(target) | ||
| is BlobShape -> deserializeBlob(memberShape) | ||
| is TimestampShape -> deserializeTimestamp(memberShape) | ||
|
|
@@ -374,6 +378,63 @@ class JsonParserGenerator( | |
| } | ||
| } | ||
|
|
||
| private fun RustWriter.deserializeBigInteger() { | ||
| // Match on Number enum to: | ||
| // 1. Validate only integers are accepted (reject floats) | ||
| // 2. Extract inner value and convert to string | ||
|
|
||
| rustTemplate( | ||
| """ | ||
| #{expect_number_or_null}(tokens.next())? | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think do actually do this properly you need to add some additional code to As it is, this isn't terrible, but its not ideal since it defeats the point |
||
| .map(|v| { | ||
| let s = match v { | ||
| #{Number}::PosInt(n) => n.to_string(), | ||
| #{Number}::NegInt(n) => n.to_string(), | ||
| #{Number}::Float(_) => return Err(#{Error}::custom("expected integer, found float")), | ||
| }; | ||
| Ok(<#{BigInteger} as ::std::str::FromStr>::from_str(&s).expect("infallible")) | ||
| }) | ||
| .transpose()? | ||
| """, | ||
| "BigInteger" to RuntimeType.bigInteger(codegenContext.runtimeConfig), | ||
| "Number" to RuntimeType.smithyTypes(codegenContext.runtimeConfig).resolve("Number"), | ||
| *codegenScope, | ||
| ) | ||
| } | ||
|
|
||
| private fun RustWriter.deserializeBigDecimal() { | ||
| // Match on Number enum to extract inner value and convert to string | ||
| // (Number doesn't implement Display, so we must match each variant) | ||
| // For floats, preserve decimal notation that f64::to_string() drops for whole numbers | ||
|
|
||
| rustTemplate( | ||
| """ | ||
| #{expect_number_or_null}(tokens.next())? | ||
| .map(|v| { | ||
| let s = match v { | ||
| #{Number}::PosInt(n) => n.to_string(), | ||
| #{Number}::NegInt(n) => n.to_string(), | ||
| #{Number}::Float(f) => { | ||
| // Use format! to avoid scientific notation and preserve precision | ||
| let s = format!("{f}"); | ||
| // f64 formatting drops ".0" for whole numbers (0.0 -> "0") | ||
| // Restore it to preserve that the original JSON had decimal notation | ||
| if !s.contains('.') && !s.contains('e') && !s.contains('E') { | ||
| format!("{s}.0") | ||
| } else { | ||
| s | ||
| } | ||
|
Comment on lines
+419
to
+426
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this correct? I definitely want to see some tests for this. |
||
| }, | ||
| }; | ||
| <#{BigDecimal} as ::std::str::FromStr>::from_str(&s).expect("infallible") | ||
| }) | ||
| """, | ||
| "BigDecimal" to RuntimeType.bigDecimal(codegenContext.runtimeConfig), | ||
| "Number" to RuntimeType.smithyTypes(codegenContext.runtimeConfig).resolve("Number"), | ||
| *codegenScope, | ||
| ) | ||
| } | ||
|
|
||
| private fun RustWriter.deserializeTimestamp(member: MemberShape) { | ||
| val timestampFormat = | ||
| httpBindingResolver.timestampFormat( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there appears to be some code that handles
E/ scientific notation but I don't see any tests of that here