Skip to content

Commit 06cef8a

Browse files
authored
fix: require values for HTTP query- and queryParams-bound parameters (#697)
1 parent 746aaff commit 06cef8a

File tree

3 files changed

+91
-4
lines changed

3 files changed

+91
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "10bfb10c-9874-4480-bc75-26157b725e22",
3+
"type": "bugfix",
4+
"description": "Require values for HTTP query- and queryParams-bound parameters"
5+
}

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import software.amazon.smithy.codegen.core.Symbol
99
import software.amazon.smithy.kotlin.codegen.core.*
1010
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
1111
import software.amazon.smithy.kotlin.codegen.model.*
12+
import software.amazon.smithy.kotlin.codegen.utils.getOrNull
1213
import software.amazon.smithy.model.shapes.*
1314
import software.amazon.smithy.model.traits.ErrorTrait
1415
import software.amazon.smithy.model.traits.HttpLabelTrait
16+
import software.amazon.smithy.model.traits.HttpQueryParamsTrait
17+
import software.amazon.smithy.model.traits.HttpQueryTrait
18+
import software.amazon.smithy.model.traits.LengthTrait
1519
import software.amazon.smithy.model.traits.RetryableTrait
1620
import software.amazon.smithy.model.traits.SensitiveTrait
1721
import software.amazon.smithy.model.traits.StreamingTrait
@@ -82,17 +86,35 @@ class StructureGenerator(
8286
writer.write("override val #1L: #2F = builder.#1L", memberName, memberSymbol)
8387
}
8488

85-
memberShape.hasTrait<HttpLabelTrait>() ->
89+
memberShape.isRequiredInStruct -> {
8690
writer.write(
8791
"""public val #1L: #2F = requireNotNull(builder.#1L) { "A non-null value must be provided for #1L" }""",
8892
memberName,
8993
memberSymbol,
9094
)
95+
if (memberShape.isNonBlankInStruct) {
96+
writer
97+
.indent()
98+
.write(
99+
""".apply { require(isNotBlank()) { "A non-blank value must be provided for #L" } }""",
100+
memberName,
101+
)
102+
.dedent()
103+
}
104+
}
91105

92106
else -> writer.write("public val #1L: #2F = builder.#1L", memberName, memberSymbol)
93107
}
94108
}
95109

110+
private val MemberShape.isRequiredInStruct
111+
get() =
112+
hasTrait<HttpLabelTrait>() ||
113+
isRequired && (hasTrait<HttpQueryTrait>() || hasTrait<HttpQueryParamsTrait>())
114+
115+
private val MemberShape.isNonBlankInStruct
116+
get() = getTrait<LengthTrait>()?.min?.getOrNull()?.takeIf { it > 0 } != null
117+
96118
private fun renderCompanionObject() {
97119
writer.withBlock("public companion object {", "}") {
98120
write("public operator fun invoke(block: Builder.() -> #Q): #Q = Builder().apply(block).build()", KotlinTypes.Unit, symbol)

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

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ class StructureGeneratorTest {
280280
}
281281

282282
@Test
283-
fun `it handles HTTP labels in initializers`() {
283+
fun `it handles required HTTP fields in initializers`() {
284284
val model = """
285285
@http(method: "POST", uri: "/foo/{bar}/{baz}")
286286
operation Foo {
@@ -292,12 +292,29 @@ class StructureGeneratorTest {
292292
@httpLabel
293293
bar: String,
294294
295-
@required
296295
@httpLabel
296+
@required
297297
baz: Integer,
298298
299299
@httpPayload
300-
qux: String
300+
qux: String,
301+
302+
@required
303+
@httpQuery("quux")
304+
quux: Boolean,
305+
306+
@httpQuery("corge")
307+
corge: String,
308+
309+
@required
310+
@length(min: 0)
311+
@httpQuery("grault")
312+
grault: String,
313+
314+
@required
315+
@length(min: 3)
316+
@httpQuery("garply")
317+
garply: String
301318
}
302319
""".prependNamespaceAndService(operations = listOf("Foo")).toSmithyModel()
303320

@@ -312,11 +329,54 @@ class StructureGeneratorTest {
312329
public class FooRequest private constructor(builder: Builder) {
313330
public val bar: kotlin.String? = requireNotNull(builder.bar) { "A non-null value must be provided for bar" }
314331
public val baz: kotlin.Int? = requireNotNull(builder.baz) { "A non-null value must be provided for baz" }
332+
public val corge: kotlin.String? = builder.corge
333+
public val garply: kotlin.String? = requireNotNull(builder.garply) { "A non-null value must be provided for garply" }
334+
.apply { require(isNotBlank()) { "A non-blank value must be provided for garply" } }
335+
public val grault: kotlin.String? = requireNotNull(builder.grault) { "A non-null value must be provided for grault" }
336+
public val quux: kotlin.Boolean? = requireNotNull(builder.quux) { "A non-null value must be provided for quux" }
315337
public val qux: kotlin.String? = builder.qux
316338
""".formatForTest(indent = "")
317339
generated.shouldContainOnlyOnceWithDiff(expected)
318340
}
319341

342+
@Test
343+
fun `it handles required query params in initializers`() {
344+
val model = """
345+
@http(method: "POST", uri: "/foo")
346+
operation Foo {
347+
input: FooRequest
348+
}
349+
350+
map MapOfStrings {
351+
key: String,
352+
value: String
353+
}
354+
355+
structure FooRequest {
356+
@required
357+
@httpQueryParams
358+
bar: MapOfStrings
359+
360+
@httpPayload
361+
baz: String
362+
}
363+
""".prependNamespaceAndService(operations = listOf("Foo")).toSmithyModel()
364+
365+
val provider: SymbolProvider = KotlinCodegenPlugin.createSymbolProvider(model)
366+
val writer = KotlinWriter(TestModelDefault.NAMESPACE)
367+
val struct = model.expectShape<StructureShape>("com.test#FooRequest")
368+
val renderingCtx = RenderingContext(writer, struct, model, provider, model.defaultSettings())
369+
StructureGenerator(renderingCtx).render()
370+
371+
val generated = writer.toString()
372+
val expected = """
373+
public class FooRequest private constructor(builder: Builder) {
374+
public val bar: Map<String, String>? = requireNotNull(builder.bar) { "A non-null value must be provided for bar" }
375+
public val baz: kotlin.String? = builder.baz
376+
""".formatForTest(indent = "")
377+
generated.shouldContainOnlyOnceWithDiff(expected)
378+
}
379+
320380
@Test
321381
fun `it handles blob shapes`() {
322382
// blobs (with and without streaming) require special attention in equals() and hashCode() implementations

0 commit comments

Comments
 (0)