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

OperationShape::NAME as ShapeId #2678

Merged
merged 18 commits into from
May 11, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// This program is exported as a binary named `pokemon-service`.
use std::{net::SocketAddr, sync::Arc};

use aws_smithy_http_server::{extension::OperationExtensionExt, plugin::PluginPipeline, AddExtensionLayer};
use aws_smithy_http_server::{extension::OperationIdExt, plugin::PluginPipeline, AddExtensionLayer};
use clap::Parser;
use pokemon_service::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, get_storage,
Expand All @@ -32,8 +32,8 @@ pub async fn main() {
let plugins = PluginPipeline::new()
// Apply the `PrintPlugin` defined in `plugin.rs`
.print()
// Apply the `OperationExtensionPlugin` defined in `aws_smithy_http_server::extension`. This allows other
// plugins or tests to access a `aws_smithy_http_server::extension::OperationExtension` from
// Apply the `OperationIdPlugin` defined in `aws_smithy_http_server::extension`. This allows other
// plugins or tests to access a `aws_smithy_http_server::extension::OperationId` from
// `Response::extensions`, or infer routing failure when it's missing.
.insert_operation_extension();
let app = PokemonService::builder_with_plugins(plugins)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ use aws_smithy_http_server::{
use tower::{layer::util::Stack, Layer, Service};

use std::task::{Context, Poll};
use aws_smithy_http_server::extension::OperationId;

/// A [`Service`] that prints a given string.
#[derive(Clone, Debug)]
pub struct PrintService<S> {
inner: S,
name: &'static str,
name: OperationId,
}

impl<R, S> Service<R> for PrintService<S>
Expand All @@ -41,15 +42,15 @@ where
/// A [`Layer`] which constructs the [`PrintService`].
#[derive(Debug)]
pub struct PrintLayer {
name: &'static str,
name: OperationId,
}
impl<S> Layer<S> for PrintLayer {
type Service = PrintService<S>;

fn layer(&self, service: S) -> Self::Service {
PrintService {
inner: service,
name: self.name,
name: self.name.clone(),
}
}
}
Expand All @@ -66,7 +67,7 @@ where
type Layer = Stack<L, PrintLayer>;

fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer> {
input.layer(PrintLayer { name: Op::NAME })
input.layer(PrintLayer { name: OperationId(Op::NAME) })
}
}

Expand Down
115 changes: 56 additions & 59 deletions rust-runtime/aws-smithy-http-server/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
//! [extensions]: https://docs.rs/http/latest/http/struct.Extensions.html

use std::{fmt, future::Future, ops::Deref, pin::Pin, task::Context, task::Poll};
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};

use futures_util::ready;
use futures_util::TryFuture;
Expand All @@ -28,20 +30,16 @@ use tower::{layer::util::Stack, Layer, Service};

use crate::operation::{Operation, OperationShape};
use crate::plugin::{plugin_from_operation_name_fn, OperationNameFn, Plugin, PluginPipeline, PluginStack};
use crate::shape_id::ShapeId;

pub use crate::request::extension::{Extension, MissingExtension};

/// Extension type used to store information about Smithy operations in HTTP responses.
/// This extension type is inserted, via the [`OperationExtensionPlugin`], whenever it has been correctly determined
/// This extension type is inserted, via the [`OperationIdPlugin`], whenever it has been correctly determined
/// that the request should be routed to a particular operation. The operation handler might not even get invoked
/// because the request fails to deserialize into the modeled operation input.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OperationExtension {
absolute: &'static str,

namespace: &'static str,
name: &'static str,
}
pub struct OperationId(pub ShapeId);

/// An error occurred when parsing an absolute operation shape ID.
#[derive(Debug, Clone, Error, PartialEq, Eq)]
Expand All @@ -52,46 +50,46 @@ pub enum ParseError {
}

#[allow(deprecated)]
impl OperationExtension {
/// Creates a new [`OperationExtension`] from the absolute shape ID.
pub fn new(absolute_operation_id: &'static str) -> Result<Self, ParseError> {
let (namespace, name) = absolute_operation_id
.rsplit_once('#')
.ok_or(ParseError::MissingNamespace)?;
Ok(Self {
absolute: absolute_operation_id,
namespace,
name,
})
}

impl OperationId {
/// Returns the Smithy model namespace.
pub fn namespace(&self) -> &'static str {
self.namespace
self.0.namespace()
}

/// Returns the Smithy operation name.
pub fn name(&self) -> &'static str {
self.name
self.0.name()
}

/// Returns the absolute operation shape ID.
pub fn absolute(&self) -> &'static str {
self.absolute
self.0.absolute()
}
}

impl Display for OperationId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.absolute())
82marbag marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl Hash for OperationId {
82marbag marked this conversation as resolved.
Show resolved Hide resolved
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.absolute().hash(state)
}
}

