-
Notifications
You must be signed in to change notification settings - Fork 180
/
HttpRequestChecksumDecorator.kt
166 lines (151 loc) · 7.62 KB
/
HttpRequestChecksumDecorator.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rustsdk
import software.amazon.smithy.aws.traits.HttpChecksumTrait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError
import software.amazon.smithy.rust.codegen.core.util.expectMember
import software.amazon.smithy.rust.codegen.core.util.getTrait
import software.amazon.smithy.rust.codegen.core.util.inputShape
import software.amazon.smithy.rust.codegen.core.util.orNull
fun RuntimeConfig.awsInlineableBodyWithChecksum() = RuntimeType.forInlineDependency(
InlineAwsDependency.forRustFile(
"http_body_checksum", visibility = Visibility.PUBLIC,
CargoDependency.Http,
CargoDependency.HttpBody,
CargoDependency.smithyHttp(this),
CargoDependency.smithyChecksums(this),
CargoDependency.smithyTypes(this),
CargoDependency.Bytes,
CargoDependency.Tracing,
),
)
class HttpRequestChecksumDecorator : ClientCodegenDecorator {
override val name: String = "HttpRequestChecksum"
override val order: Byte = 0
override fun operationCustomizations(
codegenContext: ClientCodegenContext,
operation: OperationShape,
baseCustomizations: List<OperationCustomization>,
): List<OperationCustomization> {
return baseCustomizations + HttpRequestChecksumCustomization(codegenContext, operation)
}
}
private fun HttpChecksumTrait.requestAlgorithmMember(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
): String? {
val requestAlgorithmMember = this.requestAlgorithmMember.orNull() ?: return null
val checksumAlgorithmMemberShape =
operationShape.inputShape(codegenContext.model).expectMember(requestAlgorithmMember)
return codegenContext.symbolProvider.toMemberName(checksumAlgorithmMemberShape)
}
private fun HttpChecksumTrait.checksumAlgorithmToStr(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
): Writable {
val runtimeConfig = codegenContext.runtimeConfig
val requestAlgorithmMember = this.requestAlgorithmMember(codegenContext, operationShape)
val isRequestChecksumRequired = this.isRequestChecksumRequired
return {
if (requestAlgorithmMember != null) {
// User may set checksum for requests, and we need to call as_ref before we can convert the algorithm to a &str
rust("let checksum_algorithm = $requestAlgorithmMember.as_ref();")
if (isRequestChecksumRequired) {
// Checksums are required, fall back to MD5
rust("""let checksum_algorithm = checksum_algorithm.map(|algorithm| algorithm.as_str()).or(Some("md5"));""")
} else {
// Checksums aren't required, don't set a fallback
rust("let checksum_algorithm = checksum_algorithm.map(|algorithm| algorithm.as_str());")
}
} else if (isRequestChecksumRequired) {
// Checksums are required but a user can't set one, so we set MD5 for them
rust("""let checksum_algorithm = Some("md5");""")
}
rustTemplate(
"""
let checksum_algorithm = match checksum_algorithm {
Some(algo) => Some(
algo.parse::<#{ChecksumAlgorithm}>()
.map_err(#{BuildError}::other)?
),
None => None,
};
""",
"BuildError" to runtimeConfig.operationBuildError(),
"ChecksumAlgorithm" to RuntimeType.smithyChecksums(runtimeConfig).resolve("ChecksumAlgorithm"),
)
// If a request checksum is not required and there's no way to set one, do nothing
// This happens when an operation only supports response checksums
}
}
// This generator was implemented based on this spec:
// https://awslabs.github.io/smithy/1.0/spec/aws/aws-core.html#http-request-checksums
class HttpRequestChecksumCustomization(
private val codegenContext: ClientCodegenContext,
private val operationShape: OperationShape,
) : OperationCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
override fun section(section: OperationSection): Writable {
// Get the `HttpChecksumTrait`, returning early if this `OperationShape` doesn't have one
val checksumTrait = operationShape.getTrait<HttpChecksumTrait>() ?: return emptySection
val checksumAlgorithm = checksumTrait.requestAlgorithmMember(codegenContext, operationShape)
return when (section) {
is OperationSection.MutateInput -> {
// Various other things will consume the input struct before we can get at the checksum algorithm
// field within it. This ensures that we preserve a copy of it. It's an enum so cloning is cheap.
if (checksumAlgorithm != null) {
return {
rust("let $checksumAlgorithm = self.$checksumAlgorithm().cloned();")
}
} else {
emptySection
}
}
is OperationSection.MutateRequest -> {
// Return early if no request checksum can be set nor is it required
if (!checksumTrait.isRequestChecksumRequired && checksumAlgorithm == null) {
return emptySection
} else {
// `add_checksum_calculation_to_request` handles both streaming and in-memory request bodies.
return {
rustTemplate(
"""
${section.request} = ${section.request}.augment(|mut req, properties| {
#{checksum_algorithm_to_str:W}
if let Some(checksum_algorithm) = checksum_algorithm {
#{add_checksum_calculation_to_request}(&mut req, properties, checksum_algorithm)?;
}
Result::<_, #{BuildError}>::Ok(req)
})?;
""",
"checksum_algorithm_to_str" to checksumTrait.checksumAlgorithmToStr(
codegenContext,
operationShape,
),
"add_checksum_calculation_to_request" to runtimeConfig.awsInlineableBodyWithChecksum()
.resolve("add_checksum_calculation_to_request"),
"BuildError" to runtimeConfig.operationBuildError(),
)
}
}
}
else -> {
return emptySection
}
}
}
}