Skip to content
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

Add PluginExt extension trait and FilterByOperationName plugin #1837

Merged
merged 6 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions rust-runtime/aws-smithy-http-server/src/operation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,11 @@ pub use upgrade::*;
///
/// The `L` is held and applied lazily during [`Upgradable::upgrade`].
pub struct Operation<S, L = Identity> {
inner: S,
layer: L,
/// The inner [`Service`](tower::Service) representing the logic of the operation.
Copy link
Contributor Author

@hlbarber hlbarber Oct 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These have become public in order to allow Plugin developers access to them.

Is this too severe? Maybe we should document that the average customer should use the from_handler/from_service methods?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want customers to be able to write their own plugins, along the same lines of what we are doing here with the filtering, I don't see many other options.

pub inner: S,
/// The [`Layer`](tower::Layer) applied to the HTTP [`Service`](tower::Service) after `S` has been wrapped in
/// [`Upgrade`].
pub layer: L,
}

impl<S, L> Operation<S, L> {
Expand Down
86 changes: 0 additions & 86 deletions rust-runtime/aws-smithy-http-server/src/plugin.rs

This file was deleted.

50 changes: 50 additions & 0 deletions rust-runtime/aws-smithy-http-server/src/plugin/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use tower::util::Either;

use crate::operation::{Operation, OperationShape};

use super::Plugin;

/// A [`Plugin`] used to filter [`Plugin::map`] application using a predicate over the [`OperationShape::NAME`].
///
/// See [`PluginExt::filter_by_operation_name`](super::PluginExt::filter_by_operation_name) for more information.
pub struct FilterByOperationName<Inner, F> {
inner: Inner,
predicate: F,
}

impl<Inner, F> FilterByOperationName<Inner, F> {
/// Creates a new [`FilterByOperationName`].
pub(crate) fn new(inner: Inner, predicate: F) -> Self {
Self { inner, predicate }
}
}

impl<P, Op, S, L, Inner, F> Plugin<P, Op, S, L> for FilterByOperationName<Inner, F>
where
F: Fn(&str) -> bool,
Inner: Plugin<P, Op, S, L>,
Op: OperationShape,
{
type Service = Either<Inner::Service, S>;
type Layer = Either<Inner::Layer, L>;

fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer> {
if (self.predicate)(Op::NAME) {
let Operation { inner, layer } = self.inner.map(input);
Operation {
inner: Either::A(inner),
layer: Either::A(layer),
}
} else {
Operation {
inner: Either::B(input.inner),
layer: Either::B(input.layer),
}
}
}
}
20 changes: 20 additions & 0 deletions rust-runtime/aws-smithy-http-server/src/plugin/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use crate::operation::Operation;

use super::Plugin;

/// A [`Plugin`] that maps an `input` [`Operation`] to itself.
pub struct IdentityPlugin;
LukeMathWalker marked this conversation as resolved.
Show resolved Hide resolved

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
}
}
93 changes: 93 additions & 0 deletions rust-runtime/aws-smithy-http-server/src/plugin/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

mod filter;
mod identity;
mod stack;

use crate::operation::Operation;

pub use filter::*;
pub use identity::*;
pub use stack::*;

/// 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 which are implemented for all service 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;

/// Applies a [`Plugin`] to the service builder.
fn apply(self, plugin: NewPlugin) -> Self::Output;
}

/// A mapping from one [`Operation`] to another. Used to modify the behavior of
/// [`Upgradable`](crate::operation::Upgradable) and therefore the resulting service builder,
///
/// The generics `Protocol` and `Op` allow the behavior to be parameterized.
///
/// Every service builder enjoys [`Pluggable`] and therefore can be provided with a [`Plugin`] using
/// [`Pluggable::apply`].
pub trait Plugin<Protocol, Op, S, L> {
type Service;
type Layer;

/// Maps an [`Operation`] to another.
fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer>;
}

/// An extension trait for [`Plugin`].
pub trait PluginExt<P, Op, S, L>: Plugin<P, Op, S, L> {
/// Stacks another [`Plugin`], running them sequentially.
fn stack<Other>(self, other: Other) -> PluginStack<Self, Other>
where
Self: Sized,
{
PluginStack::new(self, other)
}

/// Filters the application of the [`Plugin`] using a predicate over the
/// [`OperationShape::NAME`](crate::operation::OperationShape).
///
/// # Example
///
/// ```rust
/// # use aws_smithy_http_server::{plugin::{Plugin, PluginExt}, operation::{Operation, OperationShape}};
/// # struct Pl;
/// # struct CheckHealth;
/// # impl OperationShape for CheckHealth { const NAME: &'static str = ""; type Input = (); type Output = (); type Error = (); }
/// # impl Plugin<(), CheckHealth, (), ()> for Pl { type Service = (); type Layer = (); fn map(&self, input: Operation<(), ()>) -> Operation<(), ()> { input }}
/// # let plugin = Pl;
/// # let operation = Operation { inner: (), layer: () };
/// // Prevents `plugin` from being applied to the `CheckHealth` operation.
/// let filtered_plugin = plugin.filter_by_operation_name(|name| name != CheckHealth::NAME);
/// let new_operation = filtered_plugin.map(operation);
/// ```
fn filter_by_operation_name<F>(self, predicate: F) -> FilterByOperationName<Self, F>
where
Self: Sized,
F: Fn(&str) -> bool,
{
FilterByOperationName::new(self, predicate)
}
}

impl<Pl, P, Op, S, L> PluginExt<P, Op, S, L> for Pl where Pl: Plugin<P, Op, S, L> {}
37 changes: 37 additions & 0 deletions rust-runtime/aws-smithy-http-server/src/plugin/stack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use crate::operation::Operation;

use super::Plugin;

/// A wrapper struct which composes an `Inner` and an `Outer` [`Plugin`].
///
/// The `Inner::map` is run _then_ the `Outer::map`.
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)
}
}