-
Notifications
You must be signed in to change notification settings - Fork 189
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
Allow server decorators to inject methods on config #3111
Conversation
PR #3095 added a code-generated `${serviceName}Config` object on which users can register layers and plugins. For example: ```rust let config = PokemonServiceConfig::builder() .layer(layers) .http_plugin(authn_plugin) .model_plugin(authz_plugin) .build(); ``` This PR makes it so that server decorators can inject methods on this config builder object. These methods can apply arbitrary layers, HTTP plugins, and/or model plugins. Moreover, the decorator can configure whether invoking such method is required or not. For example, a decorator can inject an `aws_auth` method that configures some plugins using its input arguments. Missing invocation of this method will result in the config failing to build: ```rust let _: SimpleServiceConfig< // No layers have been applied. tower::layer::util::Identity, // One HTTP plugin has been applied. PluginStack<IdentityPlugin, IdentityPlugin>, // One model plugin has been applied. PluginStack<IdentityPlugin, IdentityPlugin>, > = SimpleServiceConfig::builder() // This method has been injected in the config builder! .aws_auth("a".repeat(69).to_owned(), 69) // The method will apply one HTTP plugin and one model plugin, // configuring them with the input arguments. Configuration can be // declared to be fallible, in which case we get a `Result` we unwrap // here. .expect("failed to configure aws_auth") .build() // Since `aws_auth` has been marked as required, if the user misses // invoking it, this would panic here. .unwrap(); ```
A new generated diff is ready to view.
A new doc preview is ready to view. |
A new generated diff is ready to view.
A new doc preview is ready to view. |
A new generated diff is ready to view.
A new doc preview is ready to view. |
...n/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRootGenerator.kt
Show resolved
Hide resolved
...otlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt
Show resolved
Hide resolved
...otlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt
Show resolved
Hide resolved
...otlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt
Show resolved
Hide resolved
...otlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt
Show resolved
Hide resolved
...otlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt
Show resolved
Hide resolved
...otlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt
Show resolved
Hide resolved
) | ||
} | ||
|
||
private fun isBuilderFallible() = configMethods.isBuilderFallible() |
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.
nit: Make this a private val isBuilderFallible = configMethods.isBuilderFallible()
?
rust( | ||
""" | ||
if !self.${it.requiredBuilderFlagName()} { | ||
return Err(${serviceName}ConfigError::${it.requiredErrorVariant()}); |
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.
Should we use #{Err}
instead of using Err
directly?
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.
We should!
writable { | ||
rust( | ||
""" | ||
##[error("service is not fully configured; invoke `${it.requiredBuilderFlagName()}` on the config builder")] |
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.
Wouldn't ${it.requiredBuilderFlagName()}
output the name of the flag itself, rather than the method name that the user is expected to call on Config::builder
?
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.
Good catch!
) | ||
} | ||
} | ||
if (isBuilderFallible()) { |
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.
nit: keep this before val variants
on line 292 as we don't need to compute that if builder
is not fallible.
private fun injectedMethods() = configMethods.map { | ||
writable { | ||
val paramBindings = it.params.map { binding -> | ||
writable { rustTemplate("${binding.name}: #{BindingTy},", "BindingTy" to binding.ty) } |
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.
Will this automatically include any dependencies required by BindingTy
?
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.
Yes.
// - "T" is the generic type variable name used in the enclosing impl block. | ||
fun List<Binding>.stackReturnType(genericTypeVarName: String, stackType: RuntimeType): Writable = | ||
this.fold(writable { rust(genericTypeVarName) }) { acc, next -> | ||
writable { |
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.
nit: So, if I return a listOf(A, B, C)
, does it get transformed into Stack<C, Stack<B, Stack<A, T>>>
, where T
is genericTypeVarName
? If yes, how about we use this.foldRight
so that it becomes Stack<A, Stack<B, Stack<C, T>>>
?
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 inner middleware is the left type parameter of the stack: https://docs.rs/tower/latest/tower/layer/util/struct.Stack.html
So we do want Stack<C, Stack<B, Stack<A, T>>>
. A will be executed first, then B, then C.
We should document this though.
A new generated diff is ready to view.
A new doc preview is ready to view. |
A new generated diff is ready to view.
A new doc preview is ready to view. |
PR #3095 added a code-generated `${serviceName}Config` object on which users can register layers and plugins. For example: ```rust let config = PokemonServiceConfig::builder() .layer(layers) .http_plugin(authn_plugin) .model_plugin(authz_plugin) .build(); ``` This PR makes it so that server decorators can inject methods on this config builder object. These methods can apply arbitrary layers, HTTP plugins, and/or model plugins. Moreover, the decorator can configure whether invoking such method is required or not. For example, a decorator can inject an `aws_auth` method that configures some plugins using its input arguments. Missing invocation of this method will result in the config failing to build: ```rust let _: SimpleServiceConfig< // No layers have been applied. tower::layer::util::Identity, // One HTTP plugin has been applied. PluginStack<IdentityPlugin, IdentityPlugin>, // One model plugin has been applied. PluginStack<IdentityPlugin, IdentityPlugin>, > = SimpleServiceConfig::builder() // This method has been injected in the config builder! .aws_auth("a".repeat(69).to_owned(), 69) // The method will apply one HTTP plugin and one model plugin, // configuring them with the input arguments. Configuration can be // declared to be fallible, in which case we get a `Result` we unwrap // here. .expect("failed to configure aws_auth") .build() // Since `aws_auth` has been marked as required, if the user misses // invoking it, this would panic here. .unwrap(); ``` ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
…ject This is a follow-up to #3111. Currently, the injected methods are limited to taking in concrete types. This PR allows for these methods to take in generic type parameters as well. ```rust impl<L, H, M> SimpleServiceConfigBuilder<L, H, M> { pub fn aws_auth<C>(config: C) { ... } } ```
…ject (#3274) This is a follow-up to #3111. Currently, the injected methods are limited to taking in concrete types. This PR allows for these methods to take in generic type parameters as well. ```rust impl<L, H, M> SimpleServiceConfigBuilder<L, H, M> { pub fn aws_auth<C>(config: C) { ... } } ``` ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
PR #3095 added a code-generated
${serviceName}Config
object on whichusers can register layers and plugins. For example:
This PR makes it so that server decorators can inject methods on this
config builder object. These methods can apply arbitrary layers, HTTP
plugins, and/or model plugins. Moreover, the decorator can configure
whether invoking such method is required or not.
For example, a decorator can inject an
aws_auth
method that configuressome plugins using its input arguments. Missing invocation of this method
will result in the config failing to build:
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.