-
Notifications
You must be signed in to change notification settings - Fork 180
/
ConstraintViolationSymbolProvider.kt
138 lines (130 loc) · 5.75 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
/*
* 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.CollectionShape
import software.amazon.smithy.model.shapes.IntegerShape
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.StringShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.shapes.UnionShape
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.smithy.Models
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.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 serviceShape: ServiceShape,
private val publicConstrainedTypes: Boolean,
) : WrappingSymbolProvider(base) {
private val constraintViolationName = "ConstraintViolation"
private fun constraintViolationSymbolForCollectionOrMapOrUnionShape(shape: Shape): Symbol {
check(shape is CollectionShape || shape is MapShape || shape is UnionShape)
val symbol = base.toSymbol(shape)
val constraintViolationNamespace =
"${symbol.namespace.let { it.ifEmpty { "crate::${Models.namespace}" } }}::${
RustReservedWords.escapeIfNeeded(
shape.contextName(serviceShape).toSnakeCase(),
)
}"
val rustType = RustType.Opaque(constraintViolationName, constraintViolationNamespace)
return Symbol.builder()
.rustType(rustType)
.name(rustType.name)
.namespace(rustType.namespace, "::")
.definitionFile(symbol.definitionFile)
.build()
}
override fun toSymbol(shape: Shape): Symbol {
check(shape.canReachConstrainedShape(model, base))
return when (shape) {
is MapShape, is CollectionShape, is UnionShape -> {
constraintViolationSymbolForCollectionOrMapOrUnionShape(shape)
}
is StructureShape -> {
val builderSymbol = shape.serverBuilderSymbol(base, pubCrate = !publicConstrainedTypes)
val namespace = builderSymbol.namespace
val rustType = RustType.Opaque(constraintViolationName, namespace)
Symbol.builder()
.rustType(rustType)
.name(rustType.name)
.namespace(rustType.namespace, "::")
.definitionFile(builderSymbol.definitionFile)
.build()
}
is StringShape -> {
val namespace = "crate::${Models.namespace}::${
RustReservedWords.escapeIfNeeded(
shape.contextName(serviceShape).toSnakeCase(),
)
}"
val rustType = RustType.Opaque(constraintViolationName, namespace)
Symbol.builder()
.rustType(rustType)
.name(rustType.name)
.namespace(rustType.namespace, "::")
.definitionFile(Models.filename)
.build()
}
is IntegerShape -> {
val namespace = "crate::${Models.namespace}::${
RustReservedWords.escapeIfNeeded(
shape.contextName(serviceShape).toSnakeCase(),
)
}"
val rustType = RustType.Opaque(constraintViolationName, namespace)
Symbol.builder()
.rustType(rustType)
.name(rustType.name)
.namespace(rustType.namespace, "::")
.definitionFile(Models.filename)
.build()
}
else -> TODO("Constraint traits on other shapes not implemented yet: $shape")
}
}
}