pin_project_lite::pin_project! {
/// The [`Service::Future`] of [`OperationExtensionService`] - inserts an [`OperationExtension`] into the
/// The [`Service::Future`] of [`OperationIdService`] - inserts an [`OperationId`] into the
/// [`http::Response]`.
pub struct OperationExtensionFuture<Fut> {
pub struct OperationIdFuture<Fut> {
#[pin]
inner: Fut,
operation_extension: Option<OperationExtension>
operation_extension: Option<OperationId>
}
}

impl<Fut, RespB> Future for OperationExtensionFuture<Fut>
impl<Fut, RespB> Future for OperationIdFuture<Fut>
where
Fut: TryFuture<Ok = http::Response<RespB>>,
{
Expand All @@ -111,82 +109,81 @@ where
}
}

/// Inserts a [`OperationExtension`] into the extensions of the [`http::Response`].
/// Inserts a [`OperationId`] into the extensions of the [`http::Response`].
#[derive(Debug, Clone)]
pub struct OperationExtensionService<S> {
pub struct OperationIdService<S> {
inner: S,
operation_extension: OperationExtension,
operation_extension: OperationId,
}

impl<S, B, RespBody> Service<http::Request<B>> for OperationExtensionService<S>
impl<S, B, RespBody> Service<http::Request<B>> for OperationIdService<S>
where
S: Service<http::Request<B>, Response = http::Response<RespBody>>,
{
type Response = http::Response<RespBody>;
type Error = S::Error;
type Future = OperationExtensionFuture<S::Future>;
type Future = OperationIdFuture<S::Future>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}

fn call(&mut self, req: http::Request<B>) -> Self::Future {
OperationExtensionFuture {
OperationIdFuture {
inner: self.inner.call(req),
operation_extension: Some(self.operation_extension.clone()),
}
}
}

/// A [`Layer`] applying the [`OperationExtensionService`] to an inner [`Service`].
/// A [`Layer`] applying the [`OperationIdService`] to an inner [`Service`].
#[derive(Debug, Clone)]
pub struct OperationExtensionLayer(OperationExtension);
pub struct OperationIdLayer(OperationId);

impl<S> Layer<S> for OperationExtensionLayer {
type Service = OperationExtensionService<S>;
impl<S> Layer<S> for OperationIdLayer {
type Service = OperationIdService<S>;

fn layer(&self, inner: S) -> Self::Service {
OperationExtensionService {
OperationIdService {
inner,
operation_extension: self.0.clone(),
}
}
}

/// A [`Plugin`] which applies [`OperationExtensionLayer`] to every operation.
pub struct OperationExtensionPlugin(OperationNameFn<fn(&'static str) -> OperationExtensionLayer>);
/// A [`Plugin`] which applies [`OperationIdLayer`] to every operation.
pub struct OperationIdPlugin(OperationNameFn<fn(OperationId) -> OperationIdLayer>);

impl fmt::Debug for OperationExtensionPlugin {
impl fmt::Debug for OperationIdPlugin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("OperationExtensionPlugin").field(&"...").finish()
f.debug_tuple("OperationIdPlugin").field(&"...").finish()
}
}

impl<P, Op, S, L> Plugin<P, Op, S, L> for OperationExtensionPlugin
impl<P, Op, S, L> Plugin<P, Op, S, L> for OperationIdPlugin
where
Op: OperationShape,
{
type Service = S;
type Layer = Stack<L, OperationExtensionLayer>;
type Layer = Stack<L, OperationIdLayer>;

fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer> {
<OperationNameFn<fn(&'static str) -> OperationExtensionLayer> as Plugin<P, Op, S, L>>::map(&self.0, input)
<OperationNameFn<fn(OperationId) -> OperationIdLayer> as Plugin<P, Op, S, L>>::map(&self.0, input)
}
}

/// An extension trait on [`PluginPipeline`] allowing the application of [`OperationExtensionPlugin`].
/// An extension trait on [`PluginPipeline`] allowing the application of [`OperationIdPlugin`].
///
/// See [`module`](crate::extension) documentation for more info.
pub trait OperationExtensionExt<P> {
/// Apply the [`OperationExtensionPlugin`], which inserts the [`OperationExtension`] into every [`http::Response`].
fn insert_operation_extension(self) -> PluginPipeline<PluginStack<OperationExtensionPlugin, P>>;
pub trait OperationIdExt<P> {
/// Apply the [`OperationIdPlugin`], which inserts the [`OperationId`] into every [`http::Response`].
fn insert_operation_extension(self) -> PluginPipeline<PluginStack<OperationIdPlugin, P>>;
}

impl<P> OperationExtensionExt<P> for PluginPipeline<P> {
fn insert_operation_extension(self) -> PluginPipeline<PluginStack<OperationExtensionPlugin, P>> {
let plugin = OperationExtensionPlugin(plugin_from_operation_name_fn(|name| {
let operation_extension = OperationExtension::new(name).expect("Operation name is malformed, this should never happen. Please file an issue against https://github.com/awslabs/smithy-rs");
OperationExtensionLayer(operation_extension)
impl<P> OperationIdExt<P> for PluginPipeline<P> {
fn insert_operation_extension(self) -> PluginPipeline<PluginStack<OperationIdPlugin, P>> {
let plugin = OperationIdPlugin(plugin_from_operation_name_fn(|shape_id| {
OperationIdLayer(shape_id.clone())
}));
self.push(plugin)
}
Expand Down Expand Up @@ -243,7 +240,7 @@ mod tests {
#[test]
fn ext_accept() {
let value = "com.amazonaws.ebs#CompleteSnapshot";
let ext = OperationExtension::new(value).unwrap();
let ext = OperationId::new(value).unwrap();

assert_eq!(ext.absolute(), value);
assert_eq!(ext.namespace(), "com.amazonaws.ebs");
Expand All @@ -254,7 +251,7 @@ mod tests {
fn ext_reject() {
let value = "CompleteSnapshot";
assert_eq!(
OperationExtension::new(value).unwrap_err(),
OperationId::new(value).unwrap_err(),
ParseError::MissingNamespace
)
}
Expand All @@ -281,10 +278,10 @@ mod tests {
let svc = service_fn(|_: http::Request<()>| async { Ok::<_, ()>(http::Response::new(())) });
let svc = layer.layer(svc);

// Check for `OperationExtension`.
// Check for `OperationId`.
82marbag marked this conversation as resolved.
Show resolved Hide resolved
let response = svc.oneshot(http::Request::new(())).await.unwrap();
let expected = OperationExtension::new(DummyOp::NAME).unwrap();
let actual = response.extensions().get::<OperationExtension>().unwrap();
let expected = OperationId::new(DummyOp::NAME).unwrap();
let actual = response.extensions().get::<OperationId>().unwrap();
assert_eq!(*actual, expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@

use tower::Layer;

use crate::extension::OperationId;

use super::{InstrumentOperation, MakeIdentity};

/// A [`Layer`] used to apply [`InstrumentOperation`].
#[derive(Debug)]
pub struct InstrumentLayer<RequestMakeFmt = MakeIdentity, ResponseMakeFmt = MakeIdentity> {
operation_name: &'static str,
operation_name: OperationId,
make_request: RequestMakeFmt,
make_response: ResponseMakeFmt,
}

impl InstrumentLayer {
/// Constructs a new [`InstrumentLayer`] with no data redacted.
pub fn new(operation_name: &'static str) -> Self {
pub fn new(operation_name: OperationId) -> Self {
Self {
operation_name,
make_request: MakeIdentity,
Expand Down Expand Up @@ -58,7 +60,7 @@ where
type Service = InstrumentOperation<S, RequestMakeFmt, ResponseMakeFmt>;

fn layer(&self, service: S) -> Self::Service {
InstrumentOperation::new(service, self.operation_name)
InstrumentOperation::new(service, self.operation_name.clone())
.request_fmt(self.make_request.clone())
.response_fmt(self.make_response.clone())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use tower::layer::util::Stack;

use crate::plugin::{PluginPipeline, PluginStack};
use crate::{
extension::OperationId,
operation::{Operation, OperationShape},
plugin::Plugin,
};
Expand All @@ -26,7 +27,8 @@ where
type Layer = Stack<L, InstrumentLayer<Op::RequestFmt, Op::ResponseFmt>>;

fn map(&self, operation: Operation<S, L>) -> Operation<Self::Service, Self::Layer> {
let layer = InstrumentLayer::new(Op::NAME)
let operation_id = OperationId(Op::NAME);
let layer = InstrumentLayer::new(operation_id)
.request_fmt(Op::request_fmt())
.response_fmt(Op::response_fmt());
operation.layer(layer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use http::{HeaderMap, Request, Response, StatusCode, Uri};
use tower::Service;
use tracing::{debug, debug_span, instrument::Instrumented, Instrument};

use crate::extension::OperationId;

use super::{MakeDebug, MakeDisplay, MakeIdentity};

pin_project_lite::pin_project! {
Expand Down Expand Up @@ -103,14 +105,14 @@ where
#[derive(Debug, Clone)]
pub struct InstrumentOperation<S, RequestMakeFmt = MakeIdentity, ResponseMakeFmt = MakeIdentity> {
inner: S,
operation_name: &'static str,
operation_name: OperationId,
make_request: RequestMakeFmt,
make_response: ResponseMakeFmt,
}

impl<S> InstrumentOperation<S> {
/// Constructs a new [`InstrumentOperation`] with no data redacted.
pub fn new(inner: S, operation_name: &'static str) -> Self {
pub fn new(inner: S, operation_name: OperationId) -> Self {
Self {
inner,
operation_name,
Expand Down
1 change: 1 addition & 0 deletions rust-runtime/aws-smithy-http-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod response;
pub mod routing;
#[doc(hidden)]
pub mod runtime_error;
pub mod shape_id;

#[doc(inline)]
pub(crate) use self::error::Error;
Expand Down
Loading