Expand description

The shape of a Smithy operation is modelled by the OperationShape trait. Its associated types OperationShape::Input, OperationShape::Output, and OperationShape::Error map to the structures representing the Smithy inputs, outputs, and errors respectively. When an operation error is not specified OperationShape::Error is Infallible.

We generate a marker struct for each Smithy operation and implement OperationShape on them. This is used as a helper - providing static methods and parameterizing other traits.

The model

operation GetShopping {
   input: CartIdentifier,
   output: ShoppingCart,
   errors: [...]
}

is identified with the implementation

pub struct GetShopping;

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

    type Input = CartIdentifier;
    type Output = ShoppingCart;
    type Error = GetShoppingError;
}

The behavior of a Smithy operation is encoded by an Operation. The OperationShape types can be used to construct specific operations using OperationShapeExt::from_handler and OperationShapeExt::from_service. The from_handler constructor takes a Handler whereas the from_service takes a OperationService. Both traits serve a similar purpose - they provide a common interface over a class of structures.

Handler

The Handler trait is implemented by all async functions which accept OperationShape::Input as their first argument, the remaining arguments implement FromParts, and return either OperationShape::Output when OperationShape::Error is Infallible or Result<OperationShape::Output,OperationShape::Error>. The following are examples of async functions which implement Handler:

// Simple handler where no error is modelled.
async fn handler_a(input: CartIdentifier) -> ShoppingCart {
    todo!()
}

// Handler with an extension where no error is modelled.
async fn handler_b(input: CartIdentifier, ext: Extension<Context>) -> ShoppingCart {
    todo!()
}

// More than one extension can be provided.
async fn handler_c(input: CartIdentifier, ext_1: Extension<Context>, ext_2: Extension<ExtraContext>) -> ShoppingCart {
    todo!()
}

// When an error is modelled we must return a `Result`.
async fn handler_d(input: CartIdentifier, ext: Extension<Context>) -> Result<ShoppingCart, GetShoppingError> {
    todo!()
}

OperationService

Similarly, the OperationService trait is implemented by all Service<(Op::Input, ...)> with Response = Op::Output, and Error = OperationError<Op::Error, PollError>.

We use OperationError, with a PollError not constrained by the model, to allow the user to provide a custom Service::poll_ready implementation.

The following are examples of Services which implement OperationService:

  • Service<CartIdentifier, Response = ShoppingCart, Error = OperationError<Infallible, Infallible>>.
  • Service<(CartIdentifier, Extension<Context>), Response = ShoppingCart, Error = OperationError<GetShoppingCartError, Infallible>>.
  • Service<(CartIdentifier, Extension<Context>, Extension<ExtraContext>), Response = ShoppingCart, Error = OperationError<GetShoppingCartError, PollError>).

Notice the parallels between OperationService and Handler.

Constructing an Operation

The following is an example of using both construction approaches:

// Construction of an `Operation` from a `Handler`.

async fn op_handler(input: CartIdentifier) -> Result<ShoppingCart, GetShoppingError> {
    todo!()
}

let operation = GetShopping::from_handler(op_handler);

// Construction of an `Operation` from a `Service`.

pub struct PollError;

pub struct OpService;

impl Service<CartIdentifier> for OpService {
    type Response = ShoppingCart;
    type Error = OperationError<GetShoppingError, PollError>;
    type Future = OpFuture;

    fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
        // NOTE: This MUST NOT return `Err(OperationError::Model(_))`.
        todo!()
    }

    fn call(&mut self, request: CartIdentifier) -> Self::Future {
        // NOTE: This MUST NOT return `Err(OperationError::Poll(_))`.
        todo!()
    }
}

let operation = GetShopping::from_service(OpService);

Upgrading Smithy services to HTTP services

Both Handler and OperationService accept and return Smithy model structures. After an Operation is constructed they are converted to a canonical form Service<(Op::Input, Exts), Response = Op::Output, Error = OperationError<Op::Error, PollError>>. The UpgradeLayer acts upon such services by converting them to Service<http::Request, Response = http::Response, Error = PollError>.

Note that the PollError is still exposed, for two reasons:

  • Smithy is agnostic to PollError and therefore we have no prescribed way to serialize it to a http::Response , unlike the operation errors.
  • The intention of PollError is to signal that the underlying service is no longer able to take requests, so should be discarded. See Service::poll_ready.

The UpgradeLayer and it’s Layer::Service Upgrade are both parameterized by a protocol. This allows for upgrading to Service<http::Request, Response = http::Response, Error = PollError> to be protocol dependent.

The Operation::upgrade will apply UpgradeLayer to S then apply the Layer L. The service builder provided to the user will perform this composition on build.

Structs

Enums

Traits