diff --git a/.changes/cda907f0-b704-42de-a50f-dad204dacbfb.json b/.changes/cda907f0-b704-42de-a50f-dad204dacbfb.json new file mode 100644 index 000000000..30339196e --- /dev/null +++ b/.changes/cda907f0-b704-42de-a50f-dad204dacbfb.json @@ -0,0 +1,5 @@ +{ + "id": "cda907f0-b704-42de-a50f-dad204dacbfb", + "type": "misc", + "description": "Correct documentation for `ByteStream.writeToOutputStream`, add `ByteStream.appendToOutputStream`" +} \ No newline at end of file diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index d1ba553de..ca81f3410 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -416,6 +416,7 @@ public abstract class aws/smithy/kotlin/runtime/content/ByteStream$SourceStream } public final class aws/smithy/kotlin/runtime/content/ByteStreamJVMKt { + public static final fun appendToOutputStream (Laws/smithy/kotlin/runtime/content/ByteStream;Ljava/io/OutputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun asByteStream (Ljava/io/File;JJ)Laws/smithy/kotlin/runtime/content/ByteStream; public static final fun asByteStream (Ljava/io/File;Lkotlin/ranges/LongRange;)Laws/smithy/kotlin/runtime/content/ByteStream; public static final fun asByteStream (Ljava/io/InputStream;Ljava/lang/Long;)Laws/smithy/kotlin/runtime/content/ByteStream$SourceStream; diff --git a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/content/ByteStreamJVM.kt b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/content/ByteStreamJVM.kt index af48850d0..0ab9140ef 100644 --- a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/content/ByteStreamJVM.kt +++ b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/content/ByteStreamJVM.kt @@ -123,7 +123,7 @@ public fun InputStream.asByteStream(contentLength: Long? = null): ByteStream.Sou } /** - * Writes this stream to the given [OutputStream]. This method does not flush or close the given [OutputStream]. + * Writes this stream to the given [OutputStream], then closes it. * @param outputStream The [OutputStream] to which the contents of this stream will be written */ public suspend fun ByteStream.writeToOutputStream(outputStream: OutputStream): Long = withContext(Dispatchers.IO) { @@ -140,6 +140,21 @@ public suspend fun ByteStream.writeToOutputStream(outputStream: OutputStream): L } } +/** + * Writes this stream to the given [OutputStream]. This method does not flush or close the given [OutputStream]. + * @param outputStream The [OutputStream] to which the contents of this stream will be written + */ +public suspend fun ByteStream.appendToOutputStream(outputStream: OutputStream): Long = withContext(Dispatchers.IO) { + val src = when (val stream = this@appendToOutputStream) { + is ByteStream.ChannelStream -> return@withContext outputStream.writeAll(stream.readFrom()) + is ByteStream.Buffer -> stream.bytes().source() + is ByteStream.SourceStream -> stream.readFrom() + } + + val out = outputStream.sink().buffer() + out.writeAll(src) +} + private suspend fun OutputStream.writeAll(chan: SdkByteReadChannel): Long = sink().use { chan.readAll(it) diff --git a/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/content/ByteStreamJVMTest.kt b/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/content/ByteStreamJVMTest.kt index 2c7e571eb..ec2ca6cb2 100644 --- a/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/content/ByteStreamJVMTest.kt +++ b/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/content/ByteStreamJVMTest.kt @@ -8,6 +8,7 @@ package aws.smithy.kotlin.runtime.content import aws.smithy.kotlin.runtime.testing.RandomTempFile import kotlinx.coroutines.test.runTest import java.io.ByteArrayOutputStream +import java.io.OutputStream import java.nio.file.Files import kotlin.test.* @@ -186,4 +187,38 @@ class ByteStreamJVMTest { assertContentEquals(binaryData, output) } } + + @Test + fun testWriteToByteStreamClosesOutput() = runTest { + val byteStream = ByteStream.fromString("Hello") + + val sos = StatusTrackingOutputStream(ByteArrayOutputStream()) + + assertFalse(sos.closed) + byteStream.writeToOutputStream(sos) + assertTrue(sos.closed) + } + + @Test + fun testAppendToByteStreamDoesNotCloseOutput() = runTest { + val byteStream = ByteStream.fromString("Don't close me!") + + val sos = StatusTrackingOutputStream(ByteArrayOutputStream()) + + assertFalse(sos.closed) + byteStream.appendToOutputStream(sos) + assertFalse(sos.closed) + } + + private class StatusTrackingOutputStream(val os: OutputStream) : OutputStream() { + var closed: Boolean = false + + override fun write(b: Int) { + os.write(b) + } + + override fun close() { + closed = true + } + } }