-
Notifications
You must be signed in to change notification settings - Fork 179
/
ConstraintViolationSymbolProvider.kt
143 lines (131 loc) · 5.9 KB
/
ConstraintViolationSymbolProvider.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
/*
* 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.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.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.shapes.UnionShape
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.ModelsModule
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.locatedIn
import software.amazon.smithy.rust.codegen.core.smithy.module
import software.amazon.smithy.rust.codegen.core.smithy.rustType
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
import software.amazon.smithy.rust.codegen.server.smithy.generators.serverBuilderSymbol
/**
* The [ConstraintViolationSymbolProvider] returns, for a given constrained
* shape, a symbol whose Rust type can hold information about constraint
* violations that may occur when building the shape from unconstrained values.
*
* So, for example, given the model:
*
* ```smithy
* @pattern("\\w+")
* @length(min: 1, max: 69)
* string NiceString
*
* structure Structure {
* @required
* niceString: NiceString
* }
* ```
*
* A `NiceString` built from an arbitrary Rust `String` may give rise to at
* most two constraint trait violations: one for `pattern`, one for `length`.
* Similarly, the shape `Structure` can fail to be built when a value for
* `niceString` is not provided.
*
* Said type is always called `ConstraintViolation`, and resides in a bespoke
* module inside the same module as the _public_ constrained type the user is
* exposed to. When the user is _not_ exposed to the constrained type, the
* constraint violation type's module is a child of the `model` module.
*
* It is the responsibility of the caller to ensure that the shape is
* constrained (either directly or transitively) before using this symbol
* provider. This symbol provider intentionally crashes if the shape is not
* constrained.
*/
class ConstraintViolationSymbolProvider(
private val base: RustSymbolProvider,
private val model: Model,
private val publicConstrainedTypes: Boolean,
private val serviceShape: ServiceShape,
) : WrappingSymbolProvider(base) {
private val constraintViolationName = "ConstraintViolation"
private val visibility = when (publicConstrainedTypes) {
true -> Visibility.PUBLIC
false -> Visibility.PUBCRATE
}
private fun Shape.shapeModule(): RustModule.LeafModule {
val documentation = if (publicConstrainedTypes && this.isDirectlyConstrained(base)) {
"See [`${this.contextName(serviceShape)}`]."
} else {
null
}
return RustModule.new(
// Need to use the context name so we get the correct name for maps.
name = RustReservedWords.escapeIfNeeded(this.contextName(serviceShape)).toSnakeCase(),
visibility = visibility,
parent = ModelsModule,
inline = true,
documentation = documentation,
)
}
private fun constraintViolationSymbolForCollectionOrMapOrUnionShape(shape: Shape): Symbol {
check(shape is CollectionShape || shape is MapShape || shape is UnionShape)
val module = shape.shapeModule()
val rustType = RustType.Opaque(constraintViolationName, module.fullyQualifiedPath())
return Symbol.builder()
.rustType(rustType)
.name(rustType.name)
.locatedIn(module)
.build()
}
override fun toSymbol(shape: Shape): Symbol {
check(shape.canReachConstrainedShape(model, base)) {
"`ConstraintViolationSymbolProvider` was called on shape that does not reach a constrained shape: $shape"
}
return when (shape) {
is MapShape, is CollectionShape, is UnionShape -> {
constraintViolationSymbolForCollectionOrMapOrUnionShape(shape)
}
is StructureShape -> {
val builderSymbol = shape.serverBuilderSymbol(base, pubCrate = !publicConstrainedTypes)
val module = builderSymbol.module()
val rustType = RustType.Opaque(constraintViolationName, module.fullyQualifiedPath())
Symbol.builder()
.rustType(rustType)
.name(rustType.name)
.locatedIn(module)
.build()
}
is StringShape, is IntegerShape, is ShortShape, is LongShape, is ByteShape -> {
val module = shape.shapeModule()
val rustType = RustType.Opaque(constraintViolationName, module.fullyQualifiedPath())
Symbol.builder()
.rustType(rustType)
.name(rustType.name)
.locatedIn(module)
.build()
}
else -> TODO("Constraint traits on other shapes not implemented yet: $shape")
}
}
}