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

Add new service builder machinery #1679

Merged
merged 10 commits into from
Aug 31, 2022
32 changes: 31 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,31 @@ 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> {
http::Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(empty())
.expect("invalid HTTP response for missing extensions; please file a bug report under https://github.com/awslabs/smithy-rs/issues")
hlbarber marked this conversation as resolved.
Show resolved Hide resolved
}
}

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

use std::{
convert::Infallible,
future::{ready, Ready},
task::{Context, Poll},
};

use tower::Service;

/// A basic [`MakeService`](tower::MakeService) which [`Clone`]s the inner service.
hlbarber marked this conversation as resolved.
Show resolved Hide resolved
pub struct IntoMakeService<S> {
svc: S,
}

impl<S> IntoMakeService<S> {
/// Constructs a new [`IntoMakeService`].
pub fn new(svc: S) -> Self {
Self { svc }
}
}

impl<S, T> Service<T> for IntoMakeService<S>
where
S: Clone,
{
type Response = S;
type Error = Infallible;
type Future = Ready<Result<S, Infallible>>;

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

fn call(&mut self, _target: T) -> Self::Future {
ready(Ok(self.svc.clone()))
}
}
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 handlers.
hlbarber marked this conversation as resolved.
Show resolved Hide resolved
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 ToResult<Ok, Error> {
hlbarber marked this conversation as resolved.
Show resolved Hide resolved
fn into_result(self) -> Result<Ok, Error>;
}

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

// We can convert from `Ok` to `Result<Ok, Error>`.
hlbarber marked this conversation as resolved.
Show resolved Hide resolved
impl<Ok> ToResult<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: ToResult<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(ToResult::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: ToResult<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(ToResult::into_result)
}
}

// fn(Input, Ext0, Ext1) -> Output
impl<Op, F, Fut, Ext0, Ext1> Handler<Op, (Ext0, Ext1)> for F
weihanglo marked this conversation as resolved.
Show resolved Hide resolved
where
Op: OperationShape,
F: Fn(Op::Input, Ext0, Ext1) -> Fut,
Fut: Future,
Fut::Output: ToResult<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(ToResult::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