Skip to content

Commit 262966f

Browse files
committed
Merge remote-tracking branch 'origin/main' into release
2 parents 20755d2 + 58d4642 commit 262966f

File tree

89 files changed

+4380
-679
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+4380
-679
lines changed

builder.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
"test_steps": [
2222
"{gradlew} test allTests"
2323
],
24+
"upstream": [
25+
{
26+
"name": "aws-crt-kotlin"
27+
}
28+
],
2429
"downstream": [
2530
{ "name": "aws-sdk-kotlin" }
2631
]

docs/design/event-streams.md

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,19 @@ structure ThrottlingError {}
8989

9090
### Event Stream Type Representation
9191

92-
The members of an operation input or output that target a stream will be represented with an asynchronous [Flow](https://kotlinlang.org/docs/reference/coroutines/flow.html)
93-
from the `kotlinx-coroutines-core` library. `Flow` is a natural fit for representing asynchronous streams.
92+
The members of an operation input or output that target a stream will be represented with an asynchronous
93+
[Flow](https://kotlinlang.org/docs/reference/coroutines/flow.html) from the `kotlinx-coroutines-core` library.
94+
`Flow` is a natural fit for representing asynchronous streams.
9495

95-
`Flow` was chosen for pagination and already in use as part of our public API contract. Any alternative to this would require a custom but similar type that doesn't play well with
96-
the rest of the coroutine ecosystem. There is also prior art for representing streaming requests and responses, see [gRPC Kotlin](https://github.com/grpc/grpc-kotlin).
96+
`Flow` was chosen for pagination and already in use as part of our public API contract. Any alternative to this
97+
would require a custom but similar type that doesn't play well with the rest of the coroutine ecosystem. There is
98+
also prior art for representing streaming requests and responses, see [gRPC Kotlin](https://github.com/grpc/grpc-kotlin).
9799

98100
The following types and service would be generated.
99101

100-
NOTE: only the input and output types are shown, the other structures or unions in the model would be generated as described in [Kotlin Smithy Design](kotlin-smithy-sdk.md).
102+
NOTE: only the input and output types are shown, the other structures or unions in the model would be generated as
103+
described in [Kotlin Smithy Design](kotlin-smithy-sdk.md) with the exception of event stream members targeting errors
104+
which is described below in more detail.
101105

102106
#### Input Event Streams
103107

@@ -120,7 +124,8 @@ class PublishMessagesRequest private constructor(builder: Builder){
120124

121125
#### Output Event Streams
122126

123-
Output event streams would be modeled the same way as input streams. The response object would have a `Flow<T>` field that represents the response stream.
127+
Output event streams would be modeled the same way as input streams. The response object would have a `Flow<T>`
128+
field that represents the response stream.
124129

125130
```kt
126131
class SubscribeToMovementsResponse private constructor(builder: Builder){
@@ -137,20 +142,81 @@ class SubscribeToMovementsResponse private constructor(builder: Builder){
137142
```
138143

139144

140-
Modeling the event stream as a field of the request or response allows for [initial messages](https://awslabs.github.io/smithy/1.0/spec/core/stream-traits.html#initial-messages)
141-
to be implemented. If we directly returned or took a `Flow<T>` as the input or output type we would not be able to represent the initial request or response fields when present.
145+
Modeling the event stream as a field of the request or response allows for
146+
[initial messages](https://awslabs.github.io/smithy/1.0/spec/core/stream-traits.html#initial-messages) to be
147+
implemented. If we directly returned or took a `Flow<T>` as the input or output type we would not be able to
148+
represent the initial request or response fields when present.
142149

143150

151+
#### Event Stream Error Representation
152+
153+
Event stream unions may model exceptions that can appear on the stream. These exceptions are terminal messages that
154+
are intended to be surfaced to the client using idiomatic error handling mechanisms of the target language. Thus,
155+
the modeled errors a consumer may see on the stream are part of the overall union that makes up the possible events.
156+
157+
NOTE: the set of errors on the operation MAY not be the same set of errors modeled on the event stream.
158+
159+
160+
Using the example from above:
161+
162+
```
163+
@streaming
164+
union MovementEvents {
165+
up: Movement,
166+
down: Movement,
167+
left: Movement,
168+
right: Movement,
169+
throttlingError: ThrottlingError
170+
}
171+
172+
```
173+
174+
The default representation of a union (as documented in [Kotlin Smithy Design](kotlin-smithy-sdk.md)) is generated as:
175+
176+
```kotlin
177+
sealed class MovementEvents {
178+
data class Up(val value: Movement): MovementEvents()
179+
data class Down(val value: Movement): MovementEvents()
180+
data class Left(val value: Movement): MovementEvents()
181+
data class Right(val value: Movement): MovementEvents()
182+
data class ThrottlingError(val value: ThrottlingError): MovementEvents()
183+
object SdkUnknown : MovementEvents()
184+
}
185+
```
186+
187+
This is undesirable though since event stream errors are terminal and end the stream. Keeping them in the set of
188+
possible events also means it may be easier for consumers to ignore errors depending on what events they are looking
189+
for (e.g. by having a catch all `else` branch they may inadvertently ignore an error and think the stream completed
190+
successfully).
191+
192+
193+
Event stream unions will be special-cased to filter out variants targeting error shapes. When these errors are
194+
emitted by the service on the stream they will be converted to the appropriate modeled exception and thrown rather
195+
than being emitted on the stream the consumer sees.
196+
197+
As an example, the generated event stream union will look like this (note the absence of `ThrottlingError`):
198+
199+
```kotlin
200+
sealed class MovementEvents {
201+
data class Up(val value: Movement): MovementEvents()
202+
data class Down(val value: Movement): MovementEvents()
203+
data class Left(val value: Movement): MovementEvents()
204+
data class Right(val value: Movement): MovementEvents()
205+
object SdkUnknown : MovementEvents()
206+
}
207+
```
208+
144209
### **Service and Usage**
145210

146211
NOTE: There are types and internal details here not important to the design of how customers will interact with
147-
streaming requests/responses (e.g. serialization/deserialization).
148-
Those details are subject to change and not part of this design document. The focus here is on the way
149-
streaming is exposed to a customer.
212+
streaming requests/responses (e.g. serialization/deserialization). Those details are subject to change and not part of
213+
this design document. The focus here is on the way streaming is exposed to a customer.
150214

151215

152-
The signatures generated match that of binary streaming requests and responses. Notably that output streams take a lambda instead of returning the response directly (see [binary-streaming design](binary-streaming.md) which discusses this pattern).
153-
The response (and event stream) are only valid in that scope, after which the resources consumed by the stream are closed and no longer valid.
216+
The signatures generated match that of binary streaming requests and responses. Notably that output streams take a
217+
lambda instead of returning the response directly (see [binary-streaming design](binary-streaming.md) which
218+
discusses this pattern). The response (and event stream) are only valid in that scope, after which the resources
219+
consumed by the stream are closed and no longer valid.
154220

155221

156222
```kt
@@ -206,7 +272,6 @@ fun main() = runBlocking{
206272
is MovementEvents.Down,
207273
is MovementEvents.Left,
208274
is MovementEvents.Right -> handleMovement(event)
209-
is MovementEvents.ThrottlingError -> throw event.throttlingError
210275
else -> error("unknown event type: $event")
211276
}
212277
}
@@ -218,8 +283,9 @@ fun main() = runBlocking{
218283
private fun handleMovement(event: MovementEvents) { ... }
219284
```
220285

221-
Accepting a lambda matches what is generated for binary streams (see [binary-streaming design](binary-streaming.md)) and will provide a consistent API experience as well
222-
as the same benefits to the SDK (properly scoped lifetime for resources).
286+
Accepting a lambda matches what is generated for binary streams (see [binary-streaming design](binary-streaming.md))
287+
and will provide a consistent API experience as well as the same benefits to the SDK (properly scoped lifetime
288+
for resources).
223289

224290

225291
# Appendix
@@ -228,9 +294,10 @@ as the same benefits to the SDK (properly scoped lifetime for resources).
228294
## Java Interop
229295

230296
`Flow<T>` is not easily consumable directly from Java due to the `suspend` nature of it. JetBrains provides
231-
[reactive adapters](https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive) that can be used to convert rxJava and JDK-9
232-
reactive streams to or from an equivalent `Flow`. Users would be responsible for creating a shim layer using these primitives provided
233-
by JetBrains which would allow them to expose the Kotlin functions however they see fit to their applications.
297+
[reactive adapters](https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive) that can be used to convert
298+
rxJava and JDK-9 reactive streams to or from an equivalent `Flow`. Users would be responsible for creating a shim
299+
layer using these primitives provided by JetBrains which would allow them to expose the Kotlin functions however
300+
they see fit to their applications.
234301

235302

236303
## Additional References
@@ -243,4 +310,5 @@ by JetBrains which would allow them to expose the Kotlin functions however they
243310

244311
# Revision history
245312

313+
* 02/17/2022 - Remove errors from generated event stream
246314
* 01/19/2022 - Created

docs/design/paginators.md

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Pagination Design
22

33
* **Type**: Design
4-
* **Author(s)**: Aaron Todd
4+
* **Author(s)**: Aaron Todd, Ken Gilmer
55

66
# Abstract
77

@@ -88,13 +88,16 @@ public API for the response of a paginated operation. Runtime library code is
8888
not required for `Flow`-based paginators, as all functionality will be provided
8989
by the Kotlin standard lib and the `kotlinx-coroutines-core` library.
9090

91-
Pagination by default will always generate an extension function that returns
92-
a `Flow` over the normal operation output type.
91+
Each operation that supports pagination would receive an extension function off the
92+
service client interface that returns a `Flow` over the normal operation output type.
93+
The generated function name uses the operation name plus the suffix `Paginated`.
94+
This way the paginated and non-paginated operations should show up next to eachother
95+
in the IDE to promote discoverability.
9396

9497
An example of this is demonstrated below (codegen):
9598

9699
```kotlin
97-
fun LambdaClient.paginateListFunctions(initialRequest: ListFunctionsRequest): Flow<ListFunctionsResponse> =
100+
fun LambdaClient.listFunctionsPaginated(initialRequest: ListFunctionsRequest): Flow<ListFunctionsResponse> =
98101
flow {
99102
var cursor: String? = null
100103
var isFirstPage: Boolean = true
@@ -124,6 +127,13 @@ works from the base response flow. The targeted type will provide iteration to
124127
the `item` element for the user automatically. The name of the generated
125128
function comes from the name of the member.
126129

130+
Additionally, the `item` specified in the model may be deeply nested and involve
131+
operations and types with many words. Generating a single function that combines
132+
the operation, some word to indicate pagination, plus the nested item's name
133+
often produces unreadable function signatures. Instead the member name targeted
134+
by the `items` will be used. Thas has the advantage of matching the output structure
135+
type property name.
136+
127137
Here is an example that follows from the previous example (codegen):
128138

129139
```kotlin
@@ -135,18 +145,6 @@ fun Flow<ListFunctionsResponse>.functions(): Flow<FunctionConfiguration> =
135145
}
136146
```
137147

138-
### Creating a Paginator
139-
140-
Each operation that supports pagination would receive an extension function (
141-
based from the service client interface) to create a paginator. The function
142-
generated always begins with the suffix `Paginated`, helping with
143-
the `discoverablity`
144-
design consideration, by allowing users to quickly view all paginatable
145-
operations by specifying this literal suffix in the IDE. Once customers are
146-
familiar with these two prefixes, discovering what pagination options are
147-
available for a given client or paginated response is simple and consistent.
148-
149-
Using the model in the introduction would produce the following:
150148

151149
### Examples
152150

@@ -157,20 +155,13 @@ An example of driving a paginator and processing response instances:
157155
```kotlin
158156
suspend fun rawPaginationExample(client: LambdaClient) {
159157
lambdaClient
160-
.paginateListFunctions(ListFunctionsRequest {})
158+
.listFunctionsPaginated(ListFunctionsRequest {})
161159
.collect { response ->
162160
response.functions?.forEach { functionConfiguration ->
163161
println(functionConfiguration.functionName)
164162
}
165163
}
166164
}
167-
168-
fun Flow<ListFunctionsResponse>.items(): Flow<FunctionConfiguration> =
169-
transform() { response ->
170-
response.functions?.forEach {
171-
emit(it)
172-
}
173-
}
174165
```
175166

176167
#### Usage - Iterating over Modeled Item
@@ -180,12 +171,6 @@ transform we are able to iterate over the nested element. As transforms can be
180171
applied to any member of a type, users may extend the abstraction as needed
181172
simply by providing their own flow transforms.
182173

183-
Additionally, the `item` specified in the model may be deeply nested and involve
184-
operations and types with many words. Generating a single function that combines
185-
the operation, some word to indicate pagination, plus the nested item's name
186-
often produces unreadable function signatures. In this approach, we generate
187-
more legible API by breaking the extraction of the nested items into a separate
188-
function, which always uses the name of the member referenced as the item.
189174

190175
```kotlin
191176
lambdaClient
@@ -381,6 +366,7 @@ future.
381366

382367
# Revision history
383368

369+
* 03/10/2022 - Fix function names
384370
* 12/15/2021 - Adapted to favor Flow
385371
* 8/23/2021 - Initial upload
386372
* 2/05/2021 - Created

gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ kotlinLoggingVersion=2.0.3
3939

4040
# logging - JVM
4141
slf4jVersion=1.7.30
42+
43+
# crt
44+
crtKotlinVersion=0.5.4-SNAPSHOT

runtime/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ subprojects {
4343
// don't configure anything
4444
if (!needsConfigure) return@subprojects
4545

46-
// TODO - the group to publish under needs negotiated still
4746
group = "aws.smithy.kotlin"
4847
version = sdkVersion
4948

runtime/crt-util/build.gradle.kts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
buildscript {
7+
val atomicFuVersion: String by project
8+
repositories {
9+
mavenCentral()
10+
}
11+
12+
dependencies {
13+
classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicFuVersion")
14+
}
15+
}
16+
17+
apply(plugin = "kotlinx-atomicfu")
18+
19+
description = "Utilities for working with AWS CRT Kotlin"
20+
extra["displayName"] = "Smithy :: Kotlin :: CRT :: Util"
21+
extra["moduleName"] = "aws.smithy.kotlin.runtime.crt"
22+
23+
val atomicFuVersion: String by project
24+
val coroutinesVersion: String by project
25+
val crtKotlinVersion: String by project
26+
27+
kotlin {
28+
sourceSets {
29+
commonMain {
30+
dependencies {
31+
api(project(":runtime:runtime-core"))
32+
api("aws.sdk.kotlin.crt:aws-crt-kotlin:$crtKotlinVersion")
33+
api(project(":runtime:protocol:http"))
34+
implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
35+
}
36+
}
37+
commonTest {
38+
dependencies {
39+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
40+
}
41+
}
42+
43+
all {
44+
languageSettings.optIn("aws.smithy.kotlin.runtime.util.InternalApi")
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)