-
Notifications
You must be signed in to change notification settings - Fork 214
Builders of builders #1342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Builders of builders #1342
Changes from 218 commits
1fd9c74
3d18e88
6e67a1c
442eddd
a274ec5
9f2065d
3a9bc56
2fe280d
8864f1d
b77f6fb
07da98f
1f6ef66
0cc2644
e9e8205
4422b52
8c46710
831bfc0
465139b
ff0c43b
03ff5fd
8f322cd
80ec705
fde11e9
fcd75c8
f81550c
d7b6071
c9b84c8
3670c69
fb168a5
0a03b71
af9871f
733d235
d97abfe
fe35cc2
4fc2ee5
6488239
ae351a3
ca32c8e
07007ab
5c7636d
5accf0b
9b163ab
5acda33
587fa9c
8f26ac0
cda1c09
4e31944
98b5c62
b143866
326cbd1
94a6fa2
e3616fd
3b5e751
51c83b6
893060e
fb21f93
c50ca58
4317175
e691bf7
0a02661
af2dda1
6e458ea
8afed5e
1168c5b
4abc947
3874c0e
783d384
498a0d5
9d06368
39b1712
0602785
aa1a2fb
6036e9e
83dd8c8
effa0e5
7d9f601
3afae8e
a52886d
71fb17f
8d3f434
6496b47
2744eb5
679518e
0983546
ea1bbca
d34af64
7829ddc
e280a01
f2e3b27
b20e5c4
52be445
0e4ce40
fbf0aac
dc37a8d
ad86d7d
eaf918b
a2f9980
f7218d2
6d78301
21a117a
c56d9fb
d327f31
3bdb1ca
0d8bdb9
0b9cd6b
d51238d
61cc2e7
96981d3
3fc9af4
4f0ffef
c2e2b56
fbba870
7cf173e
850a1bf
194a19b
7a6b9ca
ce42e3e
9f8f925
035e4c7
bbfc42e
91cce7a
a674f9a
56dc435
24da7b1
183fe57
1e7c975
3692e67
466b03c
4e62c13
39c457d
181769b
3c9fb91
d8a1591
f0bccb3
b49aa17
eafcc30
493cd6c
d816c8f
77acd23
b19ae9a
89c927b
c40b158
e64203e
3111c7c
5487991
c3806eb
dbf7d9e
0789fce
d6e3f89
9569928
d9abb86
c312910
860e272
e8c42fd
f8a323b
0f799a4
33e20d8
6d09543
b7b2644
018dea9
fd0d94a
f8c700c
c491fb3
97f5955
c6ac759
a79db74
603bf5c
8e63017
28b3f6f
3f9478b
e624a53
94ab662
148cc64
274adf1
defe1fe
050e225
4cad83e
169e6de
8af068d
3cbf2cf
42cb88e
925bd70
b421e05
5396e73
881ecb3
ef4b7ba
48920b5
2c0c0d6
42ee464
281e61f
973f44a
1de207c
7c029d4
a180566
af57b95
ddeedbf
7b1a2a9
62b279c
cadf33f
94c3d6e
be594bb
cc3c71b
b013e62
ccbbb35
24315cd
b45a633
4ac0511
4bd38a1
7b177bf
09b22da
8e8c59d
8ada4a0
d709b58
f99ece8
ad3fb6a
7d0d230
abd003b
fc116ad
35da738
fe25616
77b315e
7439f3d
2b73958
824d6d7
21a8df3
c4e4ad5
51f11b9
a4fa15c
4ec72aa
3a0a413
e9e5633
d7e0aaa
fb4b5cd
4380391
e372bb8
7457edf
0cd7ffd
4bf2cbb
425bbde
7c8c9ff
9215707
5f12751
945e42e
9f81653
87063b4
655fd1d
9679158
e1e729f
d35394e
a8301db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../codegen-server-test/model/constraints.smithy | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -148,6 +148,12 @@ sealed class RustType { | |
} | ||
} | ||
|
||
data class MaybeConstrained(override val member: RustType) : RustType(), Container { | ||
val runtimeType: RuntimeType = RuntimeType.MaybeConstrained() | ||
override val name = runtimeType.name!! | ||
override val namespace = runtimeType.namespace | ||
} | ||
|
||
data class Box(override val member: RustType) : RustType(), Container { | ||
override val name = "Box" | ||
override val namespace = "std::boxed" | ||
|
@@ -237,6 +243,7 @@ fun RustType.render(fullyQualified: Boolean = true): String { | |
is RustType.Box -> "${this.name}<${this.member.render(fullyQualified)}>" | ||
is RustType.Dyn -> "${this.name} ${this.member.render(fullyQualified)}" | ||
is RustType.Opaque -> this.name | ||
is RustType.MaybeConstrained -> "${this.name}<${this.member.render(fullyQualified)}>" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the first non-std type in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't characterize this class as such. This class contains Rust types that are mapped to from Smithy shapes (that is, a symbol provider inserts it into a |
||
} | ||
return "$namespace$base" | ||
} | ||
|
@@ -383,10 +390,11 @@ sealed class Attribute { | |
* indicates that more fields may be added in the future | ||
*/ | ||
val NonExhaustive = Custom("non_exhaustive") | ||
val AllowUnused = Custom("allow(unused)") | ||
val AllowUnusedMut = Custom("allow(unused_mut)") | ||
val AllowDeadCode = Custom("allow(dead_code)") | ||
val DocHidden = Custom("doc(hidden)") | ||
val DocInline = Custom("doc(inline)") | ||
val DocHidden = Custom("doc(hidden)") | ||
val AllowDeadCode = Custom("allow(dead_code)") | ||
} | ||
|
||
data class Derives(val derives: Set<RuntimeType>) : Attribute() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package software.amazon.smithy.rust.codegen.client.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.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.client.rustlang.RustReservedWords | ||
import software.amazon.smithy.rust.codegen.client.rustlang.RustType | ||
import software.amazon.smithy.rust.codegen.client.smithy.generators.builderSymbol | ||
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase | ||
|
||
// TODO Move this file to `core` or `server`. | ||
|
||
/** | ||
* 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, | ||
) : 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.builderSymbol(base) | ||
|
||
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() | ||
} | ||
else -> TODO("Constraint traits on other shapes not implemented yet: $shape") | ||
david-perez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,125 @@ | ||||||
/* | ||||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||||||
* SPDX-License-Identifier: Apache-2.0 | ||||||
*/ | ||||||
|
||||||
package software.amazon.smithy.rust.codegen.client.smithy | ||||||
|
||||||
import software.amazon.smithy.codegen.core.SymbolProvider | ||||||
import software.amazon.smithy.model.Model | ||||||
import software.amazon.smithy.model.neighbor.Walker | ||||||
import software.amazon.smithy.model.shapes.CollectionShape | ||||||
import software.amazon.smithy.model.shapes.MapShape | ||||||
import software.amazon.smithy.model.shapes.MemberShape | ||||||
import software.amazon.smithy.model.shapes.Shape | ||||||
import software.amazon.smithy.model.shapes.SimpleShape | ||||||
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.model.traits.EnumTrait | ||||||
import software.amazon.smithy.model.traits.LengthTrait | ||||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.CodegenTarget | ||||||
import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE | ||||||
import software.amazon.smithy.rust.codegen.core.util.hasTrait | ||||||
|
||||||
/** | ||||||
* This file contains utilities to work with constrained shapes. | ||||||
* | ||||||
* TODO Move this file to `core` or `server`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to see these TODOs addressed now that the core module is more fully fleshed out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only I'd also like to move |
||||||
*/ | ||||||
|
||||||
/** | ||||||
* We say a shape is _directly_ constrained if: | ||||||
* | ||||||
* - it has a constraint trait, or; | ||||||
* - in the case of it being an aggregate shape, one of its member shapes has a constraint trait. | ||||||
* | ||||||
* Note that an aggregate shape whose member shapes do not have constraint traits but that has a member whose target is | ||||||
* a constrained shape is _not_ directly constrained. | ||||||
* | ||||||
* At the moment only a subset of constraint traits are implemented on a subset of shapes; that's why we match against | ||||||
* a subset of shapes in each arm, and check for a subset of constraint traits attached to the shape in the arm's | ||||||
* (with these subsets being smaller than what [the spec] accounts for). | ||||||
david-perez marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
* | ||||||
* Note `uniqueItems` is deprecated, so we won't ever implement it. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC the deprecation was reversed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I've removed this sentence and I now link against the 2.0 spec. |
||||||
* | ||||||
* [the spec]: https://awslabs.github.io/smithy/1.0/spec/core/constraint-traits.html | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think Kotlin doc is able to reference links like this, but I could be probably wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thing to note: this is linking to the 1.0 docs. How do we navigate the existence of both 1.0 and 2.0 docs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say we lean towards linking the 2.0 docs, unless we want to call out a specific difference among the two IDL versions, in which case we cite both. Yes, Kotlin does not support Markdown formatting when creating Javadocs, but the codebase uses it extensively. |
||||||
*/ | ||||||
fun Shape.isDirectlyConstrained(symbolProvider: SymbolProvider) = when (this) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
is StructureShape -> { | ||||||
// TODO(https://github.com/awslabs/smithy-rs/issues/1302, https://github.com/awslabs/smithy/issues/1179): | ||||||
// The only reason why the functions in this file have | ||||||
// to take in a `SymbolProvider` is because non-`required` blob streaming members are interpreted as | ||||||
// `required`, so we can't use `member.isOptional` here. | ||||||
this.members().map { symbolProvider.toSymbol(it) }.any { !it.isOptional() } | ||||||
} | ||||||
is MapShape -> this.hasTrait<LengthTrait>() | ||||||
is StringShape -> this.hasTrait<EnumTrait>() || this.hasTrait<LengthTrait>() | ||||||
else -> false | ||||||
} | ||||||
|
||||||
fun Shape.hasPublicConstrainedWrapperTupleType(model: Model, publicConstrainedTypes: Boolean): Boolean = when (this) { | ||||||
is MapShape -> publicConstrainedTypes && this.hasTrait<LengthTrait>() | ||||||
is StringShape -> !this.hasTrait<EnumTrait>() && (publicConstrainedTypes && this.hasTrait<LengthTrait>()) | ||||||
is MemberShape -> model.expectShape(this.target).hasPublicConstrainedWrapperTupleType(model, publicConstrainedTypes) | ||||||
else -> false | ||||||
} | ||||||
|
||||||
fun Shape.wouldHaveConstrainedWrapperTupleTypeWerePublicConstrainedTypesEnabled(model: Model) = | ||||||
hasPublicConstrainedWrapperTupleType(model, true) | ||||||
david-perez marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* Helper function to determine whether a shape will map to a _public_ constrained wrapper tuple type. | ||||||
* | ||||||
* This function is used in core code generators, so it takes in a [CoreCodegenContext] that is downcast | ||||||
* to [ServerCodegenContext] when generating servers. | ||||||
*/ | ||||||
fun workingWithPublicConstrainedWrapperTupleType(shape: Shape, coreCodegenContext: CoreCodegenContext) = | ||||||
coreCodegenContext.target == CodegenTarget.SERVER && | ||||||
shape.hasPublicConstrainedWrapperTupleType( | ||||||
coreCodegenContext.model, | ||||||
(coreCodegenContext as ServerCodegenContext).settings.codegenConfig.publicConstrainedTypes, | ||||||
) | ||||||
david-perez marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* Returns whether a shape's type _name_ contains a non-public type when `publicConstrainedTypes` is `false`. | ||||||
* | ||||||
* For example, a `Vec<crate::model::LengthString>` contains a non-public type, because `crate::model::LengthString` | ||||||
* is `pub(crate)` when `publicConstrainedTypes` is `false` | ||||||
* | ||||||
* Note that a structure shape's type _definition_ may contain non-public types, but its _name_ is always public. | ||||||
* | ||||||
* Note how we short-circuit on `publicConstrainedTypes = true`, but we still require it to be passed in instead of laying | ||||||
* the responsibility on the caller, for API safety usage. | ||||||
*/ | ||||||
fun Shape.typeNameContainsNonPublicType( | ||||||
model: Model, | ||||||
symbolProvider: SymbolProvider, | ||||||
publicConstrainedTypes: Boolean, | ||||||
): Boolean = !publicConstrainedTypes && when (this) { | ||||||
is SimpleShape -> wouldHaveConstrainedWrapperTupleTypeWerePublicConstrainedTypesEnabled(model) | ||||||
is MemberShape -> model.expectShape(this.target).typeNameContainsNonPublicType(model, symbolProvider, publicConstrainedTypes) | ||||||
is CollectionShape -> this.canReachConstrainedShape(model, symbolProvider) | ||||||
is MapShape -> this.canReachConstrainedShape(model, symbolProvider) | ||||||
is StructureShape, is UnionShape -> false | ||||||
else -> UNREACHABLE("the above arms should be exhaustive, but we received shape: $this") | ||||||
} | ||||||
|
||||||
fun MemberShape.targetCanReachConstrainedShape(model: Model, symbolProvider: SymbolProvider): Boolean = | ||||||
model.expectShape(this.target).canReachConstrainedShape(model, symbolProvider) | ||||||
|
||||||
fun MemberShape.hasConstraintTraitOrTargetHasConstraintTrait(model: Model, symbolProvider: SymbolProvider) = | ||||||
this.isDirectlyConstrained(symbolProvider) || (model.expectShape(this.target).isDirectlyConstrained(symbolProvider)) | ||||||
|
||||||
fun Shape.isTransitivelyButNotDirectlyConstrained(model: Model, symbolProvider: SymbolProvider) = | ||||||
!this.isDirectlyConstrained(symbolProvider) && this.canReachConstrainedShape(model, symbolProvider) | ||||||
|
||||||
fun Shape.canReachConstrainedShape(model: Model, symbolProvider: SymbolProvider) = | ||||||
if (this is MemberShape) { | ||||||
// TODO(https://github.com/awslabs/smithy-rs/issues/1401) Constraint traits on member shapes are not implemented | ||||||
// yet. Also, note that a walker over a member shape can, perhaps counterintuitively, reach the _containing_ shape, | ||||||
// so we can't simply delegate to the `else` branch when we implement them. | ||||||
this.targetCanReachConstrainedShape(model, symbolProvider) | ||||||
} else { | ||||||
Walker(model).walkShapes(this).toSet().any { it.isDirectlyConstrained(symbolProvider) } | ||||||
} | ||||||
david-perez marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go in
codegen-core/common-test-models
if it's shared rather than using symlinks.