-
Notifications
You must be signed in to change notification settings - Fork 193
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
Make modules in codegen-core
configurable
#2336
Changes from all commits
bf531c3
9515458
ede5f87
473a0bc
24315bc
f421475
cc0dde9
4fe8994
74204fa
ad51645
94a9784
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 |
---|---|---|
|
@@ -24,7 +24,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.Cli | |
import software.amazon.smithy.rust.codegen.client.smithy.protocols.ClientProtocolLoader | ||
import software.amazon.smithy.rust.codegen.client.smithy.transformers.AddErrorMessage | ||
import software.amazon.smithy.rust.codegen.client.smithy.transformers.RemoveEventStreamOperations | ||
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule | ||
import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker | ||
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate | ||
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider | ||
|
@@ -33,15 +32,12 @@ import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerat | |
import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator | ||
import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator | ||
import software.amazon.smithy.rust.codegen.core.smithy.generators.error.OperationErrorGenerator | ||
import software.amazon.smithy.rust.codegen.core.smithy.generators.error.eventStreamErrorSymbol | ||
import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock | ||
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory | ||
import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait | ||
import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamNormalizer | ||
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer | ||
import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer | ||
import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamErrors | ||
import software.amazon.smithy.rust.codegen.core.smithy.transformers.operationErrors | ||
import software.amazon.smithy.rust.codegen.core.util.CommandFailed | ||
import software.amazon.smithy.rust.codegen.core.util.hasTrait | ||
import software.amazon.smithy.rust.codegen.core.util.isEventStream | ||
|
@@ -56,7 +52,6 @@ class ClientCodegenVisitor( | |
context: PluginContext, | ||
private val codegenDecorator: ClientCodegenDecorator, | ||
) : ShapeVisitor.Default<Unit>() { | ||
|
||
private val logger = Logger.getLogger(javaClass.name) | ||
private val settings = ClientRustSettings.from(context.model, context.settings) | ||
|
||
|
@@ -69,12 +64,12 @@ class ClientCodegenVisitor( | |
private val protocolGenerator: ClientProtocolGenerator | ||
|
||
init { | ||
val symbolVisitorConfig = | ||
SymbolVisitorConfig( | ||
runtimeConfig = settings.runtimeConfig, | ||
renameExceptions = settings.codegenConfig.renameExceptions, | ||
nullabilityCheckMode = NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1, | ||
) | ||
val symbolVisitorConfig = SymbolVisitorConfig( | ||
runtimeConfig = settings.runtimeConfig, | ||
renameExceptions = settings.codegenConfig.renameExceptions, | ||
nullabilityCheckMode = NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1, | ||
moduleProvider = ClientModuleProvider, | ||
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. The module provider is a cool idea |
||
) | ||
val baseModel = baselineTransform(context.model) | ||
val untransformedService = settings.getService(baseModel) | ||
val (protocol, generator) = ClientProtocolLoader( | ||
|
@@ -224,13 +219,8 @@ class ClientCodegenVisitor( | |
UnionGenerator(model, symbolProvider, this, shape, renderUnknownVariant = true).render() | ||
} | ||
if (shape.isEventStream()) { | ||
rustCrate.withModule(RustModule.Error) { | ||
val symbol = symbolProvider.toSymbol(shape) | ||
val errors = shape.eventStreamErrors() | ||
.map { model.expectShape(it.asMemberShape().get().target, StructureShape::class.java) } | ||
val errorSymbol = shape.eventStreamErrorSymbol(symbolProvider) | ||
OperationErrorGenerator(model, symbolProvider, symbol, errors) | ||
.renderErrors(this, errorSymbol, symbol) | ||
rustCrate.withModule(ClientRustModule.Error) { | ||
OperationErrorGenerator(model, symbolProvider, shape).render(this) | ||
} | ||
} | ||
} | ||
|
@@ -239,14 +229,8 @@ class ClientCodegenVisitor( | |
* Generate errors for operation shapes | ||
*/ | ||
override fun operationShape(shape: OperationShape) { | ||
rustCrate.withModule(RustModule.Error) { | ||
val operationSymbol = symbolProvider.toSymbol(shape) | ||
OperationErrorGenerator( | ||
model, | ||
symbolProvider, | ||
operationSymbol, | ||
shape.operationErrors(model).map { it.asStructureShape().get() }, | ||
).render(this) | ||
rustCrate.withModule(ClientRustModule.Error) { | ||
OperationErrorGenerator(model, symbolProvider, shape).render(this) | ||
} | ||
Comment on lines
+232
to
234
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. Based on my mental model comment, I'm wondering if this generator functionality provided by rustCrate.withModule(ClientRustModule.Error) {
operation.structDeclaration().render(this)
operation.structImpl().render(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. I think we should keep generation separate from the modeling still, in case structs need to be generated completely differently for other use cases. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* 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.model.shapes.OperationShape | ||
import software.amazon.smithy.model.shapes.Shape | ||
import software.amazon.smithy.model.shapes.StructureShape | ||
import software.amazon.smithy.model.shapes.UnionShape | ||
import software.amazon.smithy.model.traits.ErrorTrait | ||
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule | ||
import software.amazon.smithy.rust.codegen.core.smithy.ModuleProvider | ||
import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait | ||
import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticOutputTrait | ||
import software.amazon.smithy.rust.codegen.core.util.hasTrait | ||
|
||
/** | ||
* Modules for code generated client crates. | ||
*/ | ||
object ClientRustModule { | ||
/** crate::client */ | ||
val client = Client.self | ||
object Client { | ||
/** crate::client */ | ||
val self = RustModule.public("client", "Client and fluent builders for calling the service.") | ||
|
||
/** crate::client::customize */ | ||
val customize = RustModule.public("customize", "Operation customization and supporting types", parent = self) | ||
} | ||
|
||
val Config = RustModule.public("config", documentation = "Configuration for the service.") | ||
val Error = RustModule.public("error", documentation = "All error types that operations can return. Documentation on these types is copied from the model.") | ||
val Operation = RustModule.public("operation", documentation = "All operations that this crate can perform.") | ||
val Model = RustModule.public("model", documentation = "Data structures used by operation inputs/outputs. Documentation on these types is copied from the model.") | ||
val Input = RustModule.public("input", documentation = "Input structures for operations. Documentation on these types is copied from the model.") | ||
val Output = RustModule.public("output", documentation = "Output structures for operations. Documentation on these types is copied from the model.") | ||
val Types = RustModule.public("types", documentation = "Data primitives referenced by other data types.") | ||
} | ||
|
||
object ClientModuleProvider : ModuleProvider { | ||
override fun moduleForShape(shape: Shape): RustModule.LeafModule = when (shape) { | ||
is OperationShape -> ClientRustModule.Operation | ||
is StructureShape -> when { | ||
shape.hasTrait<ErrorTrait>() -> ClientRustModule.Error | ||
shape.hasTrait<SyntheticInputTrait>() -> ClientRustModule.Input | ||
shape.hasTrait<SyntheticOutputTrait>() -> ClientRustModule.Output | ||
else -> ClientRustModule.Model | ||
} | ||
else -> ClientRustModule.Model | ||
} | ||
Comment on lines
+43
to
+52
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 love this declarative approach! It's so clear and gives you a great idea of the resulting Rust crate's structure |
||
|
||
override fun moduleForOperationError(operation: OperationShape): RustModule.LeafModule = | ||
ClientRustModule.Error | ||
|
||
override fun moduleForEventStreamError(eventStream: UnionShape): RustModule.LeafModule = | ||
ClientRustModule.Error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.customize | |
|
||
import software.amazon.smithy.model.shapes.OperationShape | ||
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext | ||
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule | ||
import software.amazon.smithy.rust.codegen.client.smithy.customizations.EndpointPrefixGenerator | ||
import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpChecksumRequiredGenerator | ||
import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpVersionListCustomization | ||
|
@@ -65,6 +66,8 @@ class RequiredCustomizations : ClientCodegenDecorator { | |
// Re-export resiliency types | ||
ResiliencyReExportCustomization(codegenContext.runtimeConfig).extras(rustCrate) | ||
|
||
pubUseSmithyTypes(codegenContext.runtimeConfig, codegenContext.model, rustCrate) | ||
rustCrate.withModule(ClientRustModule.Types) { | ||
pubUseSmithyTypes(codegenContext.runtimeConfig, codegenContext.model)(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. I'd prefer if we chose one convention for rendering things outside of a rust writer template: I like seeing It only now occurs to me that we're calling a function to render things here so I guess what I'm really saying is: "should we render from free functions, or only by calling methods?" Perhaps this is a nitpick. 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've been wanting to refactor |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
|
||
package software.amazon.smithy.rust.codegen.client.smithy.generators.client | ||
|
||
import software.amazon.smithy.codegen.core.Symbol | ||
import software.amazon.smithy.model.shapes.OperationShape | ||
import software.amazon.smithy.model.shapes.ServiceShape | ||
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext | ||
|
@@ -65,7 +66,7 @@ sealed class FluentClientSection(name: String) : Section(name) { | |
/** Write custom code into an operation fluent builder's impl block */ | ||
data class FluentBuilderImpl( | ||
val operationShape: OperationShape, | ||
val operationErrorType: RuntimeType, | ||
val operationErrorType: Symbol, | ||
Comment on lines
-68
to
+69
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 prefer using a type we control in these cases. What if we updated the 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. Problem is, we don't have a I don't think we really need a |
||
) : FluentClientSection("FluentBuilderImpl") | ||
|
||
/** Write custom code into the docs */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,11 +34,7 @@ class ResponseBindingGenerator( | |
|
||
fun generateDeserializePayloadFn( | ||
binding: HttpBindingDescriptor, | ||
errorT: RuntimeType, | ||
errorSymbol: Symbol, | ||
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 see that we sometimes suffix these param names with Alternatively, we could discourage the use of suffixes that note types. 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. Yeah, I agree. We should use |
||
payloadParser: RustWriter.(String) -> Unit, | ||
): RuntimeType = httpBindingGenerator.generateDeserializePayloadFn( | ||
binding, | ||
errorT, | ||
payloadParser, | ||
) | ||
): RuntimeType = httpBindingGenerator.generateDeserializePayloadFn(binding, errorSymbol, payloadParser) | ||
} |
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.
Thinking about our symbol provider, I dislike this relationship of having to pass it around and then passing things into it.
When I think about operations, inputs, outputs, etc. I see them as the chief concepts to organize around and I'm thinking my mental model doesn't fit well with smithy's model of codegen.
In my mind, an operation is a core concept and has several relationships and representations.
Relationships include:
and representations include:
When I see code like this were we transform the operation by passing it into something else, I realize that my preference would be to decide the relation I want, and then the representation of that related concept i.e.
My question then is: Should I be changing my mental model or should we be changing codegen?
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.
The problem arises that Smithy doesn't really have a concept of an operation error. The operation has errors, but the fact that there is an operation error type is an implementation detail of smithy-rs and the SDK. So that
operation.error()
would need to return something non-Smithy if we were to take that approach. Other than that, things liketoSymbol()
will need to take a symbol provider as an argument, or alternatively we delve into some questionable global variable practices. There are certain Smithy patterns that will get more strict over time rather than less strict.Something we can consider is making an abstraction layer on top of Smithy, but it would be expensive to refactor to that, and it would make Smithy upgrades more expensive in the future.
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.
I spoke with John about this outside of GitHub. Including some notes here for posterity:
My assumptions
Smithy is a modelling language for defining a service that responds to requests.
Notes from our discussion
RuntimeType
shouldn't really exist. We should instead merge the good parts of it into theSymbol
class.smithy-rs
is not a general-purpose library for writing Kotlin programs that generate Rust. Rather, it's a tool for converting smithy models in Rust code that's usable by our runtime crates.