Skip to content

Commit

Permalink
Collect extractors into request module (#2008)
Browse files Browse the repository at this point in the history
  • Loading branch information
hlbarber authored Nov 22, 2022
1 parent 07967da commit 86fd8f5
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,9 @@ class ServerServiceGeneratorV2(
#{SmithyHttpServer}::routing::IntoMakeService::new(self)
}
/// Converts [`$serviceName`] into a [`MakeService`](tower::make::MakeService) with [`ConnectInfo`](#{SmithyHttpServer}::routing::into_make_service_with_connect_info::ConnectInfo).
pub fn into_make_service_with_connect_info<C>(self) -> #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo<Self, C> {
#{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo::new(self)
/// Converts [`$serviceName`] into a [`MakeService`](tower::make::MakeService) with [`ConnectInfo`](#{SmithyHttpServer}::request::connect_info::ConnectInfo).
pub fn into_make_service_with_connect_info<C>(self) -> #{SmithyHttpServer}::request::connect_info::IntoMakeServiceWithConnectInfo<Self, C> {
#{SmithyHttpServer}::request::connect_info::IntoMakeServiceWithConnectInfo::new(self)
}
/// Applies a [`Layer`](#{Tower}::Layer) uniformly to all routes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@
* SPDX-License-Identifier: Apache-2.0
*/

use std::net::{IpAddr, SocketAddr};

