forked from smithy-lang/smithy-rs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ConstrainedShapeSymbolProvider.kt
192 lines (177 loc) · 9.3 KB
/
ConstrainedShapeSymbolProvider.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.server.smithy
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.NullableIndex
import software.amazon.smithy.model.shapes.BlobShape
import software.amazon.smithy.model.shapes.ByteShape
import software.amazon.smithy.model.shapes.CollectionShape
import software.amazon.smithy.model.shapes.IntegerShape
import software.amazon.smithy.model.shapes.LongShape
import software.amazon.smithy.model.shapes.MapShape
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.ShortShape
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.LengthTrait
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.WrappingSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.contextName
import software.amazon.smithy.rust.codegen.core.smithy.handleOptionality
import software.amazon.smithy.rust.codegen.core.smithy.handleRustBoxing
import software.amazon.smithy.rust.codegen.core.smithy.locatedIn
import software.amazon.smithy.rust.codegen.core.smithy.rustType
import software.amazon.smithy.rust.codegen.core.smithy.symbolBuilder
import software.amazon.smithy.rust.codegen.core.util.getTrait
import software.amazon.smithy.rust.codegen.core.util.hasTrait
import software.amazon.smithy.rust.codegen.core.util.orNull
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
import software.amazon.smithy.rust.codegen.server.smithy.generators.serverBuilderModule
import software.amazon.smithy.rust.codegen.server.smithy.traits.SyntheticStructureFromConstrainedMemberTrait
/**
* The [ConstrainedShapeSymbolProvider] returns, for a given _directly_
* constrained shape, a symbol whose Rust type can hold the constrained values.
*
* For all shapes with supported traits directly attached to them, this type is
* a [RustType.Opaque] wrapper tuple newtype holding the inner constrained
* type.
*
* The symbols this symbol provider returns are always public and exposed to
* the end user.
*
* This symbol provider is meant to be used "deep" within the wrapped symbol
* providers chain, just above the core base symbol provider, `SymbolVisitor`.
*
* If the shape is _transitively but not directly_ constrained, use
* [PubCrateConstrainedShapeSymbolProvider] instead, which returns symbols
* whose associated types are `pub(crate)` and thus not exposed to the end
* user.
*/
class ConstrainedShapeSymbolProvider(
private val base: RustSymbolProvider,
private val model: Model,
private val serviceShape: ServiceShape,
private val publicConstrainedTypes: Boolean,
) : WrappingSymbolProvider(base) {
private val nullableIndex = NullableIndex.of(model)
private fun publicConstrainedSymbolForMapOrCollectionShape(shape: Shape): Symbol {
check(shape is MapShape || shape is CollectionShape)
val (name, module) = getMemberNameAndModule(shape, serviceShape, ServerRustModule.Model, !publicConstrainedTypes)
val rustType = RustType.Opaque(name)
return symbolBuilder(shape, rustType).locatedIn(module).build()
}
override fun toSymbol(shape: Shape): Symbol {
return when (shape) {
is MemberShape -> {
// TODO(https://github.com/awslabs/smithy-rs/issues/1401) Member shapes can have constraint traits
// (constraint trait precedence).
val target = model.expectShape(shape.target)
val targetSymbol = this.toSymbol(target)
// Handle boxing first, so we end up with `Option<Box<_>>`, not `Box<Option<_>>`.
handleOptionality(
handleRustBoxing(targetSymbol, shape),
shape,
nullableIndex,
base.config().nullabilityCheckMode,
)
}
is MapShape -> {
if (shape.isDirectlyConstrained(base)) {
check(shape.hasTrait<LengthTrait>()) {
"Only the `length` constraint trait can be applied to map shapes"
}
publicConstrainedSymbolForMapOrCollectionShape(shape)
} else {
val keySymbol = this.toSymbol(shape.key)
val valueSymbol = this.toSymbol(shape.value)
symbolBuilder(shape, RustType.HashMap(keySymbol.rustType(), valueSymbol.rustType()))
.addReference(keySymbol)
.addReference(valueSymbol)
.build()
}
}
is CollectionShape -> {
if (shape.isDirectlyConstrained(base)) {
check(constrainedCollectionCheck(shape)) {
"Only the `length` and `uniqueItems` constraint traits can be applied to list shapes"
}
publicConstrainedSymbolForMapOrCollectionShape(shape)
} else {
val inner = this.toSymbol(shape.member)
symbolBuilder(shape, RustType.Vec(inner.rustType())).addReference(inner).build()
}
}
is StringShape, is IntegerShape, is ShortShape, is LongShape, is ByteShape, is BlobShape -> {
if (shape.isDirectlyConstrained(base)) {
// A standalone constrained shape goes into `ModelsModule`, but one
// arising from a constrained member shape goes into a module for the container.
val (name, module) = getMemberNameAndModule(shape, serviceShape, ServerRustModule.Model, !publicConstrainedTypes)
val rustType = RustType.Opaque(name)
symbolBuilder(shape, rustType).locatedIn(module).build()
} else {
base.toSymbol(shape)
}
}
else -> base.toSymbol(shape)
}
}
/**
* Checks that the collection:
* - Has at least 1 supported constraint applied to it, and
* - That it has no unsupported constraints applied.
*/
private fun constrainedCollectionCheck(shape: CollectionShape): Boolean {
val supportedConstraintTraits =
supportedCollectionConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet()
val allConstraintTraits = allConstraintTraits.mapNotNull { shape.getTrait(it).orNull() }.toSet()
return supportedConstraintTraits.isNotEmpty() && allConstraintTraits.subtract(supportedConstraintTraits)
.isEmpty()
}
/**
* Returns the pair (Rust Symbol Name, Inline Module) for the shape. At the time of model transformation all
* constrained member shapes are extracted and are given a model-wide unique name. However, the generated code
* for the new shapes is in a module that is named after the containing shape (structure, list, map or union).
* The new shape's Rust Symbol is renamed from `{structureName}{memberName}` to `{structure_name}::{member_name}`
*/
private fun getMemberNameAndModule(
shape: Shape,
serviceShape: ServiceShape,
defaultModule: RustModule.LeafModule,
pubCrateServerBuilder: Boolean,
): Pair<String, RustModule.LeafModule> {
val syntheticMemberTrait = shape.getTrait<SyntheticStructureFromConstrainedMemberTrait>()
?: return Pair(shape.contextName(serviceShape), defaultModule)
return if (syntheticMemberTrait.container is StructureShape) {
val builderModule = syntheticMemberTrait.container.serverBuilderModule(base, pubCrateServerBuilder)
val renameTo = syntheticMemberTrait.member.memberName ?: syntheticMemberTrait.member.id.name
Pair(renameTo.toPascalCase(), builderModule)
} else {
// For non-structure shapes, the new shape defined for a constrained member shape
// needs to be placed in an inline module named `pub {container_name_in_snake_case}`.
val moduleName = RustReservedWords.escapeIfNeeded(syntheticMemberTrait.container.id.name.toSnakeCase())
val innerModuleName = moduleName + if (pubCrateServerBuilder) {
"_internal"
} else {
""
}
val innerModule = RustModule.new(
innerModuleName,
visibility = Visibility.publicIf(!pubCrateServerBuilder, Visibility.PUBCRATE),
parent = defaultModule,
inline = true,
)
val renameTo = syntheticMemberTrait.member.memberName ?: syntheticMemberTrait.member.id.name
Pair(renameTo.toPascalCase(), innerModule)
}
}
}