Skip to content
This repository has been archived by the owner on Oct 20, 2023. It is now read-only.

Add toy implementation #1

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[workspace]
members = ["generated", "runtime"]
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ Outer layers {customer}

## Proposal

The trait `OperationShape` models Smithy operations.

```rust
trait OperationShape {
type Input;
type Output;
type Error;
}
```

A service builder sets an operation by accepting `Operation<S, L>` where `S: Service<(Op::Input, Exts), Response = Op::Output, Error = PollError | Op::Error>>`, `OperationInput: FromRequest<P, Op>`, `Exts: FromRequest<P, Op>`, `Op::Output: IntoResponse<P, Op>`, and `Op::Error: IntoResponse<P, Op>` for `Op: OperationShape`.

A `Operation` includes two constructors, `from_handler` which accepts a `H: Handler<Op, Exts>` and `from_service` which accepts a `S: Flattened<Op, Exts, PollError>`. The trait `Handler<Op, Ext>` is enjoyed by all closures which accept `(Op::Input, ...)` and return `Result<Op::Input, Op::Error>`. The trait `Flattened<Op, Exts, PollError>` is enjoyed by all `Service<(Op::Input, ...), Response = Op::Output, Error = PollError | Op::Error>`. Both `Handler` and `Flattened` work to provide a common interface to convert to `S: Service<(Op::Input, Exts), Response = Op::Output, Error = PollError | Op::Error>` in `Operation<S, L>`.

The `UpgradeLayer<P, Op, Exts, B>` is a `Layer<S>`, applied to such `S`. It uses the `FromRequest<P, Op>` and `IntoResponse<P, Op>` to wrap `S` in middleware - converting `S: Service<(Op::Input, Exts), Response = Op::Output, Error = PollError | Op::Error>` to `S: Service<http::Request, Response = http::Response, Error = PollError>` in a protocol and operation aware way.

The `Operation<S, L>::upgrade<P, Op, Exts, B>` takes `S`, applies `UpgradeLayer<P, Op, Exts, B>`, then applies the `L: Layer<UpgradeLayer::Service>`. The `L` in `Operation<S, L>` can be set by the user to provide operation specific HTTP middleware. The `Operation::upgrade` is called in the service builder `build` method and the composition is immediately `Box`'d and collected up into the protocol specific router alongside the other routes.

In this way the customer can provide, for a specific operation, middleware around `S` _and_ `S` after it's upgraded to a HTTP service via `L`.