use aws_smithy_http_server::request::connect_info::ConnectInfo;
use clap::Parser;
use pokemon_service::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, setup_tracing,
};
use pokemon_service_server_sdk::{
error::{GetStorageError, NotAuthorized},
input::GetStorageInput,
output::GetStorageOutput,
PokemonService,
};

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
Expand All @@ -21,20 +30,20 @@ struct Args {

/// Retrieves the user's storage. No authentication required for locals.
pub async fn get_storage_with_local_approved(
input: pokemon_service_server_sdk::input::GetStorageInput,
connect_info: aws_smithy_http_server::Extension<aws_smithy_http_server::routing::ConnectInfo<std::net::SocketAddr>>,
) -> Result<pokemon_service_server_sdk::output::GetStorageOutput, pokemon_service_server_sdk::error::GetStorageError> {
input: GetStorageInput,
connect_info: ConnectInfo<SocketAddr>,
) -> Result<GetStorageOutput, GetStorageError> {
tracing::debug!("attempting to authenticate storage user");
let local = connect_info.0 .0.ip() == "127.0.0.1".parse::<std::net::IpAddr>().unwrap();
let local = connect_info.0.ip() == "127.0.0.1".parse::<IpAddr>().unwrap();

// We currently support Ash: he has nothing stored
if input.user == "ash" && input.passcode == "pikachu123" {
return Ok(pokemon_service_server_sdk::output::GetStorageOutput { collection: vec![] });
return Ok(GetStorageOutput { collection: vec![] });
}
// We support trainers in our gym
if local {
tracing::info!("welcome back");
return Ok(pokemon_service_server_sdk::output::GetStorageOutput {
return Ok(GetStorageOutput {
collection: vec![
String::from("bulbasaur"),
String::from("charmander"),
Expand All @@ -43,16 +52,14 @@ pub async fn get_storage_with_local_approved(
});
}
tracing::debug!("authentication failed");
Err(pokemon_service_server_sdk::error::GetStorageError::NotAuthorized(
pokemon_service_server_sdk::error::NotAuthorized {},
))
Err(GetStorageError::NotAuthorized(NotAuthorized {}))
}

#[tokio::main]
async fn main() {
let args = Args::parse();
setup_tracing();
let app = pokemon_service_server_sdk::service::PokemonService::builder_without_plugins()
let app = PokemonService::builder_without_plugins()
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage_with_local_approved)
.get_server_statistics(get_server_statistics)
Expand Down
82 changes: 4 additions & 78 deletions rust-runtime/aws-smithy-http-server/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

// This code was copied and then modified from Tokio's Axum.

/* Copyright (c) 2021 Tower Contributors
*
* Permission is hereby granted, free of charge, to any
* person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the
* Software without restriction, including without
* limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice
* shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

//! Extension types.
//!
//! Extension types are types that are stored in and extracted from _both_ requests and
Expand All @@ -50,14 +21,12 @@

use std::ops::Deref;

use http::StatusCode;
use thiserror::Error;

use crate::{
body::{empty, BoxBody},
request::{FromParts, RequestParts},
response::IntoResponse,
};
use crate::request::RequestParts;

pub use crate::request::extension::Extension;
pub use crate::request::extension::MissingExtension;

/// Extension type used to store information about Smithy operations in HTTP responses.
/// This extension type is set when it has been correctly determined that the request should be
Expand Down Expand Up @@ -151,49 +120,6 @@ impl Deref for RuntimeErrorExtension {
}
}

/// Generic extension type stored in and extracted from [request extensions].
///
/// This is commonly used to share state across handlers.
///
/// If the extension is missing it will reject the request with a `500 Internal
/// Server Error` response.
///
/// [request extensions]: https://docs.rs/http/latest/http/struct.Extensions.html
#[derive(Debug, Clone)]
pub struct Extension<T>(pub T);

impl<T> Deref for Extension<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// The extension has not been added to the [`Request`](http::Request) or has been previously removed.
#[derive(Debug, Error)]
#[error("the `Extension` is not present in the `http::Request`")]
pub struct MissingExtension;

impl<Protocol> IntoResponse<Protocol> for MissingExtension {
fn into_response(self) -> http::Response<BoxBody> {
let mut response = http::Response::new(empty());
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
response
}
}

impl<Protocol, T> FromParts<Protocol> for Extension<T>
where
T: Send + Sync + 'static,
{
type Rejection = MissingExtension;

fn from_parts(parts: &mut http::request::Parts) -> Result<Self, Self::Rejection> {
parts.extensions.remove::<T>().map(Extension).ok_or(MissingExtension)
}
}

/// Extract an [`Extension`] from a request.
/// This is essentially the implementation of `FromRequest` for `Extension`, but with a
/// protocol-agnostic rejection type. The actual code-generated implementation simply delegates to
Expand Down
2 changes: 1 addition & 1 deletion rust-runtime/aws-smithy-http-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub mod routers;

#[doc(inline)]
pub(crate) use self::error::Error;
pub use self::extension::Extension;
pub use self::request::extension::Extension;
#[doc(inline)]
pub use self::routing::Router;
#[doc(inline)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
* DEALINGS IN THE SOFTWARE.
*/

//! Extractor for getting connection information from a client.

use std::{
convert::Infallible,
fmt,
Expand All @@ -48,12 +50,11 @@ use tower_http::add_extension::{AddExtension, AddExtensionLayer};

use crate::{request::FromParts, Extension};

/// A [`MakeService`] created from a router.
/// A [`MakeService`] used to insert [`ConnectInfo<T>`] into [`http::Request`]s.
///
/// See [`Router::into_make_service_with_connect_info`] for more details.
/// The `T` must be derivable from the underlying IO resource using the [`Connected`] trait.
///
/// [`MakeService`]: tower::make::MakeService
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
pub struct IntoMakeServiceWithConnectInfo<S, C> {
inner: S,
_connect_info: PhantomData<fn() -> C>,
Expand Down Expand Up @@ -96,10 +97,6 @@ where
///
/// The goal for this trait is to allow users to implement custom IO types that
/// can still provide the same connection metadata.
///
/// See [`Router::into_make_service_with_connect_info`] for more details.
///
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
pub trait Connected<T>: Clone {
/// Create type holding information about the connection.
fn connect_info(target: T) -> Self;
Expand Down Expand Up @@ -140,13 +137,9 @@ opaque_future! {

/// Extractor for getting connection information produced by a `Connected`.
///
/// Note this extractor requires you to use
/// [`Router::into_make_service_with_connect_info`] to run your app
/// otherwise it will fail at runtime.
///
/// See [`Router::into_make_service_with_connect_info`] for more details.
///
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
/// Note this extractor requires the existence of [`Extension<ConnectInfo<T>>`] in the [`http::Extensions`]. This is
/// automatically inserted by the [`IntoMakeServiceWithConnectInfo`] middleware, which can be applied using the
/// `into_make_service_with_connect_info` method on your generated service.
#[derive(Clone, Debug)]
pub struct ConnectInfo<T>(pub T);

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

// This code was copied and then modified from Tokio's Axum.

/* Copyright (c) 2021 Tower Contributors
*
* Permission is hereby granted, free of charge, to any
* person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the
* Software without restriction, including without
* limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice
* shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

//! Extension types.
//!
//! Extension types are types that are stored in and extracted from _both_ requests and
//! responses.
//!
//! There is only one _generic_ extension type _for requests_, [`Extension`].
//!
//! On the other hand, the server SDK uses multiple concrete extension types for responses in order
//! to store a variety of information, like the operation that was executed, the operation error
//! that got returned, or the runtime error that happened, among others. The information stored in
//! these types may be useful to [`tower::Layer`]s that post-process the response: for instance, a
//! particular metrics layer implementation might want to emit metrics about the number of times an
//! an operation got executed.
//!
//! [extensions]: https://docs.rs/http/latest/http/struct.Extensions.html

use std::ops::Deref;

use http::StatusCode;
use thiserror::Error;

use crate::{
body::{empty, BoxBody},
request::FromParts,
response::IntoResponse,
};

/// Generic extension type stored in and extracted from [request extensions].
///
/// This is commonly used to share state across handlers.
///
/// If the extension is missing it will reject the request with a `500 Internal
/// Server Error` response.
///
/// [request extensions]: https://docs.rs/http/latest/http/struct.Extensions.html
#[derive(Debug, Clone)]
pub struct Extension<T>(pub T);

impl<T> Deref for Extension<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// The extension has not been added to the [`Request`](http::Request) or has been previously removed.
#[derive(Debug, Error)]
#[error("the `Extension` is not present in the `http::Request`")]
pub struct MissingExtension;

impl<Protocol> IntoResponse<Protocol> for MissingExtension {
fn into_response(self) -> http::Response<BoxBody> {
let mut response = http::Response::new(empty());
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
response
}
}

impl<Protocol, T> FromParts<Protocol> for Extension<T>
where
T: Send + Sync + 'static,
{
type Rejection = MissingExtension;

fn from_parts(parts: &mut http::request::Parts) -> Result<Self, Self::Rejection> {
parts.extensions.remove::<T>().map(Extension).ok_or(MissingExtension)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
* DEALINGS IN THE SOFTWARE.
*/

//! Types and traits for extracting data from requests.
//!
//! See [Accessing Un-modelled data](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/from_parts.md)
//! a comprehensive overview.

use std::{
convert::Infallible,
future::{ready, Future, Ready},
Expand All @@ -45,6 +50,9 @@ use http::{request::Parts, Extensions, HeaderMap, Request, Uri};

use crate::{rejection::any_rejections, response::IntoResponse};

pub mod connect_info;
pub mod extension;

#[doc(hidden)]
#[derive(Debug)]
pub struct RequestParts<B> {
Expand Down
Loading

0 comments on commit 86fd8f5

Please sign in to comment.