Skip to content

Commit 4d99591

Browse files
committed
Merge branch 'main' of github.com:smithy-lang/smithy-kotlin into kn-main
2 parents 9b624ac + e9d16a9 commit 4d99591

File tree

11 files changed

+126
-9
lines changed

11 files changed

+126
-9
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "1021e75a-45f3-4f3a-820c-700d9ec6e782",
3+
"type": "bugfix",
4+
"description": "Fix serialization of CBOR blobs"
5+
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [1.3.31] - 12/18/2024
4+
5+
### Features
6+
* [#1473](https://github.com/awslabs/aws-sdk-kotlin/issues/1473) Enhance support for replayable instances of `InputStream`
7+
8+
## [1.3.30] - 12/16/2024
9+
310
## [1.3.29] - 12/12/2024
411

512
## [1.3.28] - 12/03/2024

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpProtocolUnitTestRequestGenerator.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B
117117
write("return")
118118
}
119119
write("requireNotNull(expectedBytes) { #S }", "expected application/cbor body cannot be null")
120-
write("requireNotNull(expectedBytes) { #S }", "actual application/cbor body cannot be null")
120+
write("requireNotNull(actualBytes) { #S }", "actual application/cbor body cannot be null")
121121

122122
write("")
123123
write("val expectedRequest = #L(#T(expectedBytes))", inputDeserializer.name, RuntimeTypes.Serde.SerdeCbor.CborDeserializer)
124-
write("val actualRequest = #L(#T(expectedBytes))", inputDeserializer.name, RuntimeTypes.Serde.SerdeCbor.CborDeserializer)
124+
write("val actualRequest = #L(#T(actualBytes))", inputDeserializer.name, RuntimeTypes.Serde.SerdeCbor.CborDeserializer)
125125
write("assertEquals(expectedRequest, actualRequest)")
126126
}
127127
writer.write("")

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/SerializeStructGenerator.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,6 @@ open class SerializeStructGenerator(
647647
val target = member.targetOrSelf(ctx.model)
648648

649649
val encoded = when {
650-
target.type == ShapeType.BLOB -> writer.format("#L.#T()", identifier, RuntimeTypes.Core.Text.Encoding.encodeBase64String)
651650
target.type == ShapeType.TIMESTAMP -> {
652651
writer.addImport(RuntimeTypes.Core.TimestampFormat)
653652
val tsFormat = member

codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/SerializeStructGeneratorTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1822,7 +1822,7 @@ class SerializeStructGeneratorTest {
18221822

18231823
val expected = """
18241824
serializer.serializeStruct(OBJ_DESCRIPTOR) {
1825-
input.fooBlob?.let { field(FOOBLOB_DESCRIPTOR, it.encodeBase64String()) }
1825+
input.fooBlob?.let { field(FOOBLOB_DESCRIPTOR, it) }
18261826
}
18271827
""".trimIndent()
18281828

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ kotlinx.atomicfu.enableNativeIrTransformation=false
1313
org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=1G
1414

1515
# SDK
16-
sdkVersion=1.3.30-SNAPSHOT
16+
sdkVersion=1.3.32-SNAPSHOT
1717

1818
# codegen
19-
codegenVersion=0.33.30-SNAPSHOT
19+
codegenVersion=0.33.32-SNAPSHOT

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/JMESPath.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,20 @@ public fun Any?.type(): String = when (this) {
6666
else -> throw Exception("Undetected type for: $this")
6767
}
6868

69+
// Collection `flattenIfPossible` functions
6970
@InternalApi
7071
@JvmName("noOpUnnestedCollection")
7172
public inline fun <reified T> Collection<T>.flattenIfPossible(): Collection<T> = this
7273

7374
@InternalApi
7475
@JvmName("flattenNestedCollection")
7576
public inline fun <reified T> Collection<Collection<T>>.flattenIfPossible(): Collection<T> = flatten()
77+
78+
// List `flattenIfPossible` functions
79+
@InternalApi
80+
@JvmName("noOpUnnestedCollection")
81+
public inline fun <reified T> List<T>.flattenIfPossible(): List<T> = this
82+
83+
@InternalApi
84+
@JvmName("flattenNestedCollection")
85+
public inline fun <reified T> List<List<T>>.flattenIfPossible(): List<T> = flatten()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package aws.smithy.kotlin.runtime.util
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertTrue
6+
7+
class JmesPathTest {
8+
@Test
9+
fun flattenNestedLists() {
10+
val nestedList = listOf(
11+
listOf(1, 2, 3),
12+
listOf(4, 5),
13+
listOf(6),
14+
)
15+
val flattenedList = nestedList.flattenIfPossible()
16+
assertEquals(listOf(1, 2, 3, 4, 5, 6), flattenedList)
17+
}
18+
19+
@Test
20+
fun flattenEmptyNestedLists() {
21+
val nestedList = listOf(
22+
listOf<Int>(),
23+
listOf(),
24+
listOf(),
25+
)
26+
val flattenedList = nestedList.flattenIfPossible()
27+
assertTrue(flattenedList.isEmpty())
28+
}
29+
30+
@Test
31+
fun flattenNestedEmptyAndNonEmptyNestedLists() {
32+
val nestedList = listOf(
33+
listOf(1, 2),
34+
listOf(),
35+
listOf(3, 4, 5),
36+
)
37+
val flattenedList = nestedList.flattenIfPossible()
38+
assertEquals(listOf(1, 2, 3, 4, 5), flattenedList)
39+
}
40+
41+
@Test
42+
fun flattenList() {
43+
val nestedList = listOf(
44+
listOf(1, 2, 3),
45+
)
46+
val flattenedList = nestedList.flattenIfPossible()
47+
assertEquals(listOf(1, 2, 3), flattenedList)
48+
}
49+
}

runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/content/ByteStreamJVM.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,30 @@ public fun ByteStream.Companion.fromInputStream(
114114
* @param contentLength If specified, indicates how many bytes remain in this stream. Defaults to `null`.
115115
*/
116116
public fun InputStream.asByteStream(contentLength: Long? = null): ByteStream.SourceStream {
117-
val source = source()
117+
if (markSupported() && contentLength != null) {
118+
mark(contentLength.toInt())
119+
}
120+
118121
return object : ByteStream.SourceStream() {
119122
override val contentLength: Long? = contentLength
120123
override val isOneShot: Boolean = !markSupported()
121-
override fun readFrom(): SdkSource = source
124+
override fun readFrom(): SdkSource {
125+
if (markSupported() && contentLength != null) {
126+
reset()
127+
mark(contentLength.toInt())
128+
return object : SdkSource by source() {
129+
/*
130+
* This is a no-op close to prevent body hashing from closing the underlying InputStream, which causes
131+
* `IOException: Stream closed` on subsequent reads. Consider making [ByteStream.ChannelStream]/[ByteStream.SourceStream]
132+
* (or possibly even [ByteStream] itself) implement [Closeable] to better handle closing streams.
133+
* This should allow us to clean up our usage of [ByteStream.cancel()].
134+
*/
135+
override fun close() { }
136+
}
137+
}
138+
139+
return source()
140+
}
122141
}
123142
}
124143

runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/content/ByteStreamJVMTest.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
package aws.smithy.kotlin.runtime.content
77

8+
import aws.smithy.kotlin.runtime.io.readToByteArray
89
import aws.smithy.kotlin.runtime.testing.RandomTempFile
910
import kotlinx.coroutines.test.runTest
11+
import java.io.BufferedInputStream
12+
import java.io.ByteArrayInputStream
1013
import java.io.ByteArrayOutputStream
1114
import java.io.InputStream
1215
import java.io.OutputStream
@@ -228,6 +231,31 @@ class ByteStreamJVMTest {
228231
assertFalse(sos.closed)
229232
}
230233

234+
// https://github.com/awslabs/aws-sdk-kotlin/issues/1473
235+
@Test
236+
fun testReplayableInputStreamAsByteStream() = runTest {
237+
val content = "Hello, Bytes!".encodeToByteArray()
238+
val byteArrayIns = ByteArrayInputStream(content)
239+
val nonReplayableIns = NonReplayableInputStream(byteArrayIns)
240+
241+
// buffer the non-replayable stream, making it replayable...
242+
val bufferedIns = BufferedInputStream(nonReplayableIns)
243+
244+
val byteStream = bufferedIns.asByteStream(content.size.toLong())
245+
246+
// Test that it can be read at least twice (e.g. once for hashing the body, once for transmitting the body)
247+
assertContentEquals(content, byteStream.readFrom().use { it.readToByteArray() })
248+
assertContentEquals(content, byteStream.readFrom().use { it.readToByteArray() })
249+
}
250+
251+
private class NonReplayableInputStream(val inputStream: InputStream) : InputStream() {
252+
override fun markSupported(): Boolean = false // not replayable
253+
254+
override fun read(): Int = inputStream.read()
255+
override fun mark(readlimit: Int) = inputStream.mark(readlimit)
256+
override fun reset() = inputStream.reset()
257+
}
258+
231259
private class StatusTrackingOutputStream(val os: OutputStream) : OutputStream() {
232260
var closed: Boolean = false
233261

0 commit comments

Comments
 (0)