Skip to content

Commit

Permalink
Add new service builder machinery (#1679)
Browse files Browse the repository at this point in the history
* Add protocol specific `FromRequest` and `FromParts`.

* Add `OperationShape` trait to model Smithy operations.

* Add `Handler` and `OperationService` traits.

* Add `Upgrade` `Service` and `UpgradeLayer` `Layer`.
  • Loading branch information
hlbarber authored Aug 31, 2022
1 parent 2cb7664 commit 66f96a5
Show file tree
Hide file tree
Showing 9 changed files with 936 additions and 3 deletions.
31 changes: 30 additions & 1 deletion rust-runtime/aws-smithy-http-server/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,14 @@
use std::ops::Deref;

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

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

/// 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 @@ -165,6 +170,30 @@ impl<T> Deref for Extension<T> {
}
}

/// 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: Clone + 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: 2 additions & 0 deletions rust-runtime/aws-smithy-http-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub mod extension;
#[doc(hidden)]
pub mod logging;
#[doc(hidden)]
pub mod operation;
#[doc(hidden)]
pub mod protocols;
#[doc(hidden)]
pub mod rejection;
Expand Down
153 changes: 153 additions & 0 deletions rust-runtime/aws-smithy-http-server/src/operation/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use std::{
convert::Infallible,
future::Future,
marker::PhantomData,
task::{Context, Poll},
};

use futures_util::{
future::{Map, MapErr},
FutureExt, TryFutureExt,
};
use tower::Service;

use super::{OperationError, OperationShape};

/// A utility trait used to provide an even interface for all operation handlers.
pub trait Handler<Op, Exts>
where
Op: OperationShape,
{
type Future: Future<Output = Result<Op::Output, Op::Error>>;

fn call(&mut self, input: Op::Input, exts: Exts) -> Self::Future;
}

/// A utility trait used to provide an even interface over return types `Result<Ok, Error>`/`Ok`.
trait IntoResult<Ok, Error> {
fn into_result(self) -> Result<Ok, Error>;
}

// We can convert from `Result<Ok, Error>` to `Result<Ok, Error>`.
impl<Ok, Error> IntoResult<Ok, Error> for Result<Ok, Error> {
fn into_result(self) -> Result<Ok, Error> {
self
}
}

// We can convert from `T` to `Result<T, Infallible>`.
impl<Ok> IntoResult<Ok, Infallible> for Ok {
fn into_result(self) -> Result<Ok, Infallible> {
Ok(self)
}
}

// fn(Input) -> Output
impl<Op, F, Fut> Handler<Op, ()> for F
where
Op: OperationShape,
F: Fn(Op::Input) -> Fut,
Fut: Future,
Fut::Output: IntoResult<Op::Output, Op::Error>,
{
type Future = Map<Fut, fn(Fut::Output) -> Result<Op::Output, Op::Error>>;

fn call(&mut self, input: Op::Input, _exts: ()) -> Self::Future {
(self)(input).map(IntoResult::into_result)
}
}

// fn(Input, Ext0) -> Output
impl<Op, F, Fut, Ext0> Handler<Op, (Ext0,)> for F
where
Op: OperationShape,
F: Fn(Op::Input, Ext0) -> Fut,
Fut: Future,
Fut::Output: IntoResult<Op::Output, Op::Error>,
{
type Future = Map<Fut, fn(Fut::Output) -> Result<Op::Output, Op::Error>>;

fn call(&mut self, input: Op::Input, exts: (Ext0,)) -> Self::Future {
(self)(input, exts.0).map(IntoResult::into_result)
}
}

// fn(Input, Ext0, Ext1) -> Output
impl<Op, F, Fut, Ext0, Ext1> Handler<Op, (Ext0, Ext1)> for F
where
Op: OperationShape,
F: Fn(Op::Input, Ext0, Ext1) -> Fut,
Fut: Future,
Fut::Output: IntoResult<Op::Output, Op::Error>,
{
type Future = Map<Fut, fn(Fut::Output) -> Result<Op::Output, Op::Error>>;

fn call(&mut self, input: Op::Input, exts: (Ext0, Ext1)) -> Self::Future {
(self)(input, exts.0, exts.1).map(IntoResult::into_result)
}
}

/// An extension trait for [`Handler`].
pub trait HandlerExt<Op, Exts>: Handler<Op, Exts>
where
Op: OperationShape,
{
/// Convert the [`Handler`] into a [`Service`].
fn into_service(self) -> IntoService<Op, Self>
where
Self: Sized,
{
IntoService {
handler: self,
_operation: PhantomData,
}
}
}

impl<Op, Exts, H> HandlerExt<Op, Exts> for H
where
Op: OperationShape,
H: Handler<Op, Exts>,
{
}

/// A [`Service`] provided for every [`Handler`].
pub struct IntoService<Op, H> {
handler: H,
_operation: PhantomData<Op>,
}

impl<Op, H> Clone for IntoService<Op, H>
where
H: Clone,
{
fn clone(&self) -> Self {
Self {
handler: self.handler.clone(),
_operation: PhantomData,
}
}
}

impl<Op, Exts, H> Service<(Op::Input, Exts)> for IntoService<Op, H>
where
Op: OperationShape,
H: Handler<Op, Exts>,
{
type Response = Op::Output;
type Error = OperationError<Op::Error, Infallible>;
type Future = MapErr<H::Future, fn(Op::Error) -> Self::Error>;

fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&mut self, (input, exts): (Op::Input, Exts)) -> Self::Future {
self.handler.call(input, exts).map_err(OperationError::Model)
}
}
Loading

0 comments on commit 66f96a5

Please sign in to comment.