-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow third-party devs to write custom plugins for service builders (#…
…1736) * Allow custom layers to every operation Third-party developers can add custom functions to a service builder, through a `BuilderModifier`. These functionalities are applied to every operation of that service. An example of a builder modifier can be found in rust-runtime/aws-smithy-http-server/examples/pokemon-service/lib.rs Signed-off-by: Harry Barber <[email protected]> Signed-off-by: Daniele Ahmed <[email protected]> Co-authored-by: Harry Barber <[email protected]>
- Loading branch information
Showing
6 changed files
with
217 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/plugin.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
use aws_smithy_http_server::plugin::Plugin; | ||
|
||
/// A [`Service`](tower::Service) that adds a print log. | ||
#[derive(Clone, Debug)] | ||
pub struct PrintService<S> { | ||
inner: S, | ||
name: &'static str, | ||
} | ||
|
||
impl<R, S> tower::Service<R> for PrintService<S> | ||
where | ||
S: tower::Service<R>, | ||
{ | ||
type Response = S::Response; | ||
type Error = S::Error; | ||
type Future = S::Future; | ||
|
||
fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> { | ||
self.inner.poll_ready(cx) | ||
} | ||
|
||
fn call(&mut self, req: R) -> Self::Future { | ||
println!("Hi {}", self.name); | ||
self.inner.call(req) | ||
} | ||
} | ||
|
||
/// A [`Layer`](tower::Layer) which constructs the [`PrintService`]. | ||
#[derive(Debug)] | ||
pub struct PrintLayer { | ||
name: &'static str, | ||
} | ||
impl<S> tower::Layer<S> for PrintLayer { | ||
type Service = PrintService<S>; | ||
|
||
fn layer(&self, service: S) -> Self::Service { | ||
PrintService { | ||
inner: service, | ||
name: self.name, | ||
} | ||
} | ||
} | ||
|
||
/// A [`Plugin`]() for a service builder to add a [`PrintLayer`] over operations. | ||
#[derive(Debug)] | ||
pub struct PrintPlugin; | ||
impl<P, Op, S, L> Plugin<P, Op, S, L> for PrintPlugin | ||
where | ||
Op: aws_smithy_http_server::operation::OperationShape, | ||
{ | ||
type Service = S; | ||
type Layer = tower::layer::util::Stack<L, PrintLayer>; | ||
|
||
fn map( | ||
&self, | ||
input: aws_smithy_http_server::operation::Operation<S, L>, | ||
) -> aws_smithy_http_server::operation::Operation<Self::Service, Self::Layer> { | ||
input.layer(PrintLayer { name: Op::NAME }) | ||
} | ||
} | ||
|
||
/// An extension to service builders to add the `print()` function. | ||
pub trait PrintExt: aws_smithy_http_server::plugin::Pluggable<PrintPlugin> { | ||
/// Causes all operations to print the operation name when called. | ||
/// | ||
/// This works by applying the [`PrintPlugin`]. | ||
fn print(self) -> Self::Output | ||
where | ||
Self: Sized, | ||
{ | ||
self.apply(PrintPlugin) | ||
} | ||
} | ||
|
||
impl<Builder> PrintExt for Builder where Builder: aws_smithy_http_server::plugin::Pluggable<PrintPlugin> {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
use crate::operation::Operation; | ||
|
||
/// Provides a standard interface for applying [`Plugin`]s to a service builder. This is implemented automatically for all builders. | ||
/// As [`Plugin`]s modify the way in which [`Operation`]s are [`upgraded`](crate::operation::Upgradable) we can use [`Pluggable`] as a foundation | ||
/// to write extension traits for all builders. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// # struct PrintPlugin; | ||
/// # use aws_smithy_http_server::plugin::Pluggable; | ||
/// trait PrintExt: Pluggable<PrintPlugin> { | ||
/// fn print(self) -> Self::Output where Self: Sized { | ||
/// self.apply(PrintPlugin) | ||
/// } | ||
/// } | ||
/// impl<Builder> PrintExt for Builder where Builder: Pluggable<PrintPlugin> {} | ||
/// ``` | ||
pub trait Pluggable<NewPlugin> { | ||
type Output; | ||
|
||
/// A service builder applies this `plugin`. | ||
fn apply(self, plugin: NewPlugin) -> Self::Output; | ||
} | ||
|
||
/// Maps one [`Operation`] to another, | ||
/// parameterised by the protocol `P` and operation shape `Op` to allow for plugin behaviour to be specialised accordingly. | ||
/// | ||
/// This is passed to [`Pluggable::apply`] to modify the behaviour of the builder. | ||
pub trait Plugin<P, Op, S, L> { | ||
type Service; | ||
type Layer; | ||
|
||
/// Map an [`Operation`] to another. | ||
fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer>; | ||
} | ||
|
||
/// An [`Plugin`] that maps an `input` [`Operation`] to itself. | ||
pub struct IdentityPlugin; | ||
impl<P, Op, S, L> Plugin<P, Op, S, L> for IdentityPlugin { | ||
type Service = S; | ||
type Layer = L; | ||
|
||
fn map(&self, input: Operation<S, L>) -> Operation<S, L> { | ||
input | ||
} | ||
} | ||
|
||
/// A wrapper struct which composes an `Inner` and an `Outer` [`Plugin`]. | ||
pub struct PluginStack<Inner, Outer> { | ||
inner: Inner, | ||
outer: Outer, | ||
} | ||
|
||
impl<Inner, Outer> PluginStack<Inner, Outer> { | ||
/// Creates a new [`PluginStack`]. | ||
pub fn new(inner: Inner, outer: Outer) -> Self { | ||
PluginStack { inner, outer } | ||
} | ||
} | ||
|
||
impl<P, Op, S, L, Inner, Outer> Plugin<P, Op, S, L> for PluginStack<Inner, Outer> | ||
where | ||
Inner: Plugin<P, Op, S, L>, | ||
Outer: Plugin<P, Op, Inner::Service, Inner::Layer>, | ||
{ | ||
type Service = Outer::Service; | ||
type Layer = Outer::Layer; | ||
|
||
fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer> { | ||
let inner = self.inner.map(input); | ||
self.outer.map(inner) | ||
} | ||
} |