From 3689772e8fc72803f4adb21d1d1b41752081c33b Mon Sep 17 00:00:00 2001 From: 82marbag <69267416+82marbag@users.noreply.github.com> Date: Thu, 11 May 2023 16:02:03 +0100 Subject: [PATCH] OperationShape::NAME as ShapeId (#2678) See https://github.com/awslabs/smithy-rs/issues/2634 ## Checklist - [x] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK 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._ --------- Signed-off-by: Daniele Ahmed --- CHANGELOG.next.toml | 8 ++ .../generators/ServerOperationGenerator.kt | 4 +- .../generators/ServerServiceGenerator.kt | 7 +- examples/pokemon-service/src/plugin.rs | 11 +-- .../aws-smithy-http-server/src/extension.rs | 80 ++++++------------- .../src/instrumentation/layer.rs | 14 ++-- .../src/instrumentation/mod.rs | 4 +- .../src/instrumentation/plugin.rs | 3 +- .../src/instrumentation/service.rs | 18 +++-- .../aws-smithy-http-server/src/lib.rs | 1 + .../src/operation/mod.rs | 6 +- .../src/operation/shape.rs | 3 +- .../src/plugin/closure.rs | 29 ++++--- .../src/plugin/filter.rs | 26 +++--- .../aws-smithy-http-server/src/plugin/mod.rs | 25 +++--- .../src/plugin/pipeline.rs | 11 +-- .../aws-smithy-http-server/src/shape_id.rs | 59 ++++++++++++++ 17 files changed, 184 insertions(+), 125 deletions(-) create mode 100644 rust-runtime/aws-smithy-http-server/src/shape_id.rs diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 7267f1f4e3..93b581e99d 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -29,3 +29,11 @@ message = "Avoid extending IMDS credentials' expiry unconditionally, which may i references = ["smithy-rs#2687", "smithy-rs#2694"] meta = { "breaking" = false, "tada" = false, "bug" = true } author = "ysaito1001" + +[[smithy-rs]] +message = """`ShapeId` is the new structure used to represent a shape, with its absolute name, namespace and name. +`OperationExtension`'s members are replaced by the `ShapeId` and operations' names are now replced by a `ShapeId`. +""" +author = "82marbag" +references = ["smithy-rs#2678"] +meta = { "breaking" = true, "tada" = false, "bug" = false } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationGenerator.kt index 451aa65a85..a8ce8cbf06 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationGenerator.kt @@ -13,6 +13,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext +import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.toPascalCase import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency @@ -49,12 +50,13 @@ class ServerOperationGenerator( val requestFmt = generator.requestFmt() val responseFmt = generator.responseFmt() + val operationIdAbsolute = operationId.toString().replace("#", "##") writer.rustTemplate( """ pub struct $operationName; impl #{SmithyHttpServer}::operation::OperationShape for $operationName { - const NAME: &'static str = "${operationId.toString().replace("#", "##")}"; + const NAME: #{SmithyHttpServer}::shape_id::ShapeId = #{SmithyHttpServer}::shape_id::ShapeId::new(${operationIdAbsolute.dq()}, ${operationId.namespace.dq()}, ${operationId.name.dq()}); type Input = crate::input::${operationName}Input; type Output = crate::output::${operationName}Output; diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt index 160f9fd685..d78a7aa903 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt @@ -478,13 +478,13 @@ class ServerServiceGenerator( } private fun missingOperationsError(): Writable = writable { - rust( + rustTemplate( """ /// The error encountered when calling the [`$builderName::build`] method if one or more operation handlers are not /// specified. ##[derive(Debug)] pub struct MissingOperationsError { - operation_names2setter_methods: std::collections::HashMap<&'static str, &'static str>, + operation_names2setter_methods: std::collections::HashMap<#{SmithyHttpServer}::shape_id::ShapeId, &'static str>, } impl std::fmt::Display for MissingOperationsError { @@ -495,7 +495,7 @@ class ServerServiceGenerator( We are missing handlers for the following operations:\n", )?; for operation_name in self.operation_names2setter_methods.keys() { - writeln!(f, "- {}", operation_name)?; + writeln!(f, "- {}", operation_name.absolute())?; } writeln!(f, "\nUse the dedicated methods on `$builderName` to register the missing handlers:")?; @@ -508,6 +508,7 @@ class ServerServiceGenerator( impl std::error::Error for MissingOperationsError {} """, + *codegenScope, ) } diff --git a/examples/pokemon-service/src/plugin.rs b/examples/pokemon-service/src/plugin.rs index 8ce0e50d09..ea6ee09d91 100644 --- a/examples/pokemon-service/src/plugin.rs +++ b/examples/pokemon-service/src/plugin.rs @@ -8,6 +8,7 @@ use aws_smithy_http_server::{ operation::{Operation, OperationShape}, plugin::{Plugin, PluginPipeline, PluginStack}, + shape_id::ShapeId, }; use tower::{layer::util::Stack, Layer, Service}; @@ -17,7 +18,7 @@ use std::task::{Context, Poll}; #[derive(Clone, Debug)] pub struct PrintService { inner: S, - name: &'static str, + id: ShapeId, } impl Service for PrintService @@ -33,7 +34,7 @@ where } fn call(&mut self, req: R) -> Self::Future { - println!("Hi {}", self.name); + println!("Hi {}", self.id.absolute()); self.inner.call(req) } } @@ -41,7 +42,7 @@ where /// A [`Layer`] which constructs the [`PrintService`]. #[derive(Debug)] pub struct PrintLayer { - name: &'static str, + id: ShapeId, } impl Layer for PrintLayer { type Service = PrintService; @@ -49,7 +50,7 @@ impl Layer for PrintLayer { fn layer(&self, service: S) -> Self::Service { PrintService { inner: service, - name: self.name, + id: self.id.clone(), } } } @@ -66,7 +67,7 @@ where type Layer = Stack; fn map(&self, input: Operation) -> Operation { - input.layer(PrintLayer { name: Op::NAME }) + input.layer(PrintLayer { id: Op::NAME }) } } diff --git a/rust-runtime/aws-smithy-http-server/src/extension.rs b/rust-runtime/aws-smithy-http-server/src/extension.rs index 2a8b664cdd..371df01529 100644 --- a/rust-runtime/aws-smithy-http-server/src/extension.rs +++ b/rust-runtime/aws-smithy-http-server/src/extension.rs @@ -19,15 +19,18 @@ //! //! [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::hash::Hash; +use std::{fmt, fmt::Debug, future::Future, ops::Deref, pin::Pin, task::Context, task::Poll}; +use crate::extension; use futures_util::ready; use futures_util::TryFuture; use thiserror::Error; 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::plugin::{plugin_from_operation_id_fn, OperationIdFn, Plugin, PluginPipeline, PluginStack}; +use crate::shape_id::ShapeId; pub use crate::request::extension::{Extension, MissingExtension}; @@ -35,13 +38,8 @@ pub use crate::request::extension::{Extension, MissingExtension}; /// This extension type is inserted, via the [`OperationExtensionPlugin`], 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, -} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct OperationExtension(pub ShapeId); /// An error occurred when parsing an absolute operation shape ID. #[derive(Debug, Clone, Error, PartialEq, Eq)] @@ -51,36 +49,6 @@ pub enum ParseError { MissingNamespace, } -#[allow(deprecated)] -impl OperationExtension { - /// Creates a new [`OperationExtension`] from the absolute shape ID. - pub fn new(absolute_operation_id: &'static str) -> Result { - let (namespace, name) = absolute_operation_id - .rsplit_once('#') - .ok_or(ParseError::MissingNamespace)?; - Ok(Self { - absolute: absolute_operation_id, - namespace, - name, - }) - } - - /// Returns the Smithy model namespace. - pub fn namespace(&self) -> &'static str { - self.namespace - } - - /// Returns the Smithy operation name. - pub fn name(&self) -> &'static str { - self.name - } - - /// Returns the absolute operation shape ID. - pub fn absolute(&self) -> &'static str { - self.absolute - } -} - pin_project_lite::pin_project! { /// The [`Service::Future`] of [`OperationExtensionService`] - inserts an [`OperationExtension`] into the /// [`http::Response]`. @@ -154,7 +122,7 @@ impl Layer for OperationExtensionLayer { } /// A [`Plugin`] which applies [`OperationExtensionLayer`] to every operation. -pub struct OperationExtensionPlugin(OperationNameFn OperationExtensionLayer>); +pub struct OperationExtensionPlugin(OperationIdFn OperationExtensionLayer>); impl fmt::Debug for OperationExtensionPlugin { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -170,7 +138,7 @@ where type Layer = Stack; fn map(&self, input: Operation) -> Operation { - OperationExtensionLayer> as Plugin>::map(&self.0, input) + OperationExtensionLayer> as Plugin>::map(&self.0, input) } } @@ -184,9 +152,8 @@ pub trait OperationExtensionExt

{ impl

OperationExtensionExt

for PluginPipeline

{ fn insert_operation_extension(self) -> PluginPipeline> { - 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) + let plugin = OperationExtensionPlugin(plugin_from_operation_id_fn(|shape_id| { + OperationExtensionLayer(extension::OperationExtension(shape_id)) })); self.push(plugin) } @@ -243,28 +210,27 @@ mod tests { #[test] fn ext_accept() { let value = "com.amazonaws.ebs#CompleteSnapshot"; - let ext = OperationExtension::new(value).unwrap(); + let ext = ShapeId::new( + "com.amazonaws.ebs#CompleteSnapshot", + "com.amazonaws.ebs", + "CompleteSnapshot", + ); assert_eq!(ext.absolute(), value); assert_eq!(ext.namespace(), "com.amazonaws.ebs"); assert_eq!(ext.name(), "CompleteSnapshot"); } - #[test] - fn ext_reject() { - let value = "CompleteSnapshot"; - assert_eq!( - OperationExtension::new(value).unwrap_err(), - ParseError::MissingNamespace - ) - } - #[tokio::test] async fn plugin() { struct DummyOp; impl OperationShape for DummyOp { - const NAME: &'static str = "com.amazonaws.ebs#CompleteSnapshot"; + const NAME: ShapeId = ShapeId::new( + "com.amazonaws.ebs#CompleteSnapshot", + "com.amazonaws.ebs", + "CompleteSnapshot", + ); type Input = (); type Output = (); @@ -283,8 +249,8 @@ mod tests { // Check for `OperationExtension`. let response = svc.oneshot(http::Request::new(())).await.unwrap(); - let expected = OperationExtension::new(DummyOp::NAME).unwrap(); + let expected = DummyOp::NAME; let actual = response.extensions().get::().unwrap(); - assert_eq!(*actual, expected); + assert_eq!(actual.0, expected); } } diff --git a/rust-runtime/aws-smithy-http-server/src/instrumentation/layer.rs b/rust-runtime/aws-smithy-http-server/src/instrumentation/layer.rs index 956fd6b24d..c070d1297e 100644 --- a/rust-runtime/aws-smithy-http-server/src/instrumentation/layer.rs +++ b/rust-runtime/aws-smithy-http-server/src/instrumentation/layer.rs @@ -5,21 +5,23 @@ use tower::Layer; +use crate::shape_id::ShapeId; + use super::{InstrumentOperation, MakeIdentity}; /// A [`Layer`] used to apply [`InstrumentOperation`]. #[derive(Debug)] pub struct InstrumentLayer { - operation_name: &'static str, + operation_id: ShapeId, 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_id: ShapeId) -> Self { Self { - operation_name, + operation_id, make_request: MakeIdentity, make_response: MakeIdentity, } @@ -32,7 +34,7 @@ impl InstrumentLayer(self, make_request: R) -> InstrumentLayer { InstrumentLayer { - operation_name: self.operation_name, + operation_id: self.operation_id, make_request, make_response: self.make_response, } @@ -43,7 +45,7 @@ impl InstrumentLayer(self, make_response: R) -> InstrumentLayer { InstrumentLayer { - operation_name: self.operation_name, + operation_id: self.operation_id, make_request: self.make_request, make_response, } @@ -58,7 +60,7 @@ where type Service = InstrumentOperation; fn layer(&self, service: S) -> Self::Service { - InstrumentOperation::new(service, self.operation_name) + InstrumentOperation::new(service, self.operation_id.clone()) .request_fmt(self.make_request.clone()) .response_fmt(self.make_response.clone()) } diff --git a/rust-runtime/aws-smithy-http-server/src/instrumentation/mod.rs b/rust-runtime/aws-smithy-http-server/src/instrumentation/mod.rs index 72fd2af2e6..2519e10137 100644 --- a/rust-runtime/aws-smithy-http-server/src/instrumentation/mod.rs +++ b/rust-runtime/aws-smithy-http-server/src/instrumentation/mod.rs @@ -13,6 +13,7 @@ //! ``` //! # use std::convert::Infallible; //! # use aws_smithy_http_server::instrumentation::{*, sensitivity::{*, headers::*, uri::*}}; +//! # use aws_smithy_http_server::shape_id::ShapeId; //! # use http::{Request, Response}; //! # use tower::{util::service_fn, Service}; //! # async fn service(request: Request<()>) -> Result, Infallible> { @@ -20,6 +21,7 @@ //! # } //! # async fn example() { //! # let service = service_fn(service); +//! # const NAME: ShapeId = ShapeId::new("namespace#foo-operation", "namespace", "foo-operation"); //! let request = Request::get("http://localhost/a/b/c/d?bar=hidden") //! .header("header-name-a", "hidden") //! .body(()) @@ -47,7 +49,7 @@ //! } //! }) //! .status_code(); -//! let mut service = InstrumentOperation::new(service, "foo-operation") +//! let mut service = InstrumentOperation::new(service, NAME) //! .request_fmt(request_fmt) //! .response_fmt(response_fmt); //! diff --git a/rust-runtime/aws-smithy-http-server/src/instrumentation/plugin.rs b/rust-runtime/aws-smithy-http-server/src/instrumentation/plugin.rs index ce77218603..7da5e2fbeb 100644 --- a/rust-runtime/aws-smithy-http-server/src/instrumentation/plugin.rs +++ b/rust-runtime/aws-smithy-http-server/src/instrumentation/plugin.rs @@ -26,7 +26,8 @@ where type Layer = Stack>; fn map(&self, operation: Operation) -> Operation { - let layer = InstrumentLayer::new(Op::NAME) + let operation_id = Op::NAME; + let layer = InstrumentLayer::new(operation_id) .request_fmt(Op::request_fmt()) .response_fmt(Op::response_fmt()); operation.layer(layer) diff --git a/rust-runtime/aws-smithy-http-server/src/instrumentation/service.rs b/rust-runtime/aws-smithy-http-server/src/instrumentation/service.rs index 1047167ffb..76410cfa65 100644 --- a/rust-runtime/aws-smithy-http-server/src/instrumentation/service.rs +++ b/rust-runtime/aws-smithy-http-server/src/instrumentation/service.rs @@ -16,6 +16,8 @@ use http::{HeaderMap, Request, Response, StatusCode, Uri}; use tower::Service; use tracing::{debug, debug_span, instrument::Instrumented, Instrument}; +use crate::shape_id::ShapeId; + use super::{MakeDebug, MakeDisplay, MakeIdentity}; pin_project_lite::pin_project! { @@ -87,15 +89,17 @@ where /// /// ``` /// # use aws_smithy_http_server::instrumentation::{sensitivity::{*, uri::*, headers::*}, *}; +/// # use aws_smithy_http_server::shape_id::ShapeId; /// # use tower::{Service, service_fn}; /// # use http::{Request, Response}; /// # async fn f(request: Request<()>) -> Result, ()> { Ok(Response::new(())) } /// # let mut svc = service_fn(f); +/// # const NAME: ShapeId = ShapeId::new("namespace#foo-operation", "namespace", "foo-operation"); /// let request_fmt = RequestFmt::new() /// .label(|index| index == 1, None) /// .query(|_| QueryMarker { key: false, value: true }); /// let response_fmt = ResponseFmt::new().status_code(); -/// let mut svc = InstrumentOperation::new(svc, "foo-operation") +/// let mut svc = InstrumentOperation::new(svc, NAME) /// .request_fmt(request_fmt) /// .response_fmt(response_fmt); /// # svc.call(Request::new(())); @@ -103,17 +107,17 @@ where #[derive(Debug, Clone)] pub struct InstrumentOperation { inner: S, - operation_name: &'static str, + operation_id: ShapeId, make_request: RequestMakeFmt, make_response: ResponseMakeFmt, } impl InstrumentOperation { /// Constructs a new [`InstrumentOperation`] with no data redacted. - pub fn new(inner: S, operation_name: &'static str) -> Self { + pub fn new(inner: S, operation_id: ShapeId) -> Self { Self { inner, - operation_name, + operation_id, make_request: MakeIdentity, make_response: MakeIdentity, } @@ -127,7 +131,7 @@ impl InstrumentOperation(self, make_request: R) -> InstrumentOperation { InstrumentOperation { inner: self.inner, - operation_name: self.operation_name, + operation_id: self.operation_id, make_request, make_response: self.make_response, } @@ -139,7 +143,7 @@ impl InstrumentOperation(self, make_response: R) -> InstrumentOperation { InstrumentOperation { inner: self.inner, - operation_name: self.operation_name, + operation_id: self.operation_id, make_request: self.make_request, make_response, } @@ -170,7 +174,7 @@ where let span = { let headers = self.make_request.make_debug(request.headers()); let uri = self.make_request.make_display(request.uri()); - debug_span!("request", operation = %self.operation_name, method = %request.method(), %uri, ?headers) + debug_span!("request", operation = %self.operation_id.absolute(), method = %request.method(), %uri, ?headers) }; InstrumentedFuture { diff --git a/rust-runtime/aws-smithy-http-server/src/lib.rs b/rust-runtime/aws-smithy-http-server/src/lib.rs index 6031c53e2a..c6d1175c37 100644 --- a/rust-runtime/aws-smithy-http-server/src/lib.rs +++ b/rust-runtime/aws-smithy-http-server/src/lib.rs @@ -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; diff --git a/rust-runtime/aws-smithy-http-server/src/operation/mod.rs b/rust-runtime/aws-smithy-http-server/src/operation/mod.rs index 3abf9e2540..d94596b8ad 100644 --- a/rust-runtime/aws-smithy-http-server/src/operation/mod.rs +++ b/rust-runtime/aws-smithy-http-server/src/operation/mod.rs @@ -24,6 +24,7 @@ //! is identified with the implementation //! //! ```rust,no_run +//! # use aws_smithy_http_server::shape_id::ShapeId; //! # use aws_smithy_http_server::operation::OperationShape; //! # pub struct CartIdentifier; //! # pub struct ShoppingCart; @@ -31,7 +32,7 @@ //! pub struct GetShopping; //! //! impl OperationShape for GetShopping { -//! const NAME: &'static str = "GetShopping"; +//! const NAME: ShapeId = ShapeId::new("namespace#GetShopping", "namespace", "GetShopping"); //! //! type Input = CartIdentifier; //! type Output = ShoppingCart; @@ -105,12 +106,13 @@ //! # use std::task::{Poll, Context}; //! # use aws_smithy_http_server::operation::*; //! # use tower::Service; +//! # use aws_smithy_http_server::shape_id::ShapeId; //! # pub struct CartIdentifier; //! # pub struct ShoppingCart; //! # pub enum GetShoppingError {} //! # pub struct GetShopping; //! # impl OperationShape for GetShopping { -//! # const NAME: &'static str = "GetShopping"; +//! # const NAME: ShapeId = ShapeId::new("namespace#GetShopping", "namespace", "GetShopping"); //! # //! # type Input = CartIdentifier; //! # type Output = ShoppingCart; diff --git a/rust-runtime/aws-smithy-http-server/src/operation/shape.rs b/rust-runtime/aws-smithy-http-server/src/operation/shape.rs index 9990326279..40a915a35b 100644 --- a/rust-runtime/aws-smithy-http-server/src/operation/shape.rs +++ b/rust-runtime/aws-smithy-http-server/src/operation/shape.rs @@ -4,13 +4,14 @@ */ use super::{Handler, IntoService, Normalize, Operation, OperationService}; +use crate::shape_id::ShapeId; /// Models the [Smithy Operation shape]. /// /// [Smithy Operation shape]: https://awslabs.github.io/smithy/1.0/spec/core/model.html#operation pub trait OperationShape { /// The name of the operation. - const NAME: &'static str; + const NAME: ShapeId; /// The operation input. type Input; diff --git a/rust-runtime/aws-smithy-http-server/src/plugin/closure.rs b/rust-runtime/aws-smithy-http-server/src/plugin/closure.rs index 75685c97b9..f1f5951e8a 100644 --- a/rust-runtime/aws-smithy-http-server/src/plugin/closure.rs +++ b/rust-runtime/aws-smithy-http-server/src/plugin/closure.rs @@ -6,56 +6,59 @@ use tower::layer::util::Stack; use crate::operation::{Operation, OperationShape}; +use crate::shape_id::ShapeId; use super::Plugin; -/// An adapter to convert a `Fn(&'static str) -> Layer` closure into a [`Plugin`]. See [`plugin_from_operation_name_fn`] for more details. -pub struct OperationNameFn { +/// An adapter to convert a `Fn(ShapeId) -> Layer` closure into a [`Plugin`]. See [`plugin_from_operation_id_fn`] for more details. +pub struct OperationIdFn { f: F, } -impl Plugin for OperationNameFn +impl Plugin for OperationIdFn where - F: Fn(&'static str) -> NewLayer, + F: Fn(ShapeId) -> NewLayer, Op: OperationShape, { type Service = S; type Layer = Stack; fn map(&self, input: Operation) -> Operation { - input.layer((self.f)(Op::NAME)) + let operation_id = Op::NAME; + input.layer((self.f)(operation_id)) } } -/// Constructs a [`Plugin`] using a closure over the operation name `F: Fn(&'static str) -> L` where `L` is a HTTP +/// Constructs a [`Plugin`] using a closure over the operation name `F: Fn(ShapeId) -> L` where `L` is a HTTP /// [`Layer`](tower::Layer). /// /// # Example /// /// ```rust -/// use aws_smithy_http_server::plugin::plugin_from_operation_name_fn; +/// use aws_smithy_http_server::plugin::plugin_from_operation_id_fn; +/// use aws_smithy_http_server::shape_id::ShapeId; /// use tower::layer::layer_fn; /// /// // A `Service` which prints the operation name before calling `S`. /// struct PrintService { -/// operation_name: &'static str, +/// operation_name: ShapeId, /// inner: S /// } /// /// // A `Layer` applying `PrintService`. /// struct PrintLayer { -/// operation_name: &'static str +/// operation_name: ShapeId /// } /// /// // Defines a closure taking the operation name to `PrintLayer`. /// let f = |operation_name| PrintLayer { operation_name }; /// /// // This plugin applies the `PrintService` middleware around every operation. -/// let plugin = plugin_from_operation_name_fn(f); +/// let plugin = plugin_from_operation_id_fn(f); /// ``` -pub fn plugin_from_operation_name_fn(f: F) -> OperationNameFn +pub fn plugin_from_operation_id_fn(f: F) -> OperationIdFn where - F: Fn(&'static str) -> L, + F: Fn(ShapeId) -> L, { - OperationNameFn { f } + OperationIdFn { f } } diff --git a/rust-runtime/aws-smithy-http-server/src/plugin/filter.rs b/rust-runtime/aws-smithy-http-server/src/plugin/filter.rs index 814398b089..a977db8bf2 100644 --- a/rust-runtime/aws-smithy-http-server/src/plugin/filter.rs +++ b/rust-runtime/aws-smithy-http-server/src/plugin/filter.rs @@ -6,14 +6,15 @@ use super::{either::Either, IdentityPlugin}; use crate::operation::{Operation, OperationShape}; +use crate::shape_id::ShapeId; use super::Plugin; /// Filters the application of an inner [`Plugin`] using a predicate over the /// [`OperationShape::NAME`](crate::operation::OperationShape). /// -/// See [`filter_by_operation_name`] for more details. -pub struct FilterByOperationName { +/// See [`filter_by_operation_id`] for more details. +pub struct FilterByOperationId { inner: Inner, predicate: F, } @@ -24,35 +25,36 @@ pub struct FilterByOperationName { /// # Example /// /// ```rust -/// use aws_smithy_http_server::plugin::filter_by_operation_name; +/// use aws_smithy_http_server::plugin::filter_by_operation_id; +/// use aws_smithy_http_server::shape_id::ShapeId; /// # use aws_smithy_http_server::{plugin::Plugin, operation::{Operation, OperationShape}}; /// # struct Pl; /// # struct CheckHealth; -/// # impl OperationShape for CheckHealth { const NAME: &'static str = ""; type Input = (); type Output = (); type Error = (); } +/// # impl OperationShape for CheckHealth { const NAME: ShapeId = ShapeId::new("ns#CheckHealth", "ns", "CheckHealth"); 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 = filter_by_operation_name(plugin, |name| name != CheckHealth::NAME); +/// let filtered_plugin = filter_by_operation_id(plugin, |name| name != CheckHealth::NAME); /// let new_operation = filtered_plugin.map(operation); /// ``` -pub fn filter_by_operation_name(plugins: Inner, predicate: F) -> FilterByOperationName +pub fn filter_by_operation_id(plugins: Inner, predicate: F) -> FilterByOperationId where - F: Fn(&str) -> bool, + F: Fn(ShapeId) -> bool, { - FilterByOperationName::new(plugins, predicate) + FilterByOperationId::new(plugins, predicate) } -impl FilterByOperationName { - /// Creates a new [`FilterByOperationName`]. +impl FilterByOperationId { + /// Creates a new [`FilterByOperationId`]. fn new(inner: Inner, predicate: F) -> Self { Self { inner, predicate } } } -impl Plugin for FilterByOperationName +impl Plugin for FilterByOperationId where - F: Fn(&str) -> bool, + F: Fn(ShapeId) -> bool, Inner: Plugin, Op: OperationShape, { diff --git a/rust-runtime/aws-smithy-http-server/src/plugin/mod.rs b/rust-runtime/aws-smithy-http-server/src/plugin/mod.rs index ab3c385720..19fa4b9d32 100644 --- a/rust-runtime/aws-smithy-http-server/src/plugin/mod.rs +++ b/rust-runtime/aws-smithy-http-server/src/plugin/mod.rs @@ -12,27 +12,29 @@ //! //! ``` //! # use aws_smithy_http_server::plugin::*; +//! # use aws_smithy_http_server::shape_id::ShapeId; //! # let layer = (); //! # struct GetPokemonSpecies; -//! # impl GetPokemonSpecies { const NAME: &'static str = ""; }; +//! # impl GetPokemonSpecies { const NAME: ShapeId = ShapeId::new("namespace#name", "namespace", "name"); }; //! // Create a `Plugin` from a HTTP `Layer` //! let plugin = HttpLayer(layer); //! //! // Only apply the layer to operations with name "GetPokemonSpecies" -//! let plugin = filter_by_operation_name(plugin, |name| name == GetPokemonSpecies::NAME); +//! let plugin = filter_by_operation_id(plugin, |name| name == GetPokemonSpecies::NAME); //! ``` //! //! # Construct a [`Plugin`] from a closure that takes as input the operation name //! //! ``` //! # use aws_smithy_http_server::plugin::*; +//! # use aws_smithy_http_server::shape_id::ShapeId; //! // A `tower::Layer` which requires the operation name //! struct PrintLayer { -//! name: &'static str +//! name: ShapeId, //! } //! //! // Create a `Plugin` using `PrintLayer` -//! let plugin = plugin_from_operation_name_fn(|name| PrintLayer { name }); +//! let plugin = plugin_from_operation_id_fn(|name| PrintLayer { name }); //! ``` //! //! # Combine [`Plugin`]s @@ -55,6 +57,7 @@ //! use aws_smithy_http_server::{ //! operation::{Operation, OperationShape}, //! plugin::{Plugin, PluginPipeline, PluginStack}, +//! shape_id::ShapeId, //! }; //! # use tower::{layer::util::Stack, Layer, Service}; //! # use std::task::{Context, Poll}; @@ -63,7 +66,7 @@ //! #[derive(Clone, Debug)] //! pub struct PrintService { //! inner: S, -//! name: &'static str, +//! id: ShapeId, //! } //! //! impl Service for PrintService @@ -79,7 +82,7 @@ //! } //! //! fn call(&mut self, req: R) -> Self::Future { -//! println!("Hi {}", self.name); +//! println!("Hi {}", self.id.absolute()); //! self.inner.call(req) //! } //! } @@ -87,7 +90,7 @@ //! /// A [`Layer`] which constructs the [`PrintService`]. //! #[derive(Debug)] //! pub struct PrintLayer { -//! name: &'static str, +//! id: ShapeId, //! } //! impl Layer for PrintLayer { //! type Service = PrintService; @@ -95,7 +98,7 @@ //! fn layer(&self, service: S) -> Self::Service { //! PrintService { //! inner: service, -//! name: self.name, +//! id: self.id.clone(), //! } //! } //! } @@ -112,7 +115,7 @@ //! type Layer = Stack; //! //! fn map(&self, input: Operation) -> Operation { -//! input.layer(PrintLayer { name: Op::NAME }) +//! input.layer(PrintLayer { id: Op::NAME }) //! } //! } //! ``` @@ -129,9 +132,9 @@ mod stack; use crate::operation::Operation; -pub use closure::{plugin_from_operation_name_fn, OperationNameFn}; +pub use closure::{plugin_from_operation_id_fn, OperationIdFn}; pub use either::Either; -pub use filter::{filter_by_operation_name, FilterByOperationName}; +pub use filter::{filter_by_operation_id, FilterByOperationId}; pub use identity::IdentityPlugin; pub use layer::HttpLayer; pub use pipeline::PluginPipeline; diff --git a/rust-runtime/aws-smithy-http-server/src/plugin/pipeline.rs b/rust-runtime/aws-smithy-http-server/src/plugin/pipeline.rs index 2a956922e4..d38ecfd782 100644 --- a/rust-runtime/aws-smithy-http-server/src/plugin/pipeline.rs +++ b/rust-runtime/aws-smithy-http-server/src/plugin/pipeline.rs @@ -35,19 +35,20 @@ use super::HttpLayer; /// /// `PluginPipeline` is itself a [`Plugin`]: you can apply any transformation that expects a /// [`Plugin`] to an entire pipeline. In this case, we want to use -/// [`filter_by_operation_name`](crate::plugin::filter_by_operation_name) to limit the scope of +/// [`filter_by_operation_name`](crate::plugin::filter_by_operation_id) to limit the scope of /// the logging and metrics plugins to the `CheckHealth` operation: /// /// ```rust -/// use aws_smithy_http_server::plugin::{filter_by_operation_name, PluginPipeline}; +/// use aws_smithy_http_server::plugin::{filter_by_operation_id, PluginPipeline}; /// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin; /// # use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin; /// # use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin; +/// use aws_smithy_http_server::shape_id::ShapeId; /// # struct CheckHealth; -/// # impl CheckHealth { const NAME: &'static str = "MyName"; } +/// # impl CheckHealth { const NAME: ShapeId = ShapeId::new("namespace#MyName", "namespace", "MyName"); } /// /// // The logging and metrics plugins will only be applied to the `CheckHealth` operation. -/// let operation_specific_pipeline = filter_by_operation_name( +/// let operation_specific_pipeline = filter_by_operation_id( /// PluginPipeline::new() /// .push(LoggingPlugin) /// .push(MetricsPlugin), @@ -156,7 +157,7 @@ impl

PluginPipeline

{ /// { /// // [...] /// fn map(&self, input: Operation) -> Operation { - /// input.layer(PrintLayer { name: Op::NAME }) + /// input.layer(PrintLayer { id: Op::NAME }) /// } /// } /// ``` diff --git a/rust-runtime/aws-smithy-http-server/src/shape_id.rs b/rust-runtime/aws-smithy-http-server/src/shape_id.rs new file mode 100644 index 0000000000..1cb13f0921 --- /dev/null +++ b/rust-runtime/aws-smithy-http-server/src/shape_id.rs @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Extension types. +//! +//! Shape ID is a type that describes a Smithy shape. +//! +//! # Example +//! +//! In the following model: +//! ```smithy +//! namespace smithy.example +//! +//! operation CheckHealth {} +//! ``` +//! +//! - `absolute` is `"smithy.example#CheckHealth"` +//! - `namespace` is `"smithy.example"` +//! - `name` is `"CheckHealth"` + +pub use crate::request::extension::{Extension, MissingExtension}; + +/// Shape ID for a modelled Smithy shape. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ShapeId { + absolute: &'static str, + + namespace: &'static str, + name: &'static str, +} + +impl ShapeId { + /// Constructs a new [`ShapeId`]. This is used by the code-generator which preserves the invariants of the Shape ID format. + #[doc(hidden)] + pub const fn new(absolute: &'static str, namespace: &'static str, name: &'static str) -> Self { + Self { + absolute, + namespace, + name, + } + } + + /// Returns the Smithy operation namespace. + pub fn namespace(&self) -> &'static str { + self.namespace + } + + /// Returns the Smithy operation name. + pub fn name(&self) -> &'static str { + self.name + } + + /// Returns the absolute operation shape ID. + pub fn absolute(&self) -> &'static str { + self.absolute + } +}