Skip to content

Commit ad92991

Browse files
authored
feat: validate returned content length on S3 GetObject responses (#991)
1 parent 02ecc1b commit ad92991

File tree

6 files changed

+100
-24
lines changed

6 files changed

+100
-24
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "4e8e536e-a39c-41a8-acfb-3877528a321f",
3+
"type": "feature",
4+
"description": "Validate returned content length on S3 `GetObject` responses.",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#745"
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.codegen.customization.s3
6+
7+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
8+
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
9+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
10+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
11+
import software.amazon.smithy.kotlin.codegen.model.expectShape
12+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
13+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware
14+
import software.amazon.smithy.model.Model
15+
import software.amazon.smithy.model.shapes.OperationShape
16+
import software.amazon.smithy.model.shapes.ServiceShape
17+
18+
class GetObjectResponseLengthValidationIntegration : KotlinIntegration {
19+
override fun enabledForService(model: Model, settings: KotlinSettings) =
20+
model.expectShape<ServiceShape>(settings.service).isS3
21+
22+
override fun customizeMiddleware(ctx: ProtocolGenerator.GenerationContext, resolved: List<ProtocolMiddleware>) =
23+
resolved + responseLengthValidationMiddleware
24+
}
25+
26+
internal val responseLengthValidationMiddleware = object : ProtocolMiddleware {
27+
override val name: String = "ResponseLengthValidationMiddleware"
28+
29+
override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape) = op.id.name == "GetObject"
30+
31+
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
32+
val interceptorSymbol = RuntimeTypes.HttpClient.Interceptors.ResponseLengthValidationInterceptor
33+
writer.write("op.interceptors.add(#T())", interceptorSymbol)
34+
}
35+
}

codegen/smithy-aws-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ aws.sdk.kotlin.codegen.customization.route53.TrimResourcePrefix
2424
aws.sdk.kotlin.codegen.customization.route53.ChangeResourceRecordSetsUnmarshallingIntegration
2525
aws.sdk.kotlin.codegen.customization.s3.ClientConfigIntegration
2626
aws.sdk.kotlin.codegen.customization.s3.ContinueIntegration
27+
aws.sdk.kotlin.codegen.customization.s3.GetObjectResponseLengthValidationIntegration
2728
aws.sdk.kotlin.codegen.customization.s3.HttpPathFilter
2829
aws.sdk.kotlin.codegen.customization.s3.TruncatablePaginationIntegration
2930
aws.sdk.kotlin.codegen.customization.s3control.HostPrefixFilter

codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/customization/s3/ContinueIntegrationTest.kt

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@
55
package aws.sdk.kotlin.codegen.customization.s3
66

77
import aws.sdk.kotlin.codegen.testutil.lines
8+
import aws.sdk.kotlin.codegen.testutil.model
89
import org.junit.jupiter.api.Test
9-
import software.amazon.smithy.codegen.core.SymbolProvider
10-
import software.amazon.smithy.kotlin.codegen.KotlinSettings
11-
import software.amazon.smithy.kotlin.codegen.core.CodegenContext
1210
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
13-
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
1411
import software.amazon.smithy.kotlin.codegen.rendering.ServiceClientConfigGenerator
1512
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
1613
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware
1714
import software.amazon.smithy.kotlin.codegen.test.*
18-
import software.amazon.smithy.model.Model
1915
import software.amazon.smithy.model.shapes.OperationShape
2016
import kotlin.test.*
2117

@@ -101,25 +97,6 @@ class ContinueIntegrationTest {
10197
}
10298
}
10399

104-
private fun Model.codegenContext() = object : CodegenContext {
105-
override val model: Model = this@codegenContext
106-
override val symbolProvider: SymbolProvider get() = fail("Unexpected call to `symbolProvider`")
107-
override val settings: KotlinSettings get() = fail("Unexpected call to `settings`")
108-
override val protocolGenerator: ProtocolGenerator? = null
109-
override val integrations: List<KotlinIntegration> = listOf()
110-
}
111-
112-
private fun model(serviceName: String): Model =
113-
"""
114-
@http(method: "PUT", uri: "/foo")
115-
operation Foo { }
116-
117-
@http(method: "POST", uri: "/bar")
118-
operation Bar { }
119-
"""
120-
.prependNamespaceAndService(operations = listOf("Foo", "Bar"), serviceName = serviceName)
121-
.toSmithyModel()
122-
123100
object FooMiddleware : ProtocolMiddleware {
124101
override val name: String = "FooMiddleware"
125102
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.codegen.customization.s3
6+
7+
import aws.sdk.kotlin.codegen.testutil.model
8+
import org.junit.jupiter.api.Test
9+
import software.amazon.smithy.kotlin.codegen.test.defaultSettings
10+
import software.amazon.smithy.kotlin.codegen.test.newTestContext
11+
import kotlin.test.assertEquals
12+
import kotlin.test.assertFalse
13+
import kotlin.test.assertTrue
14+
15+
class GetObjectResponseLengthValidationIntegrationTest {
16+
@Test
17+
fun testNotExpectedForNonS3Model() {
18+
val model = model("NotS3")
19+
val actual = GetObjectResponseLengthValidationIntegration().enabledForService(model, model.defaultSettings())
20+
assertFalse(actual)
21+
}
22+
23+
@Test
24+
fun testExpectedForS3Model() {
25+
val model = model("S3")
26+
val actual = GetObjectResponseLengthValidationIntegration().enabledForService(model, model.defaultSettings())
27+
assertTrue(actual)
28+
}
29+
30+
@Test
31+
fun testMiddlewareAddition() {
32+
val model = model("S3")
33+
val preexistingMiddleware = listOf(FooMiddleware)
34+
val ctx = model.newTestContext("S3")
35+
val actual = GetObjectResponseLengthValidationIntegration().customizeMiddleware(ctx.generationCtx, preexistingMiddleware)
36+
37+
assertEquals(listOf(FooMiddleware, responseLengthValidationMiddleware), actual)
38+
}
39+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package aws.sdk.kotlin.codegen.testutil
2+
3+
import software.amazon.smithy.kotlin.codegen.test.prependNamespaceAndService
4+
import software.amazon.smithy.kotlin.codegen.test.toSmithyModel
5+
import software.amazon.smithy.model.Model
6+
7+
internal fun model(serviceName: String): Model =
8+
"""
9+
@http(method: "PUT", uri: "/foo")
10+
operation Foo { }
11+
12+
@http(method: "POST", uri: "/bar")
13+
operation Bar { }
14+
"""
15+
.prependNamespaceAndService(operations = listOf("Foo", "Bar"), serviceName = serviceName)
16+
.toSmithyModel()

0 commit comments

Comments
 (0)