You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
So far, servers have tacitly worked with the notion that plugins come in
two flavors: "HTTP plugins" and "model plugins":
- A HTTP plugin acts on the HTTP request before it is deserialized, and
acts on the HTTP response after it is serialized.
- A model plugin acts on the modeled operation input after it is
deserialized, and acts on the modeled operation output or the modeled
operation error before it is serialized.
However, this notion was never reified in the type system. Thus, users
who pass in a model plugin where a HTTP plugin is expected or
viceversa in several APIs:
```rust
let pipeline = PluginPipeline::new().push(http_plugin).push(model_plugin);
let app = SimpleService::builder_with_plugins(http_plugins, IdentityPlugin)
.post_operation(handler)
/* ... */
.build()
.unwrap();
```
would get the typical Tower service compilation errors, which can get
very confusing:
```
error[E0631]: type mismatch in function arguments
--> simple/rust-server-codegen/src/main.rs:47:6
|
15 | fn new_svc<S, Ext>(inner: S) -> model_plugin::PostOperationService<S, Ext> {
| -------------------------------------------------------------------------- found signature defined here
...
47 | .post_operation(post_operation)
| ^^^^^^^^^^^^^^ expected due to this
|
= note: expected function signature `fn(Upgrade<RestJson1, (PostOperationInput, _), PostOperationService<aws_smithy_http_server::operation::IntoService<simple::operation_shape::PostOperation, _>, _>>) -> _`
found function signature `fn(aws_smithy_http_server::operation::IntoService<simple::operation_shape::PostOperation, _>) -> _`
= note: required for `LayerFn<fn(aws_smithy_http_server::operation::IntoService<..., ...>) -> ... {new_svc::<..., ...>}>` to implement `Layer<Upgrade<RestJson1, (PostOperationInput, _), PostOperationService<aws_smithy_http_server::operation::IntoService<simple::operation_shape::PostOperation, _>, _>>>`
= note: the full type name has been written to '/local/home/davidpz/workplace/smithy-ws/src/SmithyRsSource/target/debug/deps/simple-6577f9f79749ceb9.long-type-4938700695428041031.txt'
```
This commit introduces the `HttpPlugin` and `ModelPlugin` marker traits,
allowing plugins to be marked as an HTTP plugin, a model plugin, or
both. It also removes the primary way of concatenating plugins,
`PluginPipeline`, in favor of `HttpPlugins` and `ModelPlugins`, which
eagerly check that whenever a plugin is `push`ed, it is of the expected
type. The generated service type in the server SDKs'
`builder_with_plugins` constructor now takes an `HttpPlugin` as its
first parameter, and a `ModelPlugin` as its second parameter.
I think this change perhaps goes counter to the generally accepted
wisdom that trait bounds in Rust should be enforced "at the latest
possible moment", that is, only when the behavior encoded in the trait
implementation is relied upon in the code (citation needed).
However, the result is that exposing the concepts of HTTP plugin and
model plugin in the type system makes for code that is easier to reason
about, and produces more helpful compiler error messages.
Documentation about the plugin system has been expanded, particularly on
how to implement model plugins.
## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the
smithy-rs codegen or runtime crates
----
_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
Copy file name to clipboardExpand all lines: CHANGELOG.next.toml
+38-2
Original file line number
Diff line number
Diff line change
@@ -210,6 +210,7 @@ message = """The middleware system has been reworked as we push for a unified, s
210
210
211
211
- A `ServiceShape` trait has been added.
212
212
- The `Plugin` trait has been simplified.
213
+
- The `HttpMarker` and `ModelMarker` marker traits have been added to better distinguish when plugins run and what they have access to.
213
214
- The `Operation` structure has been removed.
214
215
- A `Scoped` `Plugin` has been added.
215
216
@@ -371,6 +372,8 @@ where
371
372
PrintService { inner, name: Op::ID.name() }
372
373
}
373
374
}
375
+
376
+
impl HttpMarker for PrintPlugin { }
374
377
```
375
378
376
379
Alternatively, using the new `ServiceShape`, implemented on `Ser`:
@@ -397,6 +400,11 @@ let app = PokemonService::builder_with_plugins(/* HTTP plugins */, /* model plug
397
400
.unwrap();
398
401
```
399
402
403
+
To better distinguish when a plugin runs and what it has access to, `Plugin`s now have to additionally implement the `HttpMarker` marker trait, the `ModelMarker` marker trait, or both:
404
+
405
+
- A HTTP plugin acts on the HTTP request before it is deserialized, and acts on the HTTP response after it is serialized.
406
+
- A model plugin acts on the modeled operation input after it is deserialized, and acts on the modeled operation output or the modeled operation error before it is serialized.
407
+
400
408
The motivation behind this change is to simplify the job of middleware authors, separate concerns, accomodate common cases better, and to improve composition internally.
401
409
402
410
Because `Plugin` is now closer to `tower::Layer` we have two canonical converters:
@@ -413,6 +421,34 @@ let plugin = /* some plugin */;
413
421
let layer = LayerPlugin::new::<SomeProtocol, SomeOperation>(plugin);
414
422
```
415
423
424
+
## Removal of `PluginPipeline`
425
+
426
+
Since plugins now come in two flavors (those marked with `HttpMarker` and those marked with `ModelMarker`) that shouldn't be mixed in a collection of plugins, the primary way of concatenating plugins, `PluginPipeline` has been removed in favor of the `HttpPlugins` and `ModelPlugins` types, which eagerly check that whenever a plugin is pushed, it is of the expected type.
427
+
428
+
This worked before, but you wouldn't be able to do apply this collection of plugins anywhere; if you tried to, the compilation error messages would not be very helpful:
429
+
430
+
```rust
431
+
use aws_smithy_http_server::plugin::PluginPipeline;
432
+
433
+
let pipeline = PluginPipeline::new().push(http_plugin).push(model_plugin);
434
+
```
435
+
436
+
Now collections of plugins must contain plugins of the same flavor:
437
+
438
+
```rust
439
+
use aws_smithy_http_server::plugin::{HttpPlugins, ModelPlugins};
440
+
441
+
let http_plugins = HttpPlugins::new()
442
+
.push(http_plugin)
443
+
// .push(model_plugin) // This fails to compile with a helpful error message.
444
+
.push(&http_and_model_plugin);
445
+
let model_plugins = ModelPlugins::new()
446
+
.push(model_plugin)
447
+
.push(&http_and_model_plugin);
448
+
```
449
+
450
+
In the above example, `&http_and_model_plugin` implements both `HttpMarker` and `ModelMarker`, so we can add it to both collections.
451
+
416
452
## Removal of `Operation`
417
453
418
454
The `aws_smithy_http_server::operation::Operation` structure has now been removed. Previously, there existed a `{operation_name}_operation` setter on the service builder, which accepted an `Operation`. This allowed users to
@@ -495,8 +531,8 @@ let scoped_plugin = Scoped::new::<SomeScope>(plugin);
Copy file name to clipboardExpand all lines: codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRootGenerator.kt
+5-4
Original file line number
Diff line number
Diff line change
@@ -106,21 +106,22 @@ open class ServerRootGenerator(
106
106
//! #### Plugins
107
107
//!
108
108
//! The [`$serviceName::builder_with_plugins`] method, returning [`$builderName`],
109
-
//! accepts a [`Plugin`](aws_smithy_http_server::plugin::Plugin).
109
+
//! accepts a plugin marked with [`HttpMarker`](aws_smithy_http_server::plugin::HttpMarker) and a
110
+
//! plugin marked with [`ModelMarker`](aws_smithy_http_server::plugin::ModelMarker).
110
111
//! Plugins allow you to build middleware which is aware of the operation it is being applied to.
111
112
//!
112
113
//! ```rust
113
114
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin;
114
115
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as LoggingPlugin;
115
116
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as MetricsPlugin;
116
117
//! ## use #{Hyper}::Body;
117
-
//! use #{SmithyHttpServer}::plugin::PluginPipeline;
118
+
//! use #{SmithyHttpServer}::plugin::HttpPlugins;
118
119
//! use $crateName::{$serviceName, $builderName};
119
120
//!
120
-
//! let plugins = PluginPipeline::new()
121
+
//! let http_plugins = HttpPlugins::new()
121
122
//! .push(LoggingPlugin)
122
123
//! .push(MetricsPlugin);
123
-
//! let builder: $builderName<Body, _, _> = $serviceName::builder_with_plugins(plugins, IdentityPlugin);
124
+
//! let builder: $builderName<Body, _, _> = $serviceName::builder_with_plugins(http_plugins, IdentityPlugin);
124
125
//! ```
125
126
//!
126
127
//! Check out [`#{SmithyHttpServer}::plugin`] to learn more about plugins.
Copy file name to clipboardExpand all lines: codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRuntimeTypesReExportsGenerator.kt
Copy file name to clipboardExpand all lines: codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt
+18-17
Original file line number
Diff line number
Diff line change
@@ -136,31 +136,31 @@ class ServerServiceGenerator(
Copy file name to clipboardExpand all lines: design/src/server/anatomy.md
+16-16
Original file line number
Diff line number
Diff line change
@@ -466,40 +466,40 @@ stateDiagram-v2
466
466
The service builder API requires plugins to be specified upfront - they must be passed as an argument to `builder_with_plugins` and cannot be modified afterwards.
467
467
468
468
You might find yourself wanting to apply _multiple_ plugins to your service.
469
-
This can be accommodated via [`PluginPipeline`].
469
+
This can be accommodated via [`HttpPlugins`] and [`ModelPlugins`].
The plugins' runtime logic is executed in registration order.
481
481
In the example above, `LoggingPlugin` would run first, while `MetricsPlugin` is executed last.
482
482
483
-
If you are vending a plugin, you can leverage `PluginPipeline` as an extension point: you can add custom methods to it using an extension trait.
483
+
If you are vending a plugin, you can leverage `HttpPlugins` or `ModelPlugins` as an extension point: you can add custom methods to it using an extension trait.
0 commit comments