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

feat: add support for smithy.protocols#rpcv2Cbor protocol #1103

Merged
merged 133 commits into from
Jul 12, 2024
Merged

Conversation

lauzadis
Copy link
Contributor

@lauzadis lauzadis commented Jun 19, 2024

This PR adds support for a new protocol, smithy.protocols#rpcv2Cbor.
This is a subset of the Concise Binary Object Representation (CBOR) RFC.
Check out the Smithy docs to see where this protocol differs from the RFC.

(potentially) Controversial changes:

  • Added deserializeByteArray() and deserializeInstant() to PrimitiveDeserializer interface
  • Added serializeByteArray() to PrimitiveSerializer interface
    • note: serializeInstant() already exists

Bugs fixed:

  • Fixed codegen of equals() functions for structs with:
    • Lists of blobs. Currently this will do a naive comparison (==) which is prone to failure because ByteArrays need to be compared with contentEquals.
    • Floats / doubles will now be compared with .equals(). This is because Float.NaN == Float.NaN always returns false, but if two structures have the exact same members and a NaN floating point, I believe they should be treated as equal? (at least according to Smithy protocol tests, which were failing without this change)
  • When normalizing input/output shapes, if we come across a shape that targets the smithy.api#Unit shape, we would set the archetype to the operation shape. I updated this to set the unit shape as the archetype so we don't lose that information.

Issue #

Closes #843 and part of awslabs/aws-sdk-kotlin#1302

Description of changes

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

This comment has been minimized.

This comment has been minimized.

@lauzadis lauzadis added the acknowledge-api-break Acknowledge that a change is API breaking and may be backwards-incompatible. Review carefully! label Jul 9, 2024

This comment has been minimized.

@lauzadis
Copy link
Contributor Author

lauzadis commented Jul 9, 2024

Note: acknowledge-api-break was added because of a bug in Kotlin around our use of typealias in BigDecimalJVM / BigIntegerJVM. This was going to break during our upgrade to K2, but I had to move it forward into this PR to add new functions like operator plus, operator minus, toByteArray

This comment has been minimized.

Copy link
Contributor

@ianbotsf ianbotsf left a comment

Choose a reason for hiding this comment

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

Approved pending positive outcomes of remaining nits/questions.

Comment on lines +30 to +31
public fun toHttpBody(): HttpBody = buffer.readByteArray().toHttpBody()

Copy link
Contributor

Choose a reason for hiding this comment

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

Question: I'm still concerned about reading this all into memory at once although I note that RPC v2 does not (yet) support blob streams, merely event streams. What's the behavior for event streams wrt CborSerializer.toHttpBody()? Is that invoked for the initial request and separately for each subsequent event (as opposed to once for the entire stream)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes that's correct, each message will be encoded separately in the CborSerializer instead of all-at-once. I wasn't able to functionally test this because there's no services that have an input event stream and support CBOR at the moment. I was able to test output event streams using CloudWatch Logs and they work as expected.

Comment on lines 174 to 208
val sb = StringBuilder()

// Append mantissa
sb.append(
when (mantissa) {
is UInt -> mantissa.value.toString()
is NegInt -> "-${mantissa.value}"
is Tag -> when (mantissa.value) {
is NegBigNum -> mantissa.value.value.toString()
is BigNum -> mantissa.value.value.toString()
else -> throw DeserializationException("Expected BigNum or NegBigNum for CBOR tagged decimal fraction mantissa, got ${mantissa.id}")
}
else -> throw DeserializationException("Expected UInt, NegInt, or Tag for CBOR decimal fraction mantissa, got $mantissa")
},
)

when (exponent) {
is UInt -> { // Positive exponent, suffix with zeroes
sb.append("0".repeat(exponent.value.toInt()))
sb.append(".")
}
is NegInt -> { // Negative exponent, prefix with zeroes if necessary
val exponentValue = exponent.value.toInt().absoluteValue
val insertIndex = if (sb[0] == '-') 1 else 0
if (exponentValue > sb.length - insertIndex) {
sb.insert(insertIndex, "0".repeat(exponentValue - sb.length + insertIndex))
sb.insert(insertIndex, '.')
} else {
sb.insert(sb.length - exponentValue, '.')
}
}
else -> throw DeserializationException("Expected integer for CBOR decimal fraction exponent value, got $exponent.")
}

return DecimalFraction(BigDecimal(sb.toString()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: Apologies but I missed this the first time around when commenting on string manipulation for BigInteger but same suggestion applies: I suggest we delegate these down to JVM's BigDecimal, which has a constructor for accepting a BigInteger mantissa (which JVM calls the unscaledVal) and an Int exponent (which JVM calls the scale).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I think you did mention doing this for BigDecimal and I missed it, either way, I think it's the right change so I'll do it

Comment on lines 550 to 554
val list = mutableListOf<UByte>()
while (hasNextElement()) {
list.add(deserializeByte().toUByte())
list.add(deserializeLong().toUByte())
}
return@deserializeList list
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Why did so many tests from deserializing narrow types (e.g., Byte) to deserializing Long?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tests of this form started failing after adding the out-of-bounds check for number deserialization because the numeric value is 255 which doesn't fit in a Byte. Trying to call deserializeByte here would result in the out-of-bounds check throwing an exception, so it's updated instead to deserializeLong

This comment has been minimized.

This comment has been minimized.

Copy link
Contributor

@ianbotsf ianbotsf left a comment

Choose a reason for hiding this comment

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

Latest changes look great to me!

Copy link

Affected Artifacts

Significantly increased in size

Artifact Pull Request (bytes) Latest Release (bytes) Delta (bytes) Delta (percentage)
smithy-rpcv2-protocols-jvm.jar 6,110 (does not exist) 6,110 NaN%
serde-cbor-jvm.jar 104,350 (does not exist) 104,350 NaN%
Changed in size
Artifact Pull Request (bytes) Latest Release (bytes) Delta (bytes) Delta (percentage)
smithy-test-jvm.jar 66,665 63,752 2,913 4.57%
serde-json-jvm.jar 85,727 83,960 1,767 2.10%
serde-form-url-jvm.jar 83,091 81,768 1,323 1.62%
serde-xml-jvm.jar 223,945 220,837 3,108 1.41%
aws-protocol-core-jvm.jar 24,245 24,042 203 0.84%
http-test-jvm.jar 66,311 65,788 523 0.79%
runtime-core-jvm.jar 886,604 883,520 3,084 0.35%
serde-jvm.jar 50,921 50,880 41 0.08%
aws-json-protocols-jvm.jar 6,906 7,176 -270 -3.76%

@lauzadis lauzadis merged commit 14cf870 into main Jul 12, 2024
15 checks passed
@lauzadis lauzadis deleted the feat-rpcv2-cbor branch July 12, 2024 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
acknowledge-api-break Acknowledge that a change is API breaking and may be backwards-incompatible. Review carefully! acknowledge-artifact-size-increase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support prioritized protocol generation
3 participants