```
Outer layers {customer}
|- Router {runtime + codegen}
Expand Down
17 changes: 17 additions & 0 deletions generated/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "generated"
version = "0.1.0"
edition = "2021"

[dependencies]
runtime = { path = "../runtime" }

futures = "0.3.21"
http = "0.2.8"
http-body = "0.4.5"
hyper = "0.14.20"
tower = "0.4.13"
pin-project-lite = "0.2.9"

[dev-dependencies]
hyper = { version = "0.14.20", features = ["server", "http2", "tcp"] }
86 changes: 86 additions & 0 deletions generated/examples/usage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::{convert::Infallible, future::Ready, task::Poll};

use generated::{operations::*, services::*, structures::*};
use runtime::operation::{Extension, OperationError, OperationShapeExt};
use tower::{util::MapResponseLayer, Service};

// Fallible handler with extensions.
async fn get_pokemon_species_stateful(
_input: GetPokemonSpeciesInput,
_ext_a: Extension<usize>,
_ext_b: Extension<String>,
) -> Result<GetPokemonSpeciesOutput, ResourceNotFoundException> {
todo!()
}

// Fallible handler without extensions.
async fn get_pokemon_species(
_input: GetPokemonSpeciesInput,
) -> Result<GetPokemonSpeciesOutput, ResourceNotFoundException> {
todo!()
}

// Infallible handler without extensions.
async fn empty_operation(_input: EmptyOperationInput) -> EmptyOperationOutput {
todo!()
}

// Bespoke implementation of `EmptyOperation`.
#[derive(Clone)]
struct EmptyOperationServiceA;

impl Service<EmptyOperationInput> for EmptyOperationServiceA {
type Response = EmptyOperationOutput;
type Error = OperationError<String, Infallible>;
type Future = Ready<Result<Self::Response, Self::Error>>;

fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
todo!()
}

fn call(&mut self, _req: EmptyOperationInput) -> Self::Future {
todo!()
}
}

// Bespoke implementation of `EmptyOperation` with an extension.
#[derive(Clone)]
struct EmptyOperationServiceB;

impl Service<(EmptyOperationInput, Extension<String>)> for EmptyOperationServiceB {
type Response = EmptyOperationOutput;
type Error = OperationError<String, Infallible>;
type Future = Ready<Result<Self::Response, Self::Error>>;

fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
todo!()
}

fn call(&mut self, _req: (EmptyOperationInput, Extension<String>)) -> Self::Future {
todo!()
}
}

fn main() {
// Various ways of constructing operations
let _get_pokemon_species = GetPokemonSpecies::from_handler(get_pokemon_species);
let _empty_operation = EmptyOperation::from_handler(empty_operation);
let _empty_operation = EmptyOperation::from_service(EmptyOperationServiceA);
let empty_operation = EmptyOperation::from_service(EmptyOperationServiceB);
let get_pokemon_species = GetPokemonSpecies::from_handler(get_pokemon_species_stateful);

// We can apply a layer to them
let get_pokemon_species = get_pokemon_species.layer(MapResponseLayer::new(|resp| resp));

// We can build the `PokemonService` with static type checking
let pokemon_service = PokemonService::builder()
.get_pokemon_species(get_pokemon_species)
.empty_operation(empty_operation)
.build();

// We can apply a layer to all routes
let pokemon_service = pokemon_service.layer(MapResponseLayer::new(|resp| resp));

let addr = "localhost:8000".parse().unwrap();
hyper::server::Server::bind(&addr).serve(pokemon_service.into_make_service());
}
3 changes: 3 additions & 0 deletions generated/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod operations;
pub mod services;
pub mod structures;
44 changes: 44 additions & 0 deletions generated/src/operations/empty_operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::{convert::Infallible, future::Ready};

use http_body::combinators::BoxBody;
use hyper::body::Bytes;
use runtime::{
operation::{FromRequest, IntoResponse, OperationShape},
protocols::AWSRestJsonV1,
};

use crate::structures::{EmptyOperationInput, EmptyOperationOutput};

pub struct EmptyOperation;

impl OperationShape for EmptyOperation {
const NAME: &'static str = "EmptyOperation";

type Input = EmptyOperationInput;
type Output = EmptyOperationOutput;
type Error = Infallible;
}

pub struct FromRequestError;

impl IntoResponse<AWSRestJsonV1, EmptyOperation> for FromRequestError {
fn into_response(self) -> http::Response<BoxBody<Bytes, hyper::Error>> {
todo!()
}
}

impl<B> FromRequest<AWSRestJsonV1, EmptyOperation, B> for EmptyOperationInput {
type Error = FromRequestError;

type Future = Ready<Result<Self, Self::Error>>;

fn from_request(_request: &mut http::Request<B>) -> Self::Future {
todo!()
}
}

impl IntoResponse<AWSRestJsonV1, EmptyOperation> for EmptyOperationOutput {
fn into_response(self) -> http::Response<BoxBody<Bytes, hyper::Error>> {
todo!()
}
}
52 changes: 52 additions & 0 deletions generated/src/operations/get_pokemon_species.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::future::Ready;

use http_body::combinators::BoxBody;
use hyper::body::Bytes;
use runtime::{
operation::{FromRequest, IntoResponse, OperationShape},
protocols::AWSRestJsonV1,
};

use crate::structures::{
GetPokemonSpeciesInput, GetPokemonSpeciesOutput, ResourceNotFoundException,
};

pub struct GetPokemonSpecies;

impl OperationShape for GetPokemonSpecies {
const NAME: &'static str = "GetPokemonSpecies";

type Input = GetPokemonSpeciesInput;
type Output = GetPokemonSpeciesOutput;
type Error = ResourceNotFoundException;
}

pub struct FromRequestError;

impl IntoResponse<AWSRestJsonV1, GetPokemonSpecies> for FromRequestError {
fn into_response(self) -> http::Response<BoxBody<Bytes, hyper::Error>> {
todo!()
}
}

impl<B> FromRequest<AWSRestJsonV1, GetPokemonSpecies, B> for GetPokemonSpeciesInput {
type Error = FromRequestError;

type Future = Ready<Result<Self, Self::Error>>;

fn from_request(_request: &mut http::Request<B>) -> Self::Future {
todo!()
}
}

impl IntoResponse<AWSRestJsonV1, GetPokemonSpecies> for GetPokemonSpeciesOutput {
fn into_response(self) -> http::Response<BoxBody<Bytes, hyper::Error>> {
todo!()
}
}

impl IntoResponse<AWSRestJsonV1, GetPokemonSpecies> for ResourceNotFoundException {
fn into_response(self) -> http::Response<BoxBody<Bytes, hyper::Error>> {
todo!()
}
}
5 changes: 5 additions & 0 deletions generated/src/operations/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod empty_operation;
pub mod get_pokemon_species;

pub use empty_operation::EmptyOperation;
pub use get_pokemon_species::GetPokemonSpecies;
3 changes: 3 additions & 0 deletions generated/src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod pokemon_service;

pub use pokemon_service::*;
Loading