Skip to content

Commit

Permalink
Add PluginExt extension trait and FilterByOperationName plugin (#1837)
Browse files Browse the repository at this point in the history
* Make Operation fields public

* Split into modules and add FilterByOperationName
  • Loading branch information
hlbarber authored Oct 11, 2022
1 parent 046edd5 commit d879da1
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 88 deletions.
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.
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;

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)
}
}

0 comments on commit d879da1

Please sign in to comment.