Add CI to the book (#2027)

## Motivation and Context

Closes https://github.com/awslabs/smithy-rs/issues/2004

## Description

Run `mdbook test` over the `design` folder.

## TODO

- [x] Ignore the RFC sections using `ignore` tag on the code blocks.
- [ ] Fix the remaining examples.
- [x] Ensure local `rust-runtime` dependencies are being used.

---------

Signed-off-by: Daniele Ahmed <ahmeddan@amazon.de>
Co-authored-by: 82marbag <69267416+82marbag@users.noreply.github.com>
This commit is contained in:
Harry Barber 2023-06-07 10:01:16 +01:00 committed by GitHub
parent 3285c43b42
commit 29a900e74d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 396 additions and 587 deletions

View File

@ -111,6 +111,8 @@ jobs:
runner: ubuntu-latest
- action: check-style-and-lints
runner: ubuntu-latest
- action: check-book
runner: ubuntu-latest
- action: check-tools
runner: smithy_ubuntu-latest_8-core
- action: check-deterministic-codegen

4
ci.mk
View File

@ -52,6 +52,10 @@ check-aws-sdk-smoketest-unit-tests: generate-aws-sdk-smoketest
check-aws-sdk-standalone-integration-tests: generate-aws-sdk-smoketest
$(CI_ACTION) $@ $(ARGS)
.PHONY: check-book
check-book: check-rust-runtimes
$(CI_ACTION) $@ $(ARGS)
.PHONY: check-client-codegen-integration-tests
check-client-codegen-integration-tests:
$(CI_ACTION) $@ $(ARGS)

View File

@ -1,9 +1,12 @@
[book]
title = "Smithy Rust"
authors = ["Russell Cohen", "aws-sdk-rust@amazon.com"]
language = "en"
multilingual = false
src = "src"
title = "AWS Rust SDK Design"
[rust]
edition = "2021"
[preprocessor.mermaid]
command = "mdbook-mermaid"

View File

@ -24,7 +24,6 @@
- [Accessing Un-modelled Data](./server/from_parts.md)
- [The Anatomy of a Service](./server/anatomy.md)
- [Generating Common Service Code](./server/code_generation.md)
- [Generating the Pokémon Service](./server/pokemon_service.md)
- [RFCs](./rfcs/overview.md)
- [RFC-0001: Sharing configuration between multiple clients](./rfcs/rfc0001_shared_config.md)

View File

@ -85,7 +85,7 @@ unmaintainable levels if each configurable implementation in it was made generic
These traits look like this:
```rust
```rust,ignore
#[derive(Clone, Debug)]
pub struct HttpAuthOption {
scheme_id: &'static str,
@ -123,7 +123,7 @@ will need to understand what the concrete data type underlying that identity is.
uses a `Arc<dyn Any>` to represent the actual identity data so that generics are not needed in
the traits:
```rust
```rust,ignore
#[derive(Clone, Debug)]
pub struct Identity {
data: Arc<dyn Any + Send + Sync>,
@ -136,7 +136,7 @@ rather than `Box`. This also reduces the allocations required. The signer implem
will use downcasting to access the identity data types they understand. For example, with AWS SigV4,
it might look like the following:
```rust
```rust,ignore
fn sign_request(
&self,
request: &mut HttpRequest,
@ -162,7 +162,7 @@ to verify that that type is that trait is lost at compile time (a `std::any::Typ
about the concrete type).
In an ideal world, it would be possible to extract the expiration like this:
```rust
```rust,ignore
pub trait ExpiringIdentity {
fn expiration(&self) -> SystemTime;
}

View File

@ -90,7 +90,7 @@ In designing the orchestrator, we sought to solve the problems we had with the o
*The type signatures for the old client and its `call` method:*
```rust
```rust,ignore
impl<C, M, R> Client<C, M, R>
where
C: bounds::SmithyConnector,
@ -136,7 +136,7 @@ where
*The type signature for the new `orchestrate` method:*
```rust
```rust,ignore
pub async fn orchestrate(
input: Input,
runtime_plugins: &RuntimePlugins,
@ -153,7 +153,7 @@ I'm glad you asked. Generally, when you need traits, but you aren't willing to u
So, what are `Input` and `Output`? They're our own special flavor of a boxed trait object.
```rust
```rust,ignore
pub type Input = TypeErasedBox;
pub type Output = TypeErasedBox;
pub type Error = TypeErasedBox;

View File

@ -25,7 +25,7 @@ checksum and attaching it either as a header or a trailer.)
Here's [an example from the QLDB SDK of creating a body] from inputs and inserting it into the request to be sent:
```rust
```rust,ignore
let body = aws_smithy_http::body::SdkBody::from(
crate::operation_ser::serialize_operation_crate_operation_send_command(&self)?,
);
@ -52,7 +52,7 @@ body until you've sent the request. Any metadata that needs to be calculated by
trailers. Additionally, some metadata, like `Content-Length`, can't be sent as a trailer at all.
[MDN maintains a helpful list] of metadata that can only be sent as a header.
```rust
```rust,ignore
// When trailers are set, we must send an AWS-specific header that lists them named `x-amz-trailer`.
// For example, when sending a SHA256 checksum as a trailer,
// we have to send an `x-amz-trailer` header telling the service to watch out for it:
@ -73,7 +73,7 @@ a request body for `aws-chunked` requires us to know the length of each chunk we
have to prefix each chunk with its size in bytes, represented by one or more [hexadecimal] digits. To close the body, we
send a final chunk with a zero. For example, the body "Hello world" would look like this when encoded:
```
```text
B\r\n
Hello world\r\n
0\r\n
@ -120,7 +120,7 @@ When using `aws-chunked` encoding, the trailers have to be appended to the body
relying on the `poll_trailers` method. The working `http_body::Body` implementation of an `aws-chunked` encoded body
looked like this:
```rust
```rust,ignore
impl Body for AwsChunkedBody<Inner> {
type Data = Bytes;
type Error = aws_smithy_http::body::Error;
@ -222,14 +222,14 @@ been read.
be visible when printing with the `Debug` impl. Case in point was an error I was getting because of the
`is_end_stream` issue. When `Debug` printed, the error looked like this:
```
```rust,ignore
DispatchFailure(ConnectorError { err: hyper::Error(User(Body), hyper::Error(BodyWriteAborted)), kind: User })
```
That wasn't too helpful for me on its own. I looked into the `hyper` source code and found that the `Display` impl
contained a helpful message, so I matched into the error and printed the `hyper::Error` with the `Display` impl:
```
```markdown
user body write aborted: early end, expected 2 more bytes'
```
@ -239,7 +239,7 @@ been read.
being sent out by the SDK as I was working on it. The Rust SDK supports setting endpoints for request. This is often
used to send requests to something like [LocalStack], but I used it to send request to `localhost` instead:
```rust
```rust,ignore
#[tokio::test]
async fn test_checksum_on_streaming_request_against_s3() {
let sdk_config = aws_config::from_env()
@ -262,7 +262,7 @@ been read.
The echo server was based off of an [axum] example and looked like this:
```rust
```rust,ignore
use axum::{
body::{Body, Bytes},
http::{request::Parts, Request, StatusCode},

View File

@ -13,7 +13,7 @@ The design builds on the learnings, ideas, hard work, and GitHub issues of the 1
The Rust SDK is "modular" meaning that each AWS service is its own crate. Each crate provides two layers to access the service:
1. The "fluent" API. For most use cases, a high level API that ties together connection management and serialization will be the quickest path to success.
```rust
```rust,ignore
#[tokio::main]
async fn main() {
let client = dynamodb::Client::from_env();
@ -27,7 +27,7 @@ async fn main() {
2. The "low-level" API: It is also possible for customers to assemble the pieces themselves. This offers more control over operation construction & dispatch semantics:
```rust
```rust,ignore
#[tokio::main]
async fn main() {
let conf = dynamodb::Config::builder().build();

View File

@ -37,7 +37,7 @@ tokio = { version = "1", features = ["full"] }
Let's write a small example project to list tables:
```rust
```rust,ignore
use aws_sdk_dynamodb as dynamodb;
#[tokio::main]
@ -58,7 +58,7 @@ Next, we'll explore some other ways to configure the SDK. Perhaps you want to ov
environment with your region. In this case, we'll want more control over how we load config,
using `aws_config::from_env()` directly:
```rust
```rust,ignore
use aws_sdk_dynamodb as dynamodb;
#[tokio::main]
@ -88,7 +88,7 @@ tokio = { version = "1", features = ["full"] }
Then, we can use the shared configuration to build both service clients. The region override will apply to both clients:
```rust
```rust,ignore
use aws_sdk_dynamodb as dynamodb;
use aws_sdk_polly as polly;
@ -135,7 +135,7 @@ To do this, implement the `ProvideCredentials` trait.
> NOTE: `aws_types::Credentials` already implements `ProvideCredentials`. If you want to use the SDK with static credentials, you're already done!
```rust
```rust,ignore
use aws_types::credentials::{ProvideCredentials, provide_credentials::future, Result};
struct MyCustomProvider;
@ -160,7 +160,7 @@ impl ProvideCredentials for MyCustomProvider {
After writing your custom provider, you'll use it in when constructing the configuration:
```rust
```rust,ignore
#[tokio::main]
async fn main() {
let config = aws_config::from_env().credentials_provider(MyCustomProvider).load().await;
@ -192,7 +192,7 @@ however, they won't have any default resolvers built in. Each AWS config will im
This RFC proposes adding region and credentials providers support to the shared config. A future RFC will propose
integration with HTTP settings, HTTPs connectors, and async sleep.
```rust
```rust,ignore
struct Config {
// private fields
...
@ -239,7 +239,7 @@ uses `Config` that is incompatible, they will get confusing compiler errors.
An example of a problematic set of dependent versions:
```
```markdown
┌─────────────────┐ ┌───────────────┐
│ aws-types = 0.1 │ │aws-types= 0.2 │
└─────────────────┘ └───────────────┘

View File

@ -45,7 +45,7 @@ around the underlying connector. When constructing operation builders, this hand
given to the new builder instances so that their `send()` calls can initiate a request.
The generated fluent client code ends up looking like this:
```rust
```rust,ignore
struct Handle<C, M, R> {
client: aws_smithy_client::Client<C, M, R>,
conf: crate::Config,
@ -59,7 +59,7 @@ pub struct Client<C, M, R = Standard> {
Functions are generated per operation on the fluent client to gain access to the individual operation builders.
For example:
```rust
```rust,ignore
pub fn assume_role(&self) -> fluent_builders::AssumeRole<C, M, R> {
fluent_builders::AssumeRole::new(self.handle.clone())
}
@ -68,7 +68,7 @@ pub fn assume_role(&self) -> fluent_builders::AssumeRole<C, M, R> {
The fluent operation builders ultimately implement `send()`, which chooses the one and only Smithy client out
of the handle to make the request with:
```rust
```rust,ignore
pub struct AssumeRole<C, M, R> {
handle: std::sync::Arc<super::Handle<C, M, R>>,
inner: crate::input::assume_role_input::Builder,
@ -86,7 +86,7 @@ impl<C, M, R> AssumeRole<C, M, R> where ...{
Smithy clients are constructed from a connector, as shown:
```rust
```rust,ignore
let connector = Builder::new()
.https()
.middleware(...)
@ -117,7 +117,7 @@ so that alternate HTTP implementations can be used, or so that a fake implementa
To accomplish this, `SharedConfig` will have a `make_connector` member. A customer would configure
it as such:
```rust
```rust,ignore
let config = some_shared_config_loader()
.with_http_settings(my_http_settings)
.with_make_connector(|reqs: &MakeConnectorRequirements| {
@ -170,7 +170,7 @@ Smithy client. This cache needs to be adjusted to:
To accomplish this, the `Handle` will hold a cache that is optimized for many reads and few writes:
```rust
```rust,ignore
#[derive(Debug, Hash, Eq, PartialEq)]
struct ConnectorKey {
http_settings: HttpSettings,
@ -194,7 +194,7 @@ For cases where it is not, the custom connector type can host its own `dyn Trait
The `HttpRequirements` struct will hold `HttpSettings` as copy-on-write so that it can be used
for cache lookup without having to clone `HttpSettings`:
```rust
```rust,ignore
struct HttpRequirements<'a> {
http_settings: Cow<'a, HttpSettings>,
http_version: HttpVersion,
@ -223,7 +223,7 @@ HTTP setting overrides are implemented. This doc is not attempting to solve that
In the fluent client, this will look as follows:
```rust
```rust,ignore
impl<C, M, R> AssumeRole<C, M, R> where ... {
pub async fn send(self) -> Result<AssumeRoleOutput, SdkError<AssumeRoleError>> where ... {
let input = self.create_input()?;

View File

@ -37,7 +37,7 @@ default to `SystemTime::now()`.
Construction `PresigningConfig` can be done with a builder, but a `PresigningConfig::expires_in`
convenience function will be provided to bypass the builder for the most frequent use-case.
```rust
```rust,ignore
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct PresigningConfig {
@ -85,7 +85,7 @@ The generated fluent builders for operations that support presigning will have a
in addition to `send()` that will return a presigned URL rather than sending the request. For S3's GetObject,
the usage of this will look as follows:
```rust
```rust,ignore
let config = aws_config::load_config_from_environment().await;
let client = s3::Client::new(&config);
let presigning_config = PresigningConfig::expires_in(Duration::from_secs(86400));
@ -123,7 +123,7 @@ Even though generating a presigned URL through the fluent client doesn't necessi
it will be clearer that this is the case by allowing the creation of presigned URLs directly from an input.
This would look as follows:
```rust
```rust,ignore
let config = aws_config::load_config_from_environment().await;
let presigning_config = PresigningConfig::expires_in(Duration::from_secs(86400));
let presigned: PresignedRequest = GetObjectInput::builder()

View File

@ -39,7 +39,7 @@ _The default number of retries is 3 as specified in the [AWS SDKs and Tools Refe
Here's an example app that logs your AWS user's identity
```rust
```rust,ignore
use aws_sdk_sts as sts;
#[tokio::main]
@ -66,7 +66,7 @@ cargo run
Here's an example app that creates a shared config with custom retry behavior and then logs your AWS user's identity
```rust
```rust,ignore
use aws_sdk_sts as sts;
use aws_types::retry_config::StandardRetryConfig;
@ -86,7 +86,7 @@ async fn main() -> Result<(), sts::Error> {
Here's an example app that creates a service-specific config with custom retry behavior and then logs your AWS user's identity
```rust
```rust,ignore
use aws_sdk_sts as sts;
use aws_types::retry_config::StandardRetryConfig;
@ -107,7 +107,7 @@ async fn main() -> Result<(), sts::Error> {
Here's an example app that creates a shared config that disables retries and then logs your AWS user's identity
```rust
```rust,ignore
use aws_sdk_sts as sts;
use aws_types::config::Config;
@ -125,7 +125,7 @@ async fn main() -> Result<(), sts::Error> {
Retries can also be disabled by explicitly passing the `RetryConfig::NoRetries` enum variant to the `retry_config` builder method:
```rust
```rust,ignore
use aws_sdk_sts as sts;
use aws_types::retry_config::RetryConfig;

View File

@ -23,7 +23,7 @@ original input, but with the field marked `inputToken` to the value of `outputTo
Usage example:
```rust
```rust,ignore
let paginator = client
.list_tables()
.paginate()
@ -41,7 +41,7 @@ Paginators are lazy and only retrieve pages when polled by a client.
Paginators will be generated into the `paginator` module of service crates. Currently, paginators are _not_ feature gated, but this
could be considered in the future. A `paginator` struct captures 2 pieces of data:
```rust
```rust,ignore
// dynamodb/src/paginator.rs
struct ListTablesPaginator<C, M, R> {
// holds the low-level client and configuration
@ -55,7 +55,7 @@ struct ListTablesPaginator<C, M, R> {
In addition to the basic usage example above, when `pageSize` is modeled, customers can specify the page size during
pagination:
```rust
```rust,ignore
let mut tables = vec![];
let mut pages = client
.list_tables()
@ -75,7 +75,7 @@ enables demand driven execution of a closure. A rendezvous channel is used which
When modeled by Smithy, `page_size` which automatically sets the appropriate page_size parameter and `items()` which returns an
automatically flattened paginator are also generated. **Note**: `page_size` directly sets the modeled parameter on the internal builder.
This means that a value set for page size will override any previously set value for that field.
```rust
```rust,ignore
// Generated paginator for ListTables
impl<C, M, R> ListTablesPaginator<C, M, R>
{
@ -166,7 +166,7 @@ regressions in the generated code which would break users.
The `builders` generated by ergonomic clients will gain the following method, if they represent an operation that implements the `Paginated` trait:
```rust
```rust,ignore
/// Create a paginator for this request
///
/// Paginators are used by calling [`send().await`](crate::paginator::ListTablesPaginator::send) which returns a [`Stream`](tokio_stream::Stream).

View File

@ -10,7 +10,7 @@ than building out an entire polling mechanism manually.
At the highest level, a waiter is a simple polling loop (pseudo-Rust):
```rust
```rust,ignore
// Track state that contains the number of attempts made and the previous delay
let mut state = initial_state();
@ -74,7 +74,7 @@ Waiter API
To invoke a waiter, customers will only need to invoke a single function on the AWS Client. For example,
if waiting for a S3 bucket to exist, it would look like the following:
```rust
```rust,ignore
// Request bucket creation
client.create_bucket()
.bucket_name("my-bucket")
@ -93,7 +93,7 @@ that will start the polling and return a future.
To avoid name conflicts with other API methods, the waiter functions can be added to the client via trait:
```rust
```rust,ignore
pub trait WaitUntilBucketExists {
fn wait_until_bucket_exists(&self) -> crate::waiter::bucket_exists::Builder;
}
@ -107,7 +107,7 @@ Waiter Implementation
A waiter trait implementation will merely return a fluent builder:
```rust
```rust,ignore
impl WaitUntilBucketExists for Client {
fn wait_until_bucket_exists(&self) -> crate::waiter::bucket_exists::Builder {
crate::waiter::bucket_exists::Builder::new()
@ -117,7 +117,7 @@ impl WaitUntilBucketExists for Client {
This builder will have a short `send()` function to kick off the actual waiter implementation:
```rust
```rust,ignore
impl Builder {
// ... existing fluent builder codegen can be reused to create all the setters and constructor
@ -134,7 +134,7 @@ This wait function needs to, in a loop similar to the pseudo-code in the beginni
convert the given input into an operation, replace the default response classifier on it
with a no-retry classifier, and then determine what to do next based on that classification:
```rust
```rust,ignore
pub async fn wait(
handle: Arc<Handle<DynConnector, DynMiddleware<DynConnector>, retry::Standard>>,
input: HeadBucketInput,

View File

@ -9,7 +9,7 @@ Adding a callback API to `ByteStream` and `SdkBody` will enable developers using
*Note that comments starting with '//' are not necessarily going to be included in the actual implementation and are intended as clarifying comments for the purposes of this RFC.*
```rust
```rust,ignore
// in aws_smithy_http::callbacks...
/// A callback that, when inserted into a request body, will be called for corresponding lifecycle events.
@ -41,7 +41,7 @@ The changes we need to make to `ByteStream`:
*(The current version of `ByteStream` and `Inner` can be seen [here][ByteStream impls].)*
```rust
```rust,ignore
// in `aws_smithy_http::byte_stream`...
// We add a new method to `ByteStream` for inserting callbacks
@ -68,7 +68,7 @@ The changes we need to make to `SdkBody`:
*(The current version of `SdkBody` can be seen [here][SdkBody impls].)*
```rust
```rust,ignore
// In aws_smithy_http::body...
#[pin_project]
@ -243,7 +243,7 @@ What follows is a simplified example of how this API could be used to introduce
the checksum of some data and then returns the checksum of that data when `trailers` is called. This is fine because it's
being used to calculate the checksum of a streaming body for a request.
```rust
```rust,ignore
#[derive(Default)]
struct Crc32cChecksumCallback {
state: Option<u32>,

View File

@ -99,7 +99,7 @@ Timeouts are achieved by racing a future against a `tokio::time::Sleep` future.
_View [AwsMiddleware] in GitHub_
```rust
```rust,ignore
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct AwsMiddleware;
@ -127,7 +127,7 @@ The above code is only included for context. This RFC doesn't define any timeout
_View [aws_smithy_client::Client::call_raw] in GitHub_
```rust
```rust,ignore
impl<C, M, R> Client<C, M, R>
where
C: bounds::SmithyConnector,
@ -175,7 +175,7 @@ The **HTTP Request Timeout For A Single Attempt** and **HTTP Request Timeout For
The resulting code would look like this:
```rust
```rust,ignore
impl<C, M, R> Client<C, M, R>
where
C: bounds::SmithyConnector,

View File

@ -94,7 +94,7 @@ Conditionally compiling code when a feature is **not** activated can make it har
One case where using `not` is acceptable is when providing a fallback when no features are set:
```rust
```rust,ignore
#[cfg(feature = "rt-tokio")]
pub fn default_async_sleep() -> Option<Arc<dyn AsyncSleep>> {
Some(sleep_tokio())

View File

@ -25,7 +25,7 @@ TLDR; This refactor of aws-smithy-checksums:
- **Adds `fn checksum_header_name_to_checksum_algorithm`:** a function that's used in generated code when creating a checksum-validating response body.
- **Add new checksum-related "body wrapping" HTTP body types**: These are defined in the `body` module and will be shown later in this RFC.
```rust
```rust,ignore
// In aws-smithy-checksums/src/lib.rs
//! Checksum calculation and verification callbacks
@ -489,7 +489,7 @@ When creating a checksum-validated request with an in-memory request body, we ca
We will accomplish this by wrapping the `SdkBody` that requires validation within a `ChecksumBody`. Afterwards, we'll need to wrap the `ChecksumBody` in yet another layer which we'll discuss in the [`AwsChunkedBody` and `AwsChunkedBodyOptions`](#awschunkedbody-and-awschunkedbodyoptions) section.
```rust
```rust,ignore
// In aws-smithy-checksums/src/body.rs
use crate::{new_checksum, Checksum};
@ -654,7 +654,7 @@ impl http_body::Body for ChecksumBody<SdkBody> {
Users may request checksum validation for response bodies. That capability is provided by `ChecksumValidatedBody`, which will calculate a checksum as the response body is being read. Once all data has been read, the calculated checksum is compared to a precalculated checksum set during body creation. If the checksums don't match, then the body will emit an error.
```rust
```rust,ignore
// In aws-smithy-checksums/src/body.rs
/// A response body that will calculate a checksum as it is read. If all data is read and the
/// calculated checksum doesn't match a precalculated checksum, this body will emit an
@ -838,7 +838,7 @@ _**NOTES:**_
This encoding scheme is performed by `AwsChunkedBody` and configured with `AwsChunkedBodyOptions`.
```rust
```rust,ignore
// In aws-http/src/content_encoding.rs
use aws_smithy_checksums::body::ChecksumBody;
use aws_smithy_http::body::SdkBody;
@ -1264,7 +1264,7 @@ Setting `STREAMING-UNSIGNED-PAYLOAD-TRAILER` tells the signer that we're sending
We can achieve this by:
- Adding a new variant to `SignableBody`:
```rust
```rust,ignore
/// A signable HTTP request body
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
@ -1279,7 +1279,7 @@ We can achieve this by:
}
```
- Updating the `CanonicalRequest::payload_hash` method to include the new `SignableBody` variant:
```rust
```rust,ignore
fn payload_hash<'b>(body: &'b SignableBody<'b>) -> Cow<'b, str> {
// Payload hash computation
//
@ -1300,7 +1300,7 @@ We can achieve this by:
}
```
- *(in generated code)* Inserting the `SignableBody` into the request property bag when making a checksum-verified streaming request:
```rust
```rust,ignore
if self.checksum_algorithm.is_some() {
request
.properties_mut()
@ -1315,7 +1315,7 @@ It's possible to send `aws-chunked` requests where each chunk is signed individu
In order to avoid writing lots of Rust in Kotlin, I have implemented request and response building functions as inlineables:
- Building checksum-validated requests with in-memory request bodies:
```rust
```rust,ignore
// In aws/rust-runtime/aws-inlineable/src/streaming_body_with_checksum.rs
/// Given a `&mut http::request::Request`, and checksum algorithm name, calculate a checksum and
/// then modify the request to include the checksum as a header.
@ -1344,7 +1344,7 @@ In order to avoid writing lots of Rust in Kotlin, I have implemented request and
}
```
- Building checksum-validated requests with streaming request bodies:
```rust
```rust,ignore
/// Given an `http::request::Builder`, `SdkBody`, and a checksum algorithm name, return a
/// `Request<SdkBody>` with checksum trailers where the content is `aws-chunked` encoded.
pub fn build_checksum_validated_request_with_streaming_body(
@ -1395,7 +1395,7 @@ In order to avoid writing lots of Rust in Kotlin, I have implemented request and
}
```
- Building checksum-validated responses:
```rust
```rust,ignore
/// Given a `Response<SdkBody>`, checksum algorithm name, and pre-calculated checksum, return a
/// `Response<SdkBody>` where the body will processed with the checksum algorithm and checked
/// against the pre-calculated checksum.

View File

@ -10,7 +10,7 @@ the SDK has no easy way to accomplish this. At time of writing, the lower level
client has to be used to create an operation, and then the HTTP request augmented on
that operation type. For example:
```rust
```rust,ignore
let input = SomeOperationInput::builder().some_value(5).build()?;
let operation = {
@ -52,7 +52,7 @@ The code generated fluent builders returned by the fluent client should have a m
similar to `send`, but that returns a customizable request. The customer experience should look as
follows:
```rust
```rust,ignore
let response = client.some_operation()
.some_value(5)
.customize()
@ -69,7 +69,7 @@ let response = client.some_operation()
This new async `customize` method would return the following:
```rust
```rust,ignore
pub struct CustomizableOperation<O, R> {
handle: Arc<Handle>,
operation: Operation<O, R>,
@ -137,7 +137,7 @@ HTTP request.
The `CustomizableOperation` type will then mirror these functions so that the experience
can look as follows:
```rust
```rust,ignore
let mut operation = client.some_operation()
.some_value(5)
.customize()
@ -167,7 +167,7 @@ Alternatively, the name `build` could be used, but this increases the odds that
customers won't realize that they can call `send` directly, and then call a longer
`build`/`send` chain when customization isn't needed:
```rust
```rust,ignore
client.some_operation()
.some_value()
.build() // Oops, didn't need to do this
@ -177,7 +177,7 @@ client.some_operation()
vs.
```rust
```rust,ignore
client.some_operation()
.some_value()
.send()

View File

@ -46,7 +46,7 @@ Each of these configurable parts must therefore be logged cautiously.
It would be unfeasible to forbid the logging of sensitive data all together using the type system. With the current API, the customer will always have an opportunity to log a request containing sensitive data before it enters the `Service<Request<B>>` that we provide to them.
```rust
```rust,ignore
// The API provides us with a `Service<Request<B>>`
let app: Router = OperationRegistryBuilder::default().build().expect("unable to build operation registry").into();
@ -88,7 +88,7 @@ Developers might want to observe sensitive data for debugging purposes. It shoul
To prevent excessive branches such as
```rust
```rust,ignore
if cfg!(feature = "unredacted-logging") {
debug!(%data, "logging here");
} else {
@ -98,7 +98,7 @@ if cfg!(feature = "unredacted-logging") {
the following wrapper should be provided from a runtime crate:
```rust
```rust,ignore
pub struct Sensitive<T>(T);
impl<T> Debug for Sensitive<T>
@ -130,7 +130,7 @@ where
In which case the branch above becomes
```rust
```rust,ignore
debug!(sensitive_data = %Sensitive(data));
```
@ -168,7 +168,7 @@ structure Stocked {
should generate the following
```rust
```rust,ignore
// NOTE: This code is intended to show behavior - it does not compile
pub struct InventoryLogging<S> {
@ -226,7 +226,7 @@ This logging middleware should be applied outside of the [OperationHandler](http
An easy position to apply the logging middleware is illustrated below in the form of `Logging{Operation}::new`:
```rust
```rust,ignore
let empty_operation = LoggingEmptyOperation::new(operation(registry.empty_operation));
let get_pokemon_species = LoggingPokemonSpecies::new(operation(registry.get_pokemon_species));
let get_server_statistics = LoggingServerStatistics::new(operation(registry.get_server_statistics));
@ -273,7 +273,7 @@ Request extensions can be used to adjoin data to a Request as it passes through
These can be used to provide data to middleware interested in logging potentially sensitive data.
```rust
```rust,ignore
struct Sensitivity {
/* Data concerning which parts of the request are sensitive */
}
@ -301,7 +301,7 @@ impl<B, S> Service<Request<B>> for Middleware<S> {
A middleware layer must be code generated (much in the same way as the logging middleware) which is dedicated to inserting the `Sensitivity` struct into the extensions of each incoming request.
```rust
```rust,ignore
impl<B, S> Service<Request<B>> for SensitivityInserter<S>
where
S: Service<Request<B>>
@ -333,7 +333,7 @@ where
It is possible that sensitivity is a parameter passed to middleware during construction. This is similar in nature to [Use Request Extensions](#use-request-extensions) except that the `Sensitivity` is passed to middleware during construction.
```rust
```rust,ignore
struct Middleware<S> {
inner: S,
sensitivity: Sensitivity

View File

@ -16,7 +16,7 @@ The user experience if this RFC is implemented
----------------------------------------------
In the current version of smithy-rs, customers who want to use errors in event streams need to use them as so:
```rust
```rust,ignore
stream! {
yield Ok(EventStreamUnion::ErrorVariant ...)
}
@ -27,7 +27,7 @@ it does not signal termination and thus does not complete the stream.
This RFC proposes to make changes to:
* terminate the stream upon receiving a modeled error
* change the API so that customers will write their business logic in a more Rust-like experience:
```rust
```rust,ignore
stream! {
yield Err(EventStreamUnionError::ErrorKind ...)
}
@ -130,7 +130,7 @@ Wherever irrelevant, documentation and other lines are stripped out from the cod
The error in `AttemptCapturingPokemonEvent` is modeled as follows.
On the client,
```rust
```rust,ignore
pub struct AttemptCapturingPokemonEventError {
pub kind: AttemptCapturingPokemonEventErrorKind,
pub(crate) meta: aws_smithy_types::Error,
@ -142,7 +142,7 @@ pub enum AttemptCapturingPokemonEventErrorKind {
```
On the server,
```rust
```rust,ignore
pub enum AttemptCapturingPokemonEventError {
MasterBallUnsuccessful(crate::error::MasterBallUnsuccessful),
}
@ -160,7 +160,7 @@ On the other side, the `Receiver<>` needs to terminate the stream upon [receivin
A terminated stream has [no more data](https://github.com/awslabs/smithy-rs/blob/8f7e03ff8a84236955a65dba3d21c4bdbf17a9f4/rust-runtime/aws-smithy-http/src/event_stream/receiver.rs#L38) and will always be a [bug](https://github.com/awslabs/smithy-rs/blob/8f7e03ff8a84236955a65dba3d21c4bdbf17a9f4/rust-runtime/aws-smithy-http/src/event_stream/receiver.rs#L54) to use it.
An example of how errors can be used on clients, extracted from [this test](https://github.com/awslabs/smithy-rs/blob/8f7e03ff8a84236955a65dba3d21c4bdbf17a9f4/rust-runtime/aws-smithy-http-server/examples/pokemon_service/tests/simple_integration_test.rs#L100):
```rust
```rust,ignore
yield Err(AttemptCapturingPokemonEventError::new(
AttemptCapturingPokemonEventErrorKind::MasterBallUnsuccessful(MasterBallUnsuccessful::builder().build()),
Default::default()

View File

@ -45,7 +45,7 @@ We have purposely omitted details from the model that are unimportant to describ
Here is a quick example of what a customer might write when using the service builder:
```rust
```rust,ignore
async fn handler0(input: Operation0Input) -> Operation0Output {
todo!()
}
@ -71,7 +71,7 @@ During the survey we touch on the major mechanisms used to achieve this API.
A core concept in the service builder is the `Handler` trait:
```rust
```rust,ignore
pub trait Handler<T, Input> {
async fn call(self, req: http::Request) -> http::Response;
}
@ -81,7 +81,7 @@ Its purpose is to provide an even interface over closures of the form `FnOnce({O
We generate `Handler` implementations for said closures in [ServerOperationHandlerGenerator.kt](https://github.com/awslabs/smithy-rs/blob/458eeb63b95e6e1e26de0858457adbc0b39cbe4e/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationHandlerGenerator.kt):
```rust
```rust,ignore
impl<Fun, Fut> Handler<(), Operation0Input> for Fun
where
Fun: FnOnce(Operation0Input) -> Fut,
@ -122,7 +122,7 @@ The `request.extensions().get::<T>()` present in the `Fun: FnOnce(Operation0Inpu
To convert the closures described above into a `Service` an `OperationHandler` is used:
```rust
```rust,ignore
pub struct OperationHandler<H, T, Input> {
handler: H,
}
@ -151,7 +151,7 @@ The service builder we provide to the customer is the `OperationRegistryBuilder`
Currently, the reference model would generate the following `OperationRegistryBuilder` and `OperationRegistry`:
```rust
```rust,ignore
pub struct OperationRegistryBuilder<Op0, In0, Op1, In1> {
operation1: Option<Op0>,
operation2: Option<Op1>,
@ -165,7 +165,7 @@ pub struct OperationRegistry<Op0, In0, Op1, In1> {
The `OperationRegistryBuilder` includes a setter per operation, and a fallible `build` method:
```rust
```rust,ignore
impl<Op0, In0, Op1, In1> OperationRegistryBuilder<Op0, In0, Op1, In1> {
pub fn operation0(mut self, value: Op0) -> Self {
self.operation0 = Some(value);
@ -188,7 +188,7 @@ impl<Op0, In0, Op1, In1> OperationRegistryBuilder<Op0, In0, Op1, In1> {
The `OperationRegistry` does not include any methods of its own, however it does enjoy a `From<OperationRegistry> for Router<B>` implementation:
```rust
```rust,ignore
impl<B, Op0, In0, Op1, In1> From<OperationRegistry<B, Op0, In0, Op1, In1>> for Router<B>
where
Op0: Handler<B, In0, Operation0Input>,
@ -223,7 +223,7 @@ where
The [aws_smithy_http::routing::Router](https://github.com/awslabs/smithy-rs/blob/458eeb63b95e6e1e26de0858457adbc0b39cbe4e/rust-runtime/aws-smithy-http-server/src/routing/mod.rs#L58-L60) provides the protocol aware routing of requests to their target , it exists as
```rust
```rust,ignore
pub struct Route {
service: Box<dyn Service<http::Request, Response = http::Response>>,
}
@ -242,7 +242,7 @@ pub struct Router {
and enjoys the following `Service<http::Request>` implementation:
```rust
```rust,ignore
impl Service<http::Request> for Router
{
type Response = http::Response;
@ -268,7 +268,7 @@ impl Service<http::Request> for Router
Along side the protocol specific constructors, `Router` includes a `layer` method. This provides a way for the customer to apply a `tower::Layer` to all routes. For every protocol, `Router::layer` has the approximately the same behavior:
```rust
```rust,ignore
let new_routes = old_routes
.into_iter()
// Apply the layer
@ -299,7 +299,7 @@ To identify where the implementations should differ we should classify in what w
In `axum` there is a notion of [Extractor](https://docs.rs/axum/latest/axum/extract/index.html), which allows the customer to easily define a decomposition of an incoming `http::Request` by specifying the arguments to the handlers. For example,
```rust
```rust,ignore
async fn request(Json(payload): Json<Value>, Query(params): Query<HashMap<String, String>>, headers: HeaderMap) {
todo!()
}
@ -350,7 +350,7 @@ The Smithy model not only specifies the `http::Request` decomposition and `http:
This is in contrast to `axum`, where the user specifies the routing by use of various combinators included on the `axum::Router`, applied to other `tower::Service`s. In an `axum` application one might encounter the following code:
```rust
```rust,ignore
let user_routes = Router::new().route("/:id", /* service */);
let team_routes = Router::new().route("/", /* service */);
@ -366,7 +366,7 @@ Note that, in `axum` handlers are eagerly converted to a `tower::Service` (via `
Introducing state to handlers in `axum` is done in the same way as `smithy-rs`, described briefly in [Handlers](#handlers) - a layer is used to insert state into incoming `http::Request`s and the `Handler` implementation pops it out of the type map layer. In `axum`, if a customer wanted to scope state to all routes within `/users/` they are able to do the following:
```rust
```rust,ignore
async fn handler(Extension(state): Extension</* State */>) -> /* Return Type */ {}
let api_routes = Router::new()
@ -390,7 +390,7 @@ As described in [Builder](#builder), the customer is required to perform two con
As described in [Builder](#builder), the `OperationRegistryBuilder::build` method is fallible - it yields a runtime error when one of the handlers has not been set.
```rust
```rust,ignore
pub fn build(
self,
) -> Result<OperationRegistry<Op0, In0, Op1, In1>, OperationRegistryBuilderError> {
@ -403,7 +403,7 @@ As described in [Builder](#builder), the `OperationRegistryBuilder::build` metho
We can do away with fallibility if we allow for on `Op0`, `Op1` to switch types during build and remove the `Option` from around the fields. The `OperationRegistryBuilder` then becomes
```rust
```rust,ignore
struct OperationRegistryBuilder<Op0, Op1> {
operation_0: Op0,
operation_1: Op1
@ -444,19 +444,19 @@ The customer will now get a compile time error rather than a runtime error when
To construct a `Router`, the customer must either give a type ascription
```rust
```rust,ignore
let app: Router = /* Service builder */.into();
```
or be explicit about the `Router` namespace
```rust
```rust,ignore
let app = Router::from(/* Service builder */);
```
If we switch from a `From<OperationRegistry> for Router` to a `build` method on `OperationRegistry` the customer may simply
```rust
```rust,ignore
let app = /* Service builder */.build();
```
@ -478,7 +478,7 @@ Throughout this section we purposely ignore the existence of handlers accepting
It's possible to make progress with a small changeset, by requiring the customer eagerly uses `OperationHandler::new` rather than it being applied internally within `From<OperationRegistry> for Router` (see [Handlers](#handlers)). The setter would then become:
```rust
```rust,ignore
pub struct OperationRegistryBuilder<Op0, Op1> {
operation1: Option<Op0>,
operation2: Option<Op1>
@ -494,7 +494,7 @@ impl<Op0, Op1> OperationRegistryBuilder<Op0, Op1> {
The API usage would then become
```rust
```rust,ignore
async fn handler0(input: Operation0Input) -> Operation0Output {
todo!()
}
@ -514,7 +514,7 @@ Note that this requires that the `OperationRegistryBuilder` stores services, rat
It is still possible to retain the original API which accepts `Handler` by introducing the following setters:
```rust
```rust,ignore
impl<Op1, Op2> OperationRegistryBuilder<Op1, Op2> {
fn operation0_handler<H: Handler>(self, handler: H) -> OperationRegistryBuilder<OperationHandler<H>, Op2> {
OperationRegistryBuilder {
@ -535,7 +535,7 @@ This does not solve (3), as the customer is not able to provide a `tower::Servic
In order to achieve all three we model operations as middleware:
```rust
```rust,ignore
pub struct Operation0<S> {
inner: S,
}
@ -572,7 +572,7 @@ A consequence of this is that the user `Operation0` must have two constructors:
A brief example of how this might look:
```rust
```rust,ignore
use tower::util::{ServiceFn, service_fn};
impl<S> Operation0<S> {
@ -594,7 +594,7 @@ impl<F> Operation0<ServiceFn<F>> {
The API usage then becomes:
```rust
```rust,ignore
async fn handler(input: Operation0Input) -> Operation0Output {
todo!()
}
@ -615,7 +615,7 @@ While [Attempt B](#approach-b-operations-as-middleware) solves all three problem
Any solution which provides an `{Operation}` structure and wishes it to be accepted by multiple service builders must deal with this problem. We currently build one library per service and hence have duplicate structures when [service closures](https://awslabs.github.io/smithy/1.0/spec/core/model.html#service-closure) overlap. This means we wouldn't run into this problem today, but it would be a future obstruction if we wanted to reduce the amount of generated code.
```rust
```rust,ignore
use tower::layer::util::{Stack, Identity};
use tower::util::{ServiceFn, service_fn};
@ -709,7 +709,7 @@ Currently the `Router` stores `Box<dyn tower::Service<http::Request, Response =
Having the service type parameterized as `Router<S>`, allows us to write:
```rust
```rust,ignore
impl<S> Router<S> {
fn layer<L>(self, layer: &L) -> Router<L::Service>
where
@ -775,7 +775,7 @@ This is compatible with [Protocol specific Routers](#protocol-specific-routers),
With both of these changes the API would take the form:
```rust
```rust,ignore
let service_0: Service0 = Service0::builder()
/* use the setters */
.build()
@ -785,7 +785,7 @@ let service_0: Service0 = Service0::builder()
With [Remove two-step build procedure](#remove-two-step-build-procedure), [Switch `From<OperationRegistry> for Router` to a `OperationRegistry::build` method](#switch-fromoperationregistry-for-router-to-an-operationregistrybuild-method), and [Statically check for missing Handlers](#statically-check-for-missing-handlers) we obtain the following API:
```rust
```rust,ignore
let service_0: Service0 = Service0::builder()
/* use the setters */
.build();
@ -795,7 +795,7 @@ let service_0: Service0 = Service0::builder()
A combination of all the proposed transformations results in the following API:
```rust
```rust,ignore
struct Context {
/* fields */
}

View File

@ -28,7 +28,7 @@ that this RFC will attempt to solve, and calls out what was done well, and what
### Case study: `InvalidFullUriError`
To start, let's examine `InvalidFullUriError` (doc comments omitted):
```rust
```rust,ignore
#[derive(Debug)]
#[non_exhaustive]
pub enum InvalidFullUriError {
@ -89,7 +89,7 @@ However, there are also a number of things that could be improved:
Next, let's look at a much simpler error. The `ProfileParseError` is focused purely on the parsing
logic for the SDK config file:
```rust
```rust,ignore
#[derive(Debug, Clone)]
pub struct ProfileParseError {
location: Location,
@ -128,7 +128,7 @@ What could be improved:
The SDK currently generates errors such as the following (from S3):
```rust
```rust,ignore
#[non_exhaustive]
pub enum Error {
BucketAlreadyExists(BucketAlreadyExists),
@ -232,7 +232,7 @@ all errors in the public API for the Rust runtime crates and generated client cr
Actionable errors are represented as enums. If an error variant has an error source or additional contextual
information, it must use a separate context struct that is referenced via tuple in the enum. For example:
```rust
```rust,ignore
// Good: new error types can be added in the future
#[non_exhaustive]
pub enum Error {
@ -296,7 +296,7 @@ adding a new error variant's source at a later date.
The error `Display` implementation _must not_ include the source in its output:
```rust
```rust,ignore
// Good
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -326,7 +326,7 @@ impl fmt::Display for Error {
Informative errors must be represented as structs. If error messaging changes based on an underlying cause, then a
private error kind enum can be used internally for this purpose. For example:
```rust
```rust,ignore
#[derive(Debug)]
pub struct InformativeError {
some_additional_info: u32,
@ -356,13 +356,13 @@ In code where errors are logged rather than returned to the customer, the full e
must be displayed. This will be made easy by placing a `DisplayErrorContext` struct in `aws-smithy-types` that
is used as a wrapper to get the better error formatting:
```rust
```rust,ignore
tracing::warn!(err = %DisplayErrorContext(err), "some message");
```
This might be implemented as follows:
```rust
```rust,ignore
#[derive(Debug)]
pub struct DisplayErrorContext<E: Error>(pub E);

View File

@ -40,7 +40,7 @@ Constraints:
Let's start by reviewing the API proposed in [RFC 20]. We will use the [Pokemon service] as our driving example throughout the RFC.
This is what the startup code looks like:
```rust
```rust,ignore
#[tokio::main]
pub async fn main() {
// [...]
@ -115,7 +115,7 @@ Given the above, we think that the impact of a runtime error is low enough to be
Moving from a compile-time error to a runtime error does not require extensive refactoring.
The definition of `PokemonServiceBuilder` goes from:
```rust
```rust,ignore
pub struct PokemonServiceBuilder<
Op1,
Op2,
@ -145,7 +145,7 @@ pub struct PokemonServiceBuilder<
to:
```rust
```rust,ignore
pub struct PokemonServiceBuilder<
Op1,
Op2,
@ -176,7 +176,7 @@ pub struct PokemonServiceBuilder<
All operation fields are now `Option`-wrapped.
We introduce a new `MissingOperationsError` error to hold the names of the missing operations and their respective setter methods:
```rust
```rust,ignore
#[derive(Debug)]
pub struct MissingOperationsError {
service_name: &'static str,
@ -208,7 +208,7 @@ We can also provide actionable suggestions: Rust beginners should be able to eas
Let's take a second look at the (updated) definition of `PokemonServiceBuilder`:
```rust
```rust,ignore
pub struct PokemonServiceBuilder<
Op1,
Op2,
@ -255,7 +255,7 @@ Let's consider a toy example: if a `check_database` flag is set to `true`, we wa
The "obvious" solution would look somewhat like this:
```rust
```rust,ignore
let check_database: bool = /* */;
let app = if check_database {
app.check_health(check_health)
@ -303,7 +303,7 @@ The developer has three options to move forward:
I can't easily see a way to accomplish 1) using the current API. Pursuing 2) is straight-forward with a single conditional:
```rust
```rust,ignore
let check_database: bool = /* */;
let app = if check_database {
app.check_health(check_health).build()
@ -314,7 +314,7 @@ let app = if check_database {
It becomes more cumbersome when we have more than a single conditional:
```rust
```rust,ignore
let check_database: bool = /* */;
let include_cpu_statics: bool = /* */;
match (check_database, include_cpu_statics) {
@ -339,7 +339,7 @@ match (check_database, include_cpu_statics) {
A lot of repetition compared to the code for the "obvious" approach:
```rust
```rust,ignore
let check_database: bool = /* */;
let include_cpu_statics: bool = /* */;
let app = if check_database {
@ -367,7 +367,7 @@ The service builder must be one of the arguments if we want to register handlers
A first sketch:
```rust
```rust,ignore
fn partial_setup(builder: PokemonServiceBuilder) -> PokemonServiceBuilder {
/* */
}
@ -395,7 +395,7 @@ note: struct defined here, with at least 6 generic parameters: `Op1`, `Op2`, `Op
We could try to nudge the compiler into inferring them:
```rust
```rust,ignore
fn partial_setup(
builder: PokemonServiceBuilder<_, _, _, _, _, _>,
) -> PokemonServiceBuilder<_, _, _, _, _, _> {
@ -421,7 +421,7 @@ error[E0121]: the placeholder `_` is not allowed within types on item signatures
We must type it all out:
```rust
```rust,ignore
fn partial_setup<Op1, Op2, Op3, Op4, Op5, Op6>(
builder: PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6>,
) -> PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6> {
@ -432,7 +432,7 @@ fn partial_setup<Op1, Op2, Op3, Op4, Op5, Op6>(
That compiles, at last.
Let's try to register an operation handler now:
```rust
```rust,ignore
fn partial_setup<Op1, Op2, Op3, Op4, Op5, Op6>(
builder: PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6>,
) -> PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6> {
@ -468,7 +468,7 @@ Can we get rid of them?
Yes! Let's look at one possible approach:
```rust
```rust,ignore
pub struct PokemonServiceBuilder<Body, Plugin> {
check_health: Option<Route<Body>>,
do_nothing: Option<Route<Body>>,
@ -483,7 +483,7 @@ pub struct PokemonServiceBuilder<Body, Plugin> {
We no longer store the raw handlers inside `PokemonServiceBuilder`.
We eagerly upgrade the operation handlers to a `Route` instance when they are registered with the builder.
```rust
```rust,ignore
impl<Body, Plugin> PokemonServiceBuilder<Body, Plugin> {
pub fn get_pokemon_species<Handler, Extensions>(mut self, handler: Handler) -> Self
/* Complex trait bounds */
@ -500,7 +500,7 @@ impl<Body, Plugin> PokemonServiceBuilder<Body, Plugin> {
The existing API performs the upgrade when `build` is called, forcing `PokemonServiceBuilder` to store the raw handlers and keep two generic parameters around (`OpX` and `ExtsX`) for each operation.
The proposed API requires plugins to be specified upfront, when creating an instance of the builder. They cannot be modified after a `PokemonServiceBuilder` instance has been built:
```rust
```rust,ignore
impl PokemonService<()> {
/// Constructs a builder for [`PokemonService`].
pub fn builder<Body, Plugin>(plugin: Plugin) -> PokemonServiceBuilder<Body, Plugin> {
@ -526,7 +526,7 @@ We have seen how cumbersome it is to break the startup logic into different func
The new design prohibits the following invocation style:
```rust
```rust,ignore
let plugin = ColorPlugin::new();
PokemonService::builder(plugin)
// [...]
@ -548,7 +548,7 @@ There are no technical obstacles preventing us from implementing this API, but I
We can provide developers with other mechanisms to register plugins for a single operation or a subset of operations without introducing ambiguity.
For attaching additional plugins to a single operation, we could introduce a blanket `Pluggable` implementation for all operations in `aws-smithy-http-server`:
```rust
```rust,ignore
impl<P, Op, Pl, S, L> Pluggable<Pl> for Operation<S, L> where Pl: Plugin<P, Op, S, L> {
type Output = Operation<Pl::Service, Pl::Layer>;
@ -561,7 +561,7 @@ impl<P, Op, Pl, S, L> Pluggable<Pl> for Operation<S, L> where Pl: Plugin<P, Op,
which would allow developers to invoke `op.apply(MyPlugin)` or call extensions methods such as `op.print()` where `op` is an `Operation`.
For attaching additional plugins to a subgroup of operations, instead, we could introduce nested builders:
```rust
```rust,ignore
let initial_plugins = ColorPlugin;
let mut builder = PokemonService::builder(initial_plugins)
.get_pokemon_species(get_pokemon_species);
@ -602,7 +602,7 @@ We are going to explore both approaches under the assumption that we want to pre
This is the current definition of the `Upgradable` trait:
```rust
```rust,ignore
/// Provides an interface to convert a representation of an operation to a HTTP [`Service`](tower::Service) with
/// canonical associated types.
pub trait Upgradable<Protocol, Operation, Exts, Body, Plugin> {
@ -631,7 +631,7 @@ The above leaves us with two unconstrained type parameters, `Operation` and `Ext
Going back to the branching example:
```rust
```rust,ignore
let check_database: bool = /* */;
let builder = if check_database {
builder.check_health(check_health)
@ -643,7 +643,7 @@ let app = builder.build();
In approach 1), we could leverage the `.boxed()` method to convert the actual `OpX` type into a `Box<dyn Upgradable>`, thus ensuring that both branches return the same type:
```rust
```rust,ignore
let check_database: bool = /* */;
let builder = if check_database {
builder.check_health_operation(Operation::from_handler(check_health).boxed())
@ -655,7 +655,7 @@ let app = builder.build();
The same cannot be done when conditionally registering a route, because on the `else` branch we cannot convert `MissingOperation` into a `Box<dyn Upgradable>` since `MissingOperation` doesn't implement `Upgradable` - the pillar on which we built all our compile-time safety story.
```rust
```rust,ignore
// This won't compile!
let builder = if check_database {
builder.check_health_operation(Operation::from_handler(check_health).boxed())
@ -666,7 +666,7 @@ let builder = if check_database {
In approach 2), we can erase the whole builder in both branches when they both register a route:
```rust
```rust,ignore
let check_database: bool = /* */;
let boxed_builder = if check_database {
builder.check_health(check_health).erase()
@ -682,7 +682,7 @@ but, like in approach 1), we will still get a type mismatch error if one of the
Developers would still have to spell out all generic parameters when writing a function that takes in a builder as a parameter:
```rust
```rust,ignore
fn partial_setup<Op1, Op2, Op3, Op4, Op5, Op6, Body, Plugin>(
builder: PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6, Body, Plugin>,
) -> PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6, Body, Plugin> {
@ -693,7 +693,7 @@ fn partial_setup<Op1, Op2, Op3, Op4, Op5, Op6, Body, Plugin>(
Writing the signature after having modified the builder becomes easier though.
In approach 1), they can explicitly change the touched operation parameters to the boxed variant:
```rust
```rust,ignore
fn partial_setup<Op1, Op2, Op3, Op4, Op5, Op6, Exts4, Body, Plugin>(
builder: PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6, Body, Plugin, Exts4=Exts4>,
) -> PokemonServiceBuilder<
@ -706,7 +706,7 @@ fn partial_setup<Op1, Op2, Op3, Op4, Op5, Op6, Exts4, Body, Plugin>(
It becomes trickier in approach 2), since to retain compile-time safety on the builder we expect `erase` to map `MissingOperation` into `MissingOperation`. Therefore, we can't write something like this:
```rust
```rust,ignore
fn partial_setup<Body, Op1, Op2, Op3, Op4, Op5, Op6>(
builder: PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6>,
) -> PokemonServiceBuilder<Route<B>, Route<B>, Route<B>, Route<B>, Route<B>, Route<B>> {
@ -716,7 +716,7 @@ fn partial_setup<Body, Op1, Op2, Op3, Op4, Op5, Op6>(
The compiler would reject it since it can't guarantee that all other operations can be erased to a `Route<B>`. This is likely to require something along the lines of:
```rust
```rust,ignore
fn partial_setup<Body, Op1, Op2, Op3, Op4, Op5, Op6>(
builder: PokemonServiceBuilder<Op1, Op2, Op3, Op4, Op5, Op6>,
) -> PokemonServiceBuilder<<Op1 as TypeErase>::Erased, <Op2 as TypeErase>::Erased, <Op3 as TypeErase>::Erased, <Op4 as TypeErase>::Erased, <Op5 as TypeErase>::Erased, <Op6 as TypeErase>::Erased>
@ -742,7 +742,7 @@ We believe that the ergonomics advantages of the proposal advanced by this RFC o
The `Pluggable` trait was an interesting development out of [RFC 20]: it allows you to attach methods to a service builder using an extension trait.
```rust
```rust,ignore
/// An extension to service builders to add the `print()` function.
pub trait PrintExt: aws_smithy_http_server::plugin::Pluggable<PrintPlugin> {
/// Causes all operations to print the operation name when called.
@ -760,7 +760,7 @@ pub trait PrintExt: aws_smithy_http_server::plugin::Pluggable<PrintPlugin> {
This pattern needs to be revisited if we want to move forward with this RFC, since new plugins cannot be registered after the builder has been instantiated.
My recommendation would be to implement `Pluggable` for `PluginStack`, providing the same pattern ahead of the creation of the builder:
```rust
```rust,ignore
// Currently you'd have to go for `PluginStack::new(IdentityPlugin, IdentityPlugin)`,
// but that can be smoothed out even if this RFC isn't approved.
let plugin_stack = PluginStack::default()

View File

@ -43,13 +43,13 @@ To aid customers already relying on clients' request IDs, there will be two type
1. Implementing `FromParts` for `Extension<RequestId>` gives customers the ability to write their handlers:
```rust
```rust,ignore
pub async fn handler(
input: input::Input,
request_id: Extension<ServerRequestId>,
) -> ...
```
```rust
```rust,ignore
pub async fn handler(
input: input::Input,
request_id: Extension<ClientRequestId>,
@ -71,7 +71,7 @@ For privacy reasons, any format that provides service details should be avoided.
The proposed format is to use UUID, version 4.
A `Service` that inserts a RequestId in the extensions will be implemented as follows:
```rust
```rust,ignore
impl<R, S> Service<http::Request<R>> for ServerRequestIdProvider<S>
where
S: Service<http::Request<R>>,
@ -96,7 +96,7 @@ For client request IDs, the process will be, in order:
* Otherwise, None
`Option` is used to distinguish whether a client had provided an ID or not.
```rust
```rust,ignore
impl<R, S> Service<http::Request<R>> for ClientRequestIdProvider<S>
where
S: Service<http::Request<R>>,

View File

@ -114,7 +114,7 @@ values and perform the _validation_ at request deserialization, we can
wrapper [tuple struct] that _parses_ the string's value and is "tight" in the
set of values it can accept:
```rust
```rust,ignore
pub struct NiceString(String);
impl TryFrom<String> for NiceString {
@ -145,7 +145,7 @@ the [`?` operator for error propagation]. Each constrained struct will have a
related `std::error::Error` enum type to signal the _first_ parsing failure,
with one enum variant per applied constraint trait:
```rust
```rust,ignore
pub mod nice_string {
pub enum ConstraintViolation {
/// Validation error holding the number of Unicode code points found, when a value between `1` and
@ -161,7 +161,7 @@ pub mod nice_string {
`#[derive(Debug)]`, unless the shape also has the [`sensitive` trait], in which
case we will just print the name of the struct:
```rust
```rust,ignore
impl std::fmt::Debug for ConstraintViolation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ConstraintViolation");
@ -207,7 +207,7 @@ string Language
the code the client generates when deserializing a string from a JSON document
into the `Language` enum is (excerpt):
```rust
```rust,ignore
...
match key.to_unescaped()?.as_ref() {
"language" => {
@ -229,7 +229,7 @@ match key.to_unescaped()?.as_ref() {
Note how the `String` gets converted to the enum via `Language::from()`.
```rust
```rust,ignore
impl std::convert::From<&str> for Language {
fn from(s: &str) -> Self {
match s {

View File

@ -163,7 +163,7 @@ or that are required for the most frequent config changes (such as setting crede
or changing the region/endpoint).
Previously, the following were exported in root:
```
```text
.
├── AppName
├── Client
@ -182,7 +182,7 @@ need to be at the top-level, and will be moved into `crate::config`. `ErrorExt`
`crate::error`, but `Error` will stay in the crate root so that customers that alias the SDK crate
can easily reference it in their `Result`s:
```rust
```rust,ignore
use aws_sdk_s3 as s3;
fn some_function(/* ... */) -> Result<(), s3::Error> {

View File

@ -96,7 +96,7 @@ global endpoint provider that can override different services. There is a single
is shared across all services. However, this isn't the case for `Endpoints 2.0` where the trait actually has a generic
parameter:
```rust
```rust,ignore
pub trait ResolveEndpoint<T>: Send + Sync {
fn resolve_endpoint(&self, params: &T) -> Result<Endpoint, BoxError>;
}
@ -129,7 +129,7 @@ This RFC proposes making the following changes:
**Example: overriding the endpoint URI globally**
```rust
```rust,ignore
async fn main() {
let sdk_conf = aws_config::from_env().endpoint_url("http://localhost:8123").load().await;
let dynamo = aws_sdk_dynamodb::Client::new(&sdk_conf);
@ -139,7 +139,7 @@ async fn main() {
**Example: overriding the endpoint resolver for a service**
```rust
```rust,ignore
/// Resolve to Localhost when an environment variable is set
struct CustomDdbResolver;
@ -191,7 +191,7 @@ gated with documentation warning about stability.
#### The Endpoint Struct
```rust
```rust,ignore
// module: `aws_smithy_types::endpoint`
// potential optimization to reduce / remove allocations for keys which are almost always static
// this can also just be `String`
@ -221,7 +221,7 @@ pub struct Endpoint {
To perform produce an `Endpoint` struct we have a generic `ResolveEndpoint` trait which will be both generic in terms of
parameters and being "smithy-generic:
```rust
```rust,ignore
// module: `smithy_types::endpoint` or `aws_smithy_client`??
pub trait ResolveEndpoint<Params>: Send + Sync {
/// Resolves an `Endpoint` for `Params`
@ -242,7 +242,7 @@ the parameters are set, *not* how they are generated.
**Example `Params` struct for S3:**
```rust
```rust,ignore
#[non_exhaustive]
#[derive(std::clone::Clone, std::cmp::PartialEq, std::fmt::Debug)]
/// Configuration parameters for resolving the correct endpoint
@ -289,7 +289,7 @@ When an endpoint ruleset is present, Smithy will code generate an endpoint resol
resolver
**MUST** be a struct so that it can store/cache computations (such as a partition resolver that has compiled regexes).
```rust
```rust,ignore
pub struct DefaultEndpointResolver {
partition_resolver: PartitionResolver
}
@ -539,7 +539,7 @@ An alternative design that could provide more flexibility is a context-aware end
give context about the endpoint being returned. This would, for example, allow a customer to say explicitly "don't
modify this endpoint":
```rust
```rust,ignore
enum ContextualEndpoint {
/// Just the URI please. Pass it into the default endpoint resolver as a baseline
Uri { uri: Uri, immutable: bool },

View File

@ -33,7 +33,7 @@ and wrapping the given (or default) credentials provider.
The `CredentialsCache` would look as follows:
```rust
```rust,ignore
enum Inner {
Lazy(LazyConfig),
// Eager doesn't exist today, so this is purely for illustration
@ -89,7 +89,7 @@ the `impl ProvideCredentials + 'static`. A sealed trait could be added to facili
Customers that don't care about credential caching can configure credential providers
without needing to think about it:
```rust
```rust,ignore
let sdk_config = aws_config::from_env()
.credentials_provider(ImdsCredentialsProvider::builder().build())
.load()
@ -99,7 +99,7 @@ let sdk_config = aws_config::from_env()
However, if they want to customize the caching, they can do so without modifying
the credentials provider at all (in case they want to use the default):
```rust
```rust,ignore
let sdk_config = aws_config::from_env()
.credentials_cache(CredentialsCache::default_eager())
.load()
@ -141,7 +141,7 @@ without any caching logic, although this wouldn't be recommended and this provid
in `aws-config`.
Example configuration:
```rust
```rust,ignore
// Compiles
let sdk_config = aws_config::from_env()
.credentials(
@ -162,7 +162,7 @@ let sdk_config = aws_config::from_env()
Another method could be added to `ConfigLoader` that makes it easier to use the default cache:
```rust
```rust,ignore
let sdk_config = aws_config::from_env()
.credentials_with_default_cache(ImdsCredentialsProvider::new())
.load()
@ -187,7 +187,7 @@ This alternative is similar to alternative A, except that the cache trait is dis
that it's more apparent when mistakenly implementing the wrong trait for a custom credentials provider.
A `CacheCredentials` trait would be added that looks as follows:
```rust
```rust,ignore
pub trait CacheCredentials: Send + Sync + Debug {
async fn cached(&self, now: SystemTime) -> Result<Credentials, CredentialsError>;
}
@ -216,7 +216,7 @@ but at the same time, doesn't make it impossible to add custom caching later.
The idea is that there would be a struct called `CredentialsCache` that specifies the desired
caching approach for a given credentials provider:
```rust
```rust,ignore
pub struct LazyCache {
credentials_provider: Arc<dyn ProvideCredentials>,
// ...
@ -280,7 +280,7 @@ than `impl ProvideCredentials + 'static`. A sealed trait could be added to facil
Configuration would look as follows:
```rust
```rust,ignore
let sdk_config = aws_config::from_env()
.credentials(CredentialsCache::default_lazy(ImdsCredentialsProvider::builder().build()))
.load()
@ -293,7 +293,7 @@ a use case, then a `CredentialsCache::NoCache` variant could be made.
Like alternative A, a convenience method can be added to make using the default cache easier:
```rust
```rust,ignore
let sdk_config = aws_config::from_env()
.credentials_with_default_cache(ImdsCredentialsProvider::builder().build())
.load()
@ -302,7 +302,7 @@ let sdk_config = aws_config::from_env()
In the future if custom caching is added, it would look as follows:
```rust
```rust,ignore
let sdk_config = aws_config::from_env()
.credentials(
CredentialsCache::custom(ImdsCredentialsProvider::builder().build(), MyCache::new())
@ -316,7 +316,7 @@ from the config are needed to construct the cache (such as `sleep_impl`). Thus,
setter would merely save off the `CredentialsCache` instance, and then when `load` is called,
the complete `SharedCredentialsProvider` would be constructed:
```rust
```rust,ignore
pub async fn load(self) -> SdkConfig {
// ...
let credentials_provider = self.credentials_cache.create_cache(sleep_impl);

View File

@ -23,7 +23,7 @@ Problems
--------
Here is how our attempt to implement the selected design in the preceding RFC can lead to an obstacle. Consider this code snippet we are planning to support:
```rust
```rust,ignore
let sdk_config = aws_config::from_env()
.credentials_cache(CredentialsCache::lazy())
.load()
@ -34,7 +34,7 @@ let client = aws_sdk_s3::Client::new(&sdk_config);
A `CredentialsCache` created by `CredentialsCache::lazy()` above will internally go through three crates before the variable `client` has been created:
1. `aws-config`: after it has been passed to `aws_config::ConfigLoader::credentials_cache`
```rust
```rust,ignore
// in lib.rs
impl ConfigLoader {
@ -47,7 +47,7 @@ impl ConfigLoader {
}
```
2. `aws-types`: after `aws_config::ConfigLoader::load` has passed it to `aws_types::sdk_config::Builder::credentials_cache`
```rust
```rust,ignore
// in sdk_config.rs
impl Builder {
@ -60,7 +60,7 @@ impl Builder {
}
```
3. `aws-sdk-s3`: after `aws_sdk_s3::Client::new` has been called with the variable `sdk_config`
```rust
```rust,ignore
// in client.rs
impl Client {
@ -72,7 +72,7 @@ impl Client {
}
```
calls
```rust
```rust,ignore
// in config.rs
impl From<&aws_types::sdk_config::SdkConfig> for Builder {

View File

@ -170,7 +170,7 @@ Serde can distinguish each variant without a tag as each variant's content is di
Builder types and non Builder types implement `Serialize` and `Deserialize` with derive macro.
Example:
```rust
```rust,ignore
#[cfg_attr(
all(aws-sdk-unstable, feature = "serialize"),
derive(serde::Serialize)
@ -220,7 +220,7 @@ Here is an example of data types affected by this decision:
We considered serializing them as bytes, however, it could take some time for a stream to reach the end, and the resulting serialized data may be too big for itself to fit into the ram.
Here is an example snippet.
```rust
```rust,ignore
#[allow(missing_docs)]
#[cfg_attr(
all(aws-sdk-unstable, feature = "serde-serialize"),
@ -272,7 +272,7 @@ SDK does not have a method that allows users to use deserialized `Input`.
Thus, we add a new method `fn set_fields` to `Client` types.
This method accepts inputs and replaces all parameters that `Client` has with the new one.
```rust
```rust,ignore
pub fn set_fields(mut self, input_type: path::to::input_type) -> path::to::input_type {
self.inner = input_type;
self
@ -310,7 +310,7 @@ To clarify, this is the same approach we took on `Data Type to skip` section.
Either way, we will mention this on the generated docs to avoid surprising users.
e.g.
```rust
```rust,ignore
#[derive(serde::Serialize, serde::Deserialize)]
struct OutputV1 {
string_field: Option<String>
@ -389,7 +389,7 @@ We believe that features implemented as part of this RFC do not produce a mislea
# Appendix
## [Use Case Examples](UseCaseExamples)
```rust
```rust,ignore
use aws_sdk_dynamodb::{Client, Error};
async fn example(read_builder: bool) -> Result<(), Error> {

View File

@ -27,14 +27,14 @@ We have mentioned static stability. Supporting it calls for the following functi
- REQ 1: Once a credentials provider has served credentials, it should continue serving them in the event of a timeout (whether internal or external) while obtaining refreshed credentials.
Today, we have the following trait method to obtain credentials:
```rust
```rust,ignore
fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a>
where
Self: 'a,
```
This method returns a future, which can be raced against a timeout future as demonstrated by the following code snippet from `LazyCredentialsCache`:
```rust
```rust,ignore
let timeout_future = self.sleeper.sleep(self.load_timeout); // by default self.load_timeout is 5 seconds.
// --snip--
let future = Timeout::new(provider.provide_credentials(), timeout_future);
@ -76,7 +76,7 @@ Proposal
To address the problem in the previous section, we propose to add a new method to the `ProvideCredentials` trait called `fallback_on_interrupt`. This method allows credentials providers to have a fallback mechanism on an external timeout and to serve credentials to users if needed. There are two options as to how it is implemented, either as a synchronous primitive or as an asynchronous primitive.
#### Option A: Synchronous primitive
```rust
```rust,ignore
pub trait ProvideCredentials: Send + Sync + std::fmt::Debug {
// --snip--
@ -90,7 +90,7 @@ pub trait ProvideCredentials: Send + Sync + std::fmt::Debug {
- :-1: It may turn into a blocking operation if it takes longer than it should.
#### Option B: Asynchronous primitive
```rust
```rust,ignore
mod future {
// --snip--
@ -117,7 +117,7 @@ pub trait ProvideCredentials: Send + Sync + std::fmt::Debug {
Option A cannot be reversible in the future if we are to support the use case for asynchronously retrieving the fallback credentials, whereas option B allows us to continue supporting both ready and pending futures when retrieving the fallback credentials. However, `fallback_on_interrupt` is supposed to return credentials that have been set aside in case `provide_credentials` is timed out. To express that intent, we choose option A and document that users should NOT go fetch new credentials in `fallback_on_interrupt`.
The user experience for the code snippet in question will look like this once this proposal is implemented:
```rust
```rust,ignore
let timeout_future = self.sleeper.sleep(self.load_timeout); // by default self.load_timeout is 5 seconds.
// --snip--
let future = Timeout::new(provider.provide_credentials(), timeout_future);
@ -146,7 +146,7 @@ Almost all credentials providers do not have to implement their own `fallback_on
Considering the two cases we analyzed above, implementing `CredentialsProviderChain::fallback_on_interrupt` is not so straightforward. Keeping track of whose turn in the chain it is to call `provide_credentials` when an external timeout has occurred is a challenging task. Even if we figured it out, that would still not satisfy `Case 2` above, because it was provider 1 that was actively running when the external timeout kicked in, but the chain should return credentials from provider 2, not from provider 1.
With that in mind, consider instead the following approach:
```rust
```rust,ignore
impl ProvideCredentials for CredentialsProviderChain {
// --snip--
@ -174,7 +174,7 @@ Alternative
In this section, we will describe an alternative approach that we ended up dismissing as unworkable.
Instead of `fallback_on_interrupt`, we considered the following method to be added to the `ProvideCredentials` trait:
```rust
```rust,ignore
pub trait ProvideCredentials: Send + Sync + std::fmt::Debug {
// --snip--
@ -201,7 +201,7 @@ pub trait ProvideCredentials: Send + Sync + std::fmt::Debug {
}
```
`provide_credentials_with_timeout` encapsulated the timeout race and allowed users to specify how long the external timeout for `provide_credentials` would be. The code snippet from `LazyCredentialsCache` then looked like
```rust
```rust,ignore
let sleeper = Arc::clone(&self.sleeper);
let load_timeout = self.load_timeout; // by default self.load_timeout is 5 seconds.
// --snip--
@ -217,7 +217,7 @@ let result = cache
// --snip--
```
However, implementing `CredentialsProviderChain::provide_credentials_with_timeout` quickly ran into the following problem:
```rust
```rust,ignore
impl ProvideCredentials for CredentialsProviderChain {
// --snip--
@ -289,7 +289,7 @@ This a case where `CredentialsProviderChain::fallback_on_interrupt` requires the
</p>
The outermost chain is a `CredentialsProviderChain` and follows the precedence policy for `fallback_on_interrupt`. It contains a sub-chain that, in turn, contains provider 1 and provider 2. This sub-chain implements its own `fallback_on_interrupt` to realize the recency policy for fallback credentials found in provider 1 and provider 2. Conceptually, we have
```
```rust,ignore
pub struct FallbackRecencyChain {
provider_chain: CredentialsProviderChain,
}
@ -310,7 +310,7 @@ impl ProvideCredentials for FallbackRecencyChain {
}
```
We can then compose the entire chain like so:
```
```rust,ignore
let provider_1 = /* ... */
let provider_2 = /* ... */
let provider_3 = /* ... */

View File

@ -67,7 +67,7 @@ A constrained type has a fallible constructor by virtue of it implementing the
[`TryFrom`] trait. The error type this constructor may yield is known as a
**constraint violation**:
```rust
```rust,ignore
impl TryFrom<UnconstrainedType> for ConstrainedType {
type Error = ConstraintViolation;
@ -90,7 +90,7 @@ structure A {
Yields:
```rust
```rust,ignore
/// See [`A`](crate::model::A).
pub mod a {
#[derive(std::cmp::PartialEq, std::fmt::Debug)]
@ -107,7 +107,7 @@ variant.
Constraint violations can occur in application code:
```rust
```rust,ignore
use my_server_sdk::model
let res = model::a::Builder::default().build(); // We forgot to set `member`.
@ -148,7 +148,7 @@ string LengthString
This produces:
```rust
```rust,ignore
pub struct LengthMap(
pub(crate) std::collections::HashMap<std::string::String, crate::model::LengthString>,
);
@ -206,7 +206,7 @@ the server framework uses upon deserialization. Observe how
`LengthMapOfLengthStringsUnconstrained` is _fully unconstrained_ and how the
`try_from` constructor can yield `ConstraintViolation::Value`.
```rust
```rust,ignore
pub(crate) mod length_map_of_length_strings_unconstrained {
#[derive(Debug, Clone)]
pub(crate) struct LengthMapOfLengthStringsUnconstrained(
@ -260,7 +260,7 @@ violation type into two types, which this RFC proposes:
2. one for use by user application code, with `pub` visibility, named
`ConstraintViolation`.
```rust
```rust,ignore
pub mod length_map {
pub enum ConstraintViolation {
Length(usize),
@ -362,7 +362,7 @@ string LengthPatternString
Yields:
```rust
```rust,ignore
pub struct LengthPatternString(pub(crate) std::string::String);
impl LengthPatternString {
@ -446,7 +446,7 @@ Let's consider a `ConstraintViolations` type (note the plural) that represents
a collection of constraint violations that can occur _within user application
code_. Roughly:
```rust
```rust,ignore
pub ConstraintViolations<T>(pub(crate) Vec<T>);
impl<T> IntoIterator<Item = T> for ConstraintViolations<T> { ... }
@ -558,7 +558,7 @@ string LengthString
The corresponding `ConstraintViolationException` Rust type for the `LengthMap`
shape is:
```rust
```rust,ignore
pub mod length_map {
pub enum ConstraintViolation {
Length(usize),
@ -575,7 +575,7 @@ pub mod length_map {
`ConstraintViolationExceptions` is just a container over this type:
```rust
```rust,ignore
pub ConstraintViolationExceptions<T>(pub(crate) Vec<T>);
impl<T> IntoIterator<Item = T> for ConstraintViolationExceptions<T> { ... }
@ -604,8 +604,8 @@ string LengthPatternString
This would yield:
```rust
pub ConstraintViolations<T>(pub(crate) Vec<T>);
```rust,ignore
pub struct ConstraintViolations<T>(pub(crate) Vec<T>);
impl<T> IntoIterator<Item = T> for ConstraintViolations<T> { ... }
@ -656,7 +656,7 @@ string LengthPatternString
This would yield, as per the first substitution:
```rust
```rust,ignore
pub mod length_pattern_string {
pub struct ConstraintViolations {
pub length: Option<constraint_violation::Length>,
@ -693,7 +693,7 @@ map LengthMap {
This gives us:
```rust
```rust,ignore
pub mod length_map {
pub struct ConstraintViolations {
pub length: Option<constraint_violation::Length>,
@ -752,7 +752,7 @@ structure A {
And this time let's feature _both_ the resulting
`ConstraintViolationExceptions` and `ConstraintViolations` types:
```rust
```rust,ignore
pub mod a {
pub struct ConstraintViolationExceptions {
// All fields must be `Option`, despite the members being `@required`,

View File

@ -156,7 +156,7 @@ Example Interactions
### Generic Handling Case
```rust
```rust,ignore
// A re-export of the RequestId trait
use aws_sdk_service::primitives::RequestId;
@ -170,7 +170,7 @@ my_request_id_logging_fn(&result);
### Success Case
```rust
```rust,ignore
use aws_sdk_service::primitives::RequestId;
let output = client.some_operation().send().await?;
@ -179,7 +179,7 @@ println!("request ID: {:?}", output.request_id());
### Error Case with `SdkError`
```rust
```rust,ignore
use aws_sdk_service::primitives::RequestId;
match client.some_operation().send().await {
@ -192,7 +192,7 @@ match client.some_operation().send().await {
### Error Case with operation error
```rust
```rust,ignore
use aws_sdk_service::primitives::RequestId;
match client.some_operation().send().await {

View File

@ -51,7 +51,7 @@ When a smithy client communicates with a smithy service, messages are handled by
For many users, the changes described by this RFC will be invisible. Making a request with an orchestrator-based SDK client looks very similar to the way requests were made pre-RFC:
```rust
```rust,ignore
let sdk_config = aws_config::load_from_env().await;
let client = aws_sdk_s3::Client::new(&sdk_config);
let res = client.get_object()
@ -111,7 +111,7 @@ Interceptors are similar to middlewares, in that they are functions that can rea
As mentioned above, interceptors may read/write a context object that is shared between all interceptors:
```rust
```rust,ignore
pub struct InterceptorContext<ModReq, TxReq, TxRes, ModRes> {
// a.k.a. the input message
modeled_request: ModReq,
@ -134,7 +134,7 @@ The optional request and response types in the interceptor context can only be a
Imagine we have some sort of request signer. This signer doesn't refer to any orchestrator types. All it needs is a `HeaderMap` along with two strings, and will return a signature in string form.
```rust
```rust,ignore
struct Signer;
impl Signer {
@ -147,7 +147,7 @@ impl Signer {
Now imagine things from the orchestrator's point of view. It requires something that implements an `AuthOrchestrator` which will be responsible for resolving the correct auth
scheme, identity, and signer for an operation, as well as signing the request
```rust
```rust,ignore
pub trait AuthOrchestrator<Req>: Send + Sync + Debug {
fn auth_request(&self, req: &mut Req, cfg: &ConfigBag) -> Result<(), BoxError>;
}
@ -171,7 +171,7 @@ fn invoke() {
The specific implementation of the `AuthOrchestrator` is what brings these two things together:
```rust
```rust,ignore
struct Sigv4AuthOrchestrator;
impl AuthOrchestrator for Sigv4AuthOrchestrator {
@ -201,7 +201,7 @@ This intermediate code should be free from as much logic as possible. Whenever p
>
> *See [typemap](https://docs.rs/typemap), [type-map](https://docs.rs/crate/type-map), [http::Extensions](https://docs.rs/http/latest/http/struct.Extensions.html), and [actix_http::Extensions](https://docs.rs/actix-http/latest/actix_http/struct.Extensions.html) for examples.*
```rust
```rust,ignore
let conf: ConfigBag = aws_config::from_env()
// Configuration can be common to all smithy clients
.with(RetryConfig::builder().disable_retries().build())
@ -229,7 +229,7 @@ Setting configuration that will not be used wastes memory and can make debugging
Configuration has precedence. Configuration set on an operation will override configuration set on a client, and configuration set on a client will override default configuration. However, configuration with a higher precedence can also augment configuration with a lower precedence. For example:
```rust
```rust,ignore
let conf: ConfigBag = aws_config::from_env()
.with(
SomeConfig::builder()
@ -269,7 +269,7 @@ Config values are wrapped in a special enum called `Value` with three variants:
Builders are defined like this:
```rust
```rust,ignore
struct SomeBuilder {
value: Value<T>,
}
@ -316,7 +316,7 @@ Configuration is resolved in a fixed manner by reading the "lowest level" of con
*I've omitted some of the error conversion to shorten this example and make it easier to understand. The real version will be messier.*
```rust
```rust,ignore
/// `In`: The input message e.g. `ListObjectsRequest`
/// `Req`: The transport request message e.g. `http::Request<SmithyBody>`
/// `Res`: The transport response message e.g. `http::Response<SmithyBody>`
@ -452,7 +452,7 @@ async fn make_an_attempt<In, Req, Res, T>(
At various points in the execution of `invoke`, trait objects are fetched from the `ConfigBag`. These are preliminary definitions of those traits:
```rust
```rust,ignore
pub trait TraceProbe: Send + Sync + Debug {
fn dispatch_events(&self, cfg: &ConfigBag) -> BoxFallibleFut<()>;
}

View File

@ -41,7 +41,7 @@ service PokemonService {
Smithy Rust will use this model to produce the following API:
```rust
```rust,ignore
// A handler for the `GetPokemonSpecies` operation (the `PokemonSpecies` resource).
async fn get_pokemon_species(input: GetPokemonSpeciesInput) -> Result<GetPokemonSpeciesOutput, GetPokemonSpeciesError> {
/* implementation */
@ -67,7 +67,7 @@ A [Smithy Operation](https://awslabs.github.io/smithy/2.0/spec/service-types.htm
We represent this in Rust using the [`OperationShape`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693eb87b5853fe72629cd/rust-runtime/aws-smithy-http-server/src/operation/shape.rs#L8-L22) trait:
```rust
```rust,ignore
pub trait OperationShape {
/// The name of the operation.
const NAME: &'static str;
@ -97,7 +97,7 @@ operation GetPokemonSpecies {
the following implementation is generated
```rust
```rust,ignore
/// Retrieve information about a Pokémon species.
pub struct GetPokemonSpecies;
@ -118,7 +118,7 @@ The following nomenclature will aid us in our survey. We describe a `tower::Serv
In contrast to the marker ZSTs above, the [`Operation<S, L>`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693eb87b5853fe72629cd/rust-runtime/aws-smithy-http-server/src/operation/mod.rs#L192-L198) structure holds the actual runtime behavior of an operation, which is specified, during construction, by the customer.
```rust
```rust,ignore
/// A Smithy operation, represented by a [`Service`](tower::Service) `S` and a [`Layer`](tower::Layer) `L`.
///
/// The `L` is held and applied lazily during [`Upgradable::upgrade`].
@ -130,7 +130,7 @@ pub struct Operation<S, L = Identity> {
The `S` here is a model service, this is specified during construction of the `Operation<S, L>`. The constructors exist on the marker ZSTs as an extension trait to `OperationShape`, namely [`OperationShapeExt`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693eb87b5853fe72629cd/rust-runtime/aws-smithy-http-server/src/operation/shape.rs#L24-L45):
```rust
```rust,ignore
/// An extension trait over [`OperationShape`].
pub trait OperationShapeExt: OperationShape {
/// Creates a new [`Operation`] for well-formed [`Handler`]s.
@ -162,7 +162,7 @@ The [`Handler`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693
The `from_handler` constructor is used in the following way:
```rust
```rust,ignore
async fn get_pokemon_service(input: GetPokemonServiceInput) -> Result<GetPokemonServiceOutput, GetPokemonServiceError> {
/* Handler logic */
}
@ -172,7 +172,7 @@ let operation = GetPokemonService::from_handler(get_pokemon_service);
Alternatively, `from_service` constructor:
```rust
```rust,ignore
struct Svc {
/* ... */
}
@ -192,7 +192,7 @@ To summarize, the `S`, in `Operation<S, L>`, is a _model service_ constructed fr
Now, what about the `L` in `Operation<S, L>`? The `L` is a [`tower::Layer`](https://docs.rs/tower/latest/tower/layer/trait.Layer.html), or colloquially "middleware", that is applied to a _HTTP service_. Note that this means that `L` is _not_ applied directly to `S`. We can append to `L` using the `Operation::layer` method:
```rust
```rust,ignore
impl<S, L> Operation<S, L> {
/// Applies a [`Layer`] to the operation _after_ it has been upgraded via [`Operation::upgrade`].
pub fn layer<NewL>(self, layer: NewL) -> Operation<S, Stack<L, NewL>> {
@ -208,7 +208,7 @@ where [`tower::layer::util::Stack`](https://docs.rs/tower/latest/tower/layer/uti
A typical use of this might be:
```rust
```rust,ignore
let operation = GetPokemonSpecies::from_handler(handler).layer(RequestBodyLimitLayer::new(500));
```
@ -220,7 +220,7 @@ As mentioned, `L` is applied _after_ the `Operation<S, L>` has been "upgraded" t
A [Smithy protocol](https://awslabs.github.io/smithy/2.0/spec/protocol-traits.html#serialization-and-protocol-traits) specifies the serialization/deserialization scheme - how a HTTP request is transformed into a modelled input and a modelled output to a HTTP response. The is formalized using the [`FromRequest`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693eb87b5853fe72629cd/rust-runtime/aws-smithy-http-server/src/request.rs#L156-L164) and [`IntoResponse`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693eb87b5853fe72629cd/rust-runtime/aws-smithy-http-server/src/response.rs#L40-L44) traits:
```rust
```rust,ignore
/// Provides a protocol aware extraction from a [`Request`]. This consumes the
/// [`Request`], in contrast to [`FromParts`].
pub trait FromRequest<Protocol>: Sized {
@ -240,7 +240,7 @@ pub trait IntoResponse<Protocol> {
Note that both traits are parameterized by `Protocol`. These [protocols](https://awslabs.github.io/smithy/2.0/aws/protocols/index.html) exist as ZST marker structs:
```rust
```rust,ignore
/// [AWS REST JSON 1.0 Protocol](https://awslabs.github.io/smithy/2.0/aws/protocols/aws-restjson1-protocol.html).
pub struct RestJson1;
@ -274,7 +274,7 @@ stateDiagram-v2
This is formalized by the [`Upgrade<Protocol, Op, S>`](https://github.com/awslabs/smithy-rs/blob/9a6de1f533f8743dbbc3fa6ad974d104c8b841f4/rust-runtime/aws-smithy-http-server/src/operation/upgrade.rs#L74-L82) HTTP service. The `tower::Service` implementation is approximately:
```rust
```rust,ignore
impl<P, Op, S> Service<http::Request> for Upgrade<P, Op, S>
where
// `Op` is used to specify the operation shape
@ -321,7 +321,7 @@ Note that the `S` and `L` are specified by logic written, in Rust, by the custom
The procedure of taking a struct and transforming it into a HTTP service is formalized by the [`Upgradable`](https://github.com/awslabs/smithy-rs/blob/9a6de1f533f8743dbbc3fa6ad974d104c8b841f4/rust-runtime/aws-smithy-http-server/src/operation/upgrade.rs#L220-L225) trait:
```rust
```rust,ignore
/// An interface to convert a representation of a Smithy operation into a [`Route`].
pub trait Upgradable<Protocol, Operation> {
/// Upgrade the representation of a Smithy operation to a [`Route`].
@ -333,7 +333,7 @@ Why do we need a trait for this? Why not simply write an `upgrade` method on `Op
Below we give an example of a ZST which can be provided to the builder, which also satisfies `Upgradable` and returns a `MissingFailure` `tower::Service`. This `MissingFailure` service simply returns a status code 500.
```rust
```rust,ignore
/// A marker struct indicating an [`Operation`] has not been set in a builder.
///
/// This _does_ implement [`Upgradable`] but produces a [`Service`] which always returns an internal failure message.
@ -358,7 +358,7 @@ Different protocols supported by Smithy enjoy different routing mechanisms, for
Despite their differences, all routing mechanisms satisfy a common interface. This is formalized using the `Router` trait:
```rust
```rust,ignore
/// An interface for retrieving an inner [`Service`] given a [`http::Request`].
pub trait Router {
type Service;
@ -373,7 +373,7 @@ which provides the ability to determine an inner HTTP service from a collection
Types which implement the `Router` trait are converted to a HTTP service via the `RoutingService` struct:
```rust
```rust,ignore
/// A [`Service`] using a [`Router`] `R` to redirect messages to specific routes.
///
/// The `Protocol` parameter is used to determine the serialization of errors.
@ -432,7 +432,7 @@ You can create an instance of a service builder by calling either `builder_witho
> Plugins? What plugins? Don't worry, they'll be covered in a [dedicated section](#plugins) later on!
```rust
```rust,ignore
/// The service builder for [`PokemonService`].
///
/// Constructed via [`PokemonService::builder`].
@ -449,7 +449,7 @@ pub struct PokemonServiceBuilder<Body, Plugin> {
The builder has two setter methods for each [Smithy Operation](https://awslabs.github.io/smithy/2.0/spec/service-types.html#operation) in the [Smithy Service](https://awslabs.github.io/smithy/2.0/spec/service-types.html#service):
```rust
```rust,ignore
/// Sets the [`GetPokemonSpecies`](crate::operation_shape::GetPokemonSpecies) operation.
///
/// This should be an async function satisfying the [`Handler`](aws_smithy_http_server::operation::Handler) trait.
@ -499,7 +499,7 @@ Both builder methods take care of:
The final outcome, an instance of `PokemonService`, looks roughly like this:
```rust
```rust,ignore
/// The Pokémon Service allows you to retrieve information about Pokémon species.
#[derive(Clone)]
pub struct PokemonService<S> {
@ -560,7 +560,7 @@ After the build is finalized:
Although this provides a reasonably "complete" API, it can be cumbersome in some use cases. Suppose a customer wants to log the operation name when a request is routed to said operation. Writing a `Layer`, `NameLogger`, to log an operation name is simple, however with the current API the customer is forced to do the following
```rust
```rust,ignore
let get_pokemon_species = GetPokemonSpecies::from_handler(/* handler */).layer(NameLogger::new("GetPokemonSpecies"));
let get_storage = GetStorage::from_handler(/* handler */).layer(NameLogger::new("GetStorage"));
let do_nothing = DoNothing::from_handler(/* handler */).layer(NameLogger::new("DoNothing"));
@ -569,7 +569,7 @@ let do_nothing = DoNothing::from_handler(/* handler */).layer(NameLogger::new("D
Note that `PokemonService::layer` cannot be used here because it applies a _single_ layer uniformly across all `Route`s stored in the `Router`.
```rust
```rust,ignore
impl<S> PokemonService<S> {
/// Applies a [`Layer`](tower::Layer) uniformly to all routes.
pub fn layer<L>(self, layer: &L) -> PokemonService<L::Service>
@ -587,7 +587,7 @@ The plugin system solves the general problem of modifying `Operation<S, L>` prio
The central trait is [`Plugin`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693eb87b5853fe72629cd/rust-runtime/aws-smithy-http-server/src/plugin.rs#L31-L41):
```rust
```rust,ignore
/// A mapping from one [`Operation`] to another. Used to modify the behavior of
/// [`Upgradable`](crate::operation::Upgradable) and therefore the resulting service builder.
///
@ -603,7 +603,7 @@ pub trait Plugin<Protocol, Op, S, L> {
The `Upgradable::upgrade` method on `Operation<S, L>`, previously presented in [Upgrading a Model Service](#upgrading-a-model-service), is more accurately:
```rust
```rust,ignore
/// Takes the [`Operation<S, L>`](Operation), applies [`Plugin`], then applies [`UpgradeLayer`] to
/// the modified `S`, then finally applies the modified `L`.
///
@ -656,7 +656,7 @@ This constraint is in place to ensure that all handlers are upgraded using the s
You might find yourself wanting to apply _multiple_ plugins to your service.
This can be accommodated via [`PluginPipeline`].
```rust
```rust,ignore
use aws_smithy_http_server::plugin::PluginPipeline;
# use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
# use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;
@ -670,7 +670,7 @@ In the example above, `LoggingPlugin` would run first, while `MetricsPlugin` is
If you are vending a plugin, you can leverage `PluginPipeline` as an extension point: you can add custom methods to it using an extension trait.
For example:
```rust
```rust,ignore
use aws_smithy_http_server::plugin::{PluginPipeline, PluginStack};
# use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
# use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin;
@ -695,7 +695,7 @@ let pipeline = PluginPipeline::new()
An additional omitted detail is that we provide an "escape hatch" allowing `Handler`s and `OperationService`s to accept data that isn't modelled. In addition to accepting `Op::Input` they can accept additional arguments which implement the [`FromParts`](https://github.com/awslabs/smithy-rs/blob/4c5cbc39384f0d949d7693eb87b5853fe72629cd/rust-runtime/aws-smithy-http-server/src/request.rs#L114-L121) trait:
```rust
```rust,ignore
use http::request::Parts;
/// Provides a protocol aware extraction from a [`Request`]. This borrows the
@ -711,7 +711,7 @@ pub trait FromParts<Protocol>: Sized {
This differs from `FromRequest` trait, introduced in [Serialization and Deserialization](#serialization-and-deserialization), as it's synchronous and has non-consuming access to [`Parts`](https://docs.rs/http/0.2.8/http/request/struct.Parts.html), rather than the entire [Request](https://docs.rs/http/0.2.8/http/request/struct.Request.html).
```rust
```rust,ignore
pub struct Parts {
pub method: Method,
pub uri: Uri,
@ -724,7 +724,7 @@ pub struct Parts {
This is commonly used to access types stored within [`Extensions`](https://docs.rs/http/0.2.8/http/struct.Extensions.html) which have been inserted by a middleware. An `Extension` struct implements `FromParts` to support this use case:
```rust
```rust,ignore
/// Generic extension type stored in and extracted from [request extensions].
///
/// This is commonly used to share state across handlers.

View File

@ -1,6 +1,5 @@
# Generating Common Service Code
How a service is constructed and how to plug in new business logic is described in [Pokémon Service][1].
This document introduces the project and how code is being generated. It is written for developers who want to start contributing to `smithy-rs`.
## Folder structure
@ -12,37 +11,37 @@ The project is divided in:
- `/codegen-server`: server code generation. Depends on `codegen-core`
- `/aws`: the AWS Rust SDK, it deals with AWS services specifically. The folder structure reflects the project's, with the `rust-runtime` and the `codegen`
- `/rust-runtime`: the generated client and server crates may depend on crates in this folder. Crates here are not code generated. The only crate that is not published is `inlineable`,
which contains common functions used by other crates, [copied into][2] the source crate
which contains common functions used by other crates, [copied into][1] the source crate
Crates in `/rust-runtime` (informally referred to as "runtime crates") are added to a crate's dependency only when used.
For example, if a model uses event streams, the generated crates will depend on [`aws-smithy-eventstream`][3].
For example, if a model uses event streams, the generated crates will depend on [`aws-smithy-eventstream`][2].
## Generating code
`smithy-rs`'s entry points are Smithy code-generation plugins, and is not a command. One entry point is in [RustCodegenPlugin::execute][4] and
inherits from `SmithyBuildPlugin` in [smithy-build][5]. Code generation is in Kotlin and shared common, non-Rust specific code with the [`smithy` Java repository][6]. They plug into the [Smithy gradle][7] plugin, which is a gradle plugin.
`smithy-rs`'s entry points are Smithy code-generation plugins, and is not a command. One entry point is in [RustCodegenPlugin::execute][3] and
inherits from `SmithyBuildPlugin` in [smithy-build][4]. Code generation is in Kotlin and shared common, non-Rust specific code with the [`smithy` Java repository][5]. They plug into the [Smithy gradle][6] plugin, which is a gradle plugin.
The comment at the beginning of `execute` describes what a `Decorator` is and uses the following terms:
- Context: contains the model being generated, projection and settings for the build
- Decorator: (also referred to as customizations) customizes how code is being generated. AWS services are required to sign with the SigV4 protocol, and [a decorator][8] adds Rust code to sign requests and responses.
- Decorator: (also referred to as customizations) customizes how code is being generated. AWS services are required to sign with the SigV4 protocol, and [a decorator][7] adds Rust code to sign requests and responses.
Decorators are applied in reverse order of being added and have a priority order.
- Writer: creates files and adds content; it supports templating, using `#` for substitutions
- Location: the file where a symbol will be written to
The only task of a `RustCodegenPlugin` is to construct a `CodegenVisitor` and call its [execute()][9] method.
The only task of a `RustCodegenPlugin` is to construct a `CodegenVisitor` and call its [execute()][8] method.
`CodegenVisitor::execute()` is given a `Context` and decorators, and calls a [CodegenVisitor][10].
`CodegenVisitor::execute()` is given a `Context` and decorators, and calls a [CodegenVisitor][9].
CodegenVisitor, RustCodegenPlugin, and wherever there are different implementations between client and server, such as in generating error types,
have corresponding server versions.
Objects used throughout code generation are:
- Symbol: a node in a graph, an abstraction that represents the qualified name of a type; symbols reference and depend on other symbols, and have some common properties among languages (such as a namespace or a definition file). For Rust, we add properties to include more metadata about a symbol, such as its [type][11]
- [RustType][12]: `Option<T>`, `HashMap`, ... along with their namespaces of origin such as `std::collections`
- [RuntimeType][13]: the information to locate a type, plus the crates it depends on
- [ShapeId][14]: an immutable object that identifies a `Shape`
- Symbol: a node in a graph, an abstraction that represents the qualified name of a type; symbols reference and depend on other symbols, and have some common properties among languages (such as a namespace or a definition file). For Rust, we add properties to include more metadata about a symbol, such as its [type][10]
- [RustType][11]: `Option<T>`, `HashMap`, ... along with their namespaces of origin such as `std::collections`
- [RuntimeType][12]: the information to locate a type, plus the crates it depends on
- [ShapeId][13]: an immutable object that identifies a `Shape`
Useful conversions are:
@ -51,9 +50,9 @@ SymbolProvider.toSymbol(shape)
```
where `SymbolProvider` constructs symbols for shapes. Some symbols require to create other symbols and types;
[event streams][15] and [other streaming shapes][16] are an example.
Symbol providers are all [applied][17] in order; if a shape uses a reserved keyword in Rust, its name is converted to a new name by a [symbol provider][18],
and all other providers will work with this [new][19] symbol.
[event streams][14] and [other streaming shapes][15] are an example.
Symbol providers are all [applied][16] in order; if a shape uses a reserved keyword in Rust, its name is converted to a new name by a [symbol provider][17],
and all other providers will work with this [new][18] symbol.
```kotlin
Model.expectShape(shapeId)
@ -61,42 +60,41 @@ Model.expectShape(shapeId)
Each model has a `shapeId` to `shape` map; this method returns the shape associated with this shapeId.
Some objects implement a `transform` [method][20] that only change the input model, so that code generation will work on that new model. This is used to, for example, add a trait to a shape.
Some objects implement a `transform` [method][19] that only change the input model, so that code generation will work on that new model. This is used to, for example, add a trait to a shape.
`CodegenVisitor` is a `ShapeVisitor`. For all services in the input model, shapes are [converted into Rust][21];
[here][22] is how a service is constructed,
[here][23] a structure and so on.
`CodegenVisitor` is a `ShapeVisitor`. For all services in the input model, shapes are [converted into Rust][20];
[here][21] is how a service is constructed,
[here][22] a structure and so on.
Code generation flows from writer to files and entities are (mostly) generated only on a [need-by-need basis][24].
The complete result is a [Rust crate][25],
in which all dependencies are written into their modules and `lib.rs` is generated ([here][26]).
`execute()` ends by running [cargo fmt][27],
Code generation flows from writer to files and entities are (mostly) generated only on a [need-by-need basis][23].
The complete result is a [Rust crate][24],
in which all dependencies are written into their modules and `lib.rs` is generated ([here][25]).
`execute()` ends by running [cargo fmt][26],
to avoid having to format correctly Rust in `Writer`s and to be sure the generated code follows the styling rules.
[1]: ./pokemon_service.md
[2]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt#L95-L95
[3]: https://docs.rs/aws-smithy-eventstream
[4]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RustCodegenPlugin.kt#L34
[5]: https://github.com/awslabs/smithy/tree/main/smithy-build
[6]: https://github.com/awslabs/smithy
[7]: https://awslabs.github.io/smithy/1.0/guides/building-models/gradle-plugin.html
[8]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4SigningDecorator.kt#L45
[9]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L115-L115
[10]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L44
[11]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolVisitor.kt#L363-L363
[12]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypes.kt#L25-L25
[13]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt#L113-L113
[14]: https://awslabs.github.io/smithy/1.0/spec/core/model.html#shape-id
[15]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/EventStreamSymbolProvider.kt#L65-L65
[16]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/StreamingTraitSymbolProvider.kt#L26-L26
[17]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RustCodegenPlugin.kt#L62-L62
[18]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWords.kt#L26-L26
[19]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/EventStreamSymbolProvider.kt#L38-L38
[20]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/transformers/OperationNormalizer.kt#L52-L52
[21]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L119-L119
[22]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L150-L150
[23]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L172-L172
[24]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenDelegator.kt#L119-L126
[25]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenDelegator.kt#L42-L42
[26]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenDelegator.kt#L96-L107
[27]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L133-L133
[1]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt#L95-L95
[2]: https://docs.rs/aws-smithy-eventstream
[3]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RustCodegenPlugin.kt#L34
[4]: https://github.com/awslabs/smithy/tree/main/smithy-build
[5]: https://github.com/awslabs/smithy
[6]: https://awslabs.github.io/smithy/1.0/guides/building-models/gradle-plugin.html
[7]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4SigningDecorator.kt#L45
[8]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L115-L115
[9]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L44
[10]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/SymbolVisitor.kt#L363-L363
[11]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypes.kt#L25-L25
[12]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt#L113-L113
[13]: https://awslabs.github.io/smithy/1.0/spec/core/model.html#shape-id
[14]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/EventStreamSymbolProvider.kt#L65-L65
[15]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/StreamingTraitSymbolProvider.kt#L26-L26
[16]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RustCodegenPlugin.kt#L62-L62
[17]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustReservedWords.kt#L26-L26
[18]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/EventStreamSymbolProvider.kt#L38-L38
[19]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/transformers/OperationNormalizer.kt#L52-L52
[20]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L119-L119
[21]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L150-L150
[22]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L172-L172
[23]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenDelegator.kt#L119-L126
[24]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenDelegator.kt#L42-L42
[25]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenDelegator.kt#L96-L107
[26]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/CodegenVisitor.kt#L133-L133

View File

@ -6,7 +6,7 @@ But what if we, the customer, want to access data in the handler which is _not_
<!-- TODO(IntoParts): Dually, what if we want to return data from the handler which is _not_ modelled by our Smithy model? -->
```rust
```rust,ignore
/// Provides a protocol aware extraction from a [`Request`]. This borrows the
/// [`Parts`], in contrast to [`FromRequest`].
pub trait FromParts<Protocol>: Sized {
@ -22,7 +22,7 @@ Here [`Parts`](https://docs.rs/http/latest/http/request/struct.Parts.html) is th
A prolific example of a `FromParts` implementation is `Extension<T>`:
```rust
```rust,ignore
/// Generic extension type stored in and extracted from [request extensions].
///
/// This is commonly used to share state across handlers.
@ -61,7 +61,7 @@ where
This allows the service builder to accept the following handler
```rust
```rust,ignore
async fn handler(input: ModelInput, extension: Extension<SomeStruct>) -> ModelOutput {
/* ... */
}
@ -71,7 +71,7 @@ where `ModelInput` and `ModelOutput` are specified by the Smithy Operation and `
Up to 32 structures implementing `FromParts` can be provided to the handler with the constraint that they _must_ be provided _after_ the `ModelInput`:
```rust
```rust,ignore
async fn handler(input: ModelInput, ext1: Extension<SomeStruct1>, ext2: Extension<SomeStruct2>, other: Other /* : FromParts */, /* ... */) -> ModelOutput {
/* ... */
}
@ -81,7 +81,7 @@ Note that the `parts.extensions.remove::<T>()` in `Extensions::from_parts` will
The `FromParts` trait is public so customers have the ability specify their own implementations:
```rust
```rust,ignore
struct CustomerDefined {
/* ... */
}

View File

@ -19,7 +19,7 @@ RUST_LOG=aws_smithy_http_server=warn,aws_smithy_http_server_python=error
and
```rust
```rust,ignore,ignore
let filter = filter::Targets::new().with_target("aws_smithy_http_server", Level::DEBUG);
```
@ -55,7 +55,7 @@ Smithy provides an out-the-box middleware which:
This is enabled via the `instrument` method provided by the `aws_smithy_http_server::instrumentation::InstrumentExt` trait.
```rust
```rust,ignore
use aws_smithy_http_server::instrumentation::InstrumentExt;
let plugins = PluginPipeline::new().instrument();
@ -71,7 +71,7 @@ let app = PokemonService::builder_with_plugins(plugins)
The Pokémon service example, located at `/examples/pokemon-service`, sets up a `tracing` `Subscriber` as follows:
```rust
```rust,ignore,ignore
/// Setup `tracing::subscriber` to read the log level from RUST_LOG environment variable.
pub fn setup_tracing() {
let format = tracing_subscriber::fmt::layer().pretty();

View File

@ -49,7 +49,7 @@ The `Service` trait can be thought of as an asynchronous function from a request
Middleware in `tower` typically conforms to the following pattern, a `Service` implementation of the form
```rust
```rust,ignore
pub struct NewService<S> {
inner: S,
/* auxillary data */
@ -58,7 +58,7 @@ pub struct NewService<S> {
and a complementary
```rust
```rust,ignore
pub struct NewLayer {
/* auxiliary data */
}
@ -133,7 +133,7 @@ where `UpgradeLayer` is the `Layer` converting Smithy model structures to HTTP s
The output of the Smithy service builder provides the user with a `Service<http::Request, Response = http::Response>` implementation. A `Layer` can be applied around the entire `Service`.
```rust
```rust,ignore
// This is a HTTP `Service`.
let app /* : PokemonService<Route<B>> */ = PokemonService::builder_without_plugins()
.get_pokemon_species(/* handler */)
@ -151,7 +151,7 @@ let app = timeout_layer.layer(app);
A _single_ layer can be applied to _all_ routes inside the `Router`. This exists as a method on the output of the service builder.
```rust
```rust,ignore
// Construct `TraceLayer`.
let trace_layer = TraceLayer::new_for_http(Duration::from_secs(3));
@ -169,7 +169,7 @@ Note that requests pass through this middleware immediately _after_ routing succ
A "HTTP layer" can be applied to specific operations.
```rust
```rust,ignore
// Construct `TraceLayer`.
let trace_layer = TraceLayer::new_for_http(Duration::from_secs(3));
@ -189,8 +189,18 @@ This middleware transforms the operations HTTP requests and responses.
A "model layer" can be applied to specific operations.
```rust
# extern crate tower;
# extern crate pokemon_service_server_sdk;
# extern crate aws_smithy_http_server;
# use tower::{util::service_fn, Layer};
# use pokemon_service_server_sdk::{operation_shape::GetPokemonSpecies, PokemonService, input::*, output::*, error::*};
# use aws_smithy_http_server::operation::OperationShapeExt;
# let handler = |req: GetPokemonSpeciesInput| async { Result::<GetPokemonSpeciesOutput, GetPokemonSpeciesError>::Ok(todo!()) };
# struct BufferLayer;
# impl BufferLayer { pub fn new(size: usize) -> Self { Self } }
# impl<S> Layer<S> for BufferLayer { type Service = S; fn layer(&self, svc: S) -> Self::Service { svc } }
// A handler `Service`.
let handler_svc = service_fn(/* handler */);
let handler_svc = service_fn(handler);
// Construct `BufferLayer`.
let buffer_layer = BufferLayer::new(3);
@ -203,7 +213,10 @@ let layered_handler = GetPokemonSpecies::from_service(handler_svc);
let app /* : PokemonService<Route<B>> */ = PokemonService::builder_without_plugins()
.get_pokemon_species_operation(layered_handler)
/* ... */
.build();
.build()
# ;Result::<(), ()>::Ok(())
.unwrap();
# let app: Result<PokemonService<aws_smithy_http_server::routing::Route>, _> = app;
```
In contrast to [position C](#c-operation-specific-http-middleware), this middleware transforms the operations modelled inputs to modelled outputs.
@ -214,7 +227,7 @@ Suppose we want to apply a different `Layer` to every operation. In this case, p
Consider the following middleware:
```rust
```rust,ignore
/// A [`Service`] that adds a print log.
#[derive(Clone, Debug)]
pub struct PrintService<S> {
@ -261,7 +274,7 @@ The plugin system provides a way to construct then apply `Layer`s in position [C
An example of a `PrintPlugin` which applies a layer printing the operation name:
```rust
```rust,ignore
/// A [`Plugin`] for a service builder to add a [`PrintLayer`] over operations.
#[derive(Debug)]
pub struct PrintPlugin;
@ -281,7 +294,7 @@ where
An alternative example which applies a layer for a given protocol:
```rust
```rust,ignore
/// A [`Plugin`] for a service builder to add a [`PrintLayer`] over operations.
#[derive(Debug)]
pub struct PrintPlugin;
@ -309,7 +322,7 @@ impl<Op, S, L> Plugin<AwsRestXml, Op, S, L> for PrintPlugin
You can provide a custom method to add your plugin to a `PluginPipeline` via an extension trait:
```rust
```rust,ignore
/// This provides a [`print`](PrintExt::print) method on [`PluginPipeline`].
pub trait PrintExt<ExistingPlugins> {
/// Causes all operations to print the operation name when called.
@ -327,7 +340,7 @@ impl<ExistingPlugins> PrintExt<ExistingPlugins> for PluginPipeline<ExistingPlugi
This allows for:
```rust
```rust,ignore
let plugin_pipeline = PluginPipeline::new()
// [..other plugins..]
// The custom method!

View File

@ -7,4 +7,3 @@ Smithy Rust provides the ability to generate a server whose operations are provi
- [Accessing Un-modelled Data](./from_parts.md)
- [The Anatomy of a Service](./anatomy.md)
- [Generating Common Service Code](./code_generation.md)
- [Generating the Pokémon Service](./pokemon_service.md)

View File

@ -1,236 +0,0 @@
# Generating the Pokémon Service
This is an overview of client and server of the Pokémon service. It introduces:
- How a smithy-rs server customer uses the vanilla SDK and writes their business logic
- What the runtime is and how code is generated
- The folder structure of the project
All the code shown and linked to is from the repository at this commit: [db48039065bec890ef387385773b37154b555b14][1]
The Smithy model used to generate the code snippets is: [Pokémon][2]
## Building the service
The entry point of a service is [main.rs][3]
The `PokemonService` service in the `pokemon.smithy` has these operations and resources:
```smithy
resources: [PokemonSpecies, Storage],
operations: [GetServerStatistics, EmptyOperation, CapturePokemonOperation, HealthCheckOperation],
```
The entry app is constructed as:
```rust
let app: Router = OperationRegistryBuilder::default()
```
`OperationRegistryBuilder` is a struct, generated [here][4],
used by service implementors to register, for each operation, the operation's implementation logic, input and output.
```rust
pub struct OperationRegistry<B, Op0, In0, Op1, In1, Op2, In2, Op3, In3, Op4, In4, Op5, In5> {
capture_pokemon_operation: Op0,
empty_operation: Op1,
get_pokemon_species: Op2,
get_server_statistics: Op3,
get_storage: Op4,
health_check_operation: Op5,
_phantom: std::marker::PhantomData<(B, In0, In1, In2, In3, In4, In5)>,
}
```
The builder is constructed by a `OperationRegistryBuilder`; if an operation is not passed to the builder, it will return an error.
```rust
let app: Router = OperationRegistryBuilder::default()
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage)
.get_server_statistics(get_server_statistics)
.capture_pokemon_operation(capture_pokemon)
.empty_operation(empty_operation)
.health_check_operation(health_check_operation)
.build()
.expect("Unable to build operation registry")
.into();
```
Each of these operations is a function that can take any of these signatures.
1. If the operation is not fallible and does not share any state:
```rust
pub async fn health_check_operation(_input: input::HealthCheckOperationInput) -> output::HealthCheckOperationOutput {...}
```
2. If the operation is fallible and does not share any state:
```rust
pub async fn capture_pokemon(
mut input: input::CapturePokemonOperationInput,
) -> Result<output::CapturePokemonOperationOutput, error::CapturePokemonOperationError> {...}
```
3. If the operation is not fallible and shares some state:
```rust
pub async fn get_server_statistics(
_input: input::GetServerStatisticsInput,
state: Extension<Arc<State>>,
) -> output::GetServerStatisticsOutput {...}
```
4. If the operation is fallible and shares some state:
```rust
pub async fn get_storage(
input: input::GetStorageInput,
_state: Extension<Arc<State>>,
) -> Result<output::GetStorageOutput, error::GetStorageError> {...}
```
All of these are operations which implementors define; they are the business logic of the application. The rest is code generated.
The `OperationRegistry` builds into a `Router` (`let app: Router = OperationRegistryBuilder...build().into()`).
The implementation is code generated [here][5].
```rust
impl<B, Op0, In0, Op1, In1, Op2, In2, Op3, In3, Op4, In4, Op5, In5>
std::convert::From<
OperationRegistry<B, Op0, In0, Op1, In1, Op2, In2, Op3, In3, Op4, In4, Op5, In5>,
> for aws_smithy_http_server::routing::Router<B>
where
B: Send + 'static,
Op0: crate::server_operation_handler_trait::Handler<
B,
In0,
crate::input::CapturePokemonOperationInput,
>,
In0: 'static + Send,
... for all Op, In
{
fn from(
registry: OperationRegistry<B, Op0, In0, Op1, In1, Op2, In2, Op3, In3, Op4, In4, Op5, In5>,
) -> Self {...}
}
```
For each operation, it registers a route; the specifics depend on the [protocol][6].
The PokemonService uses [restJson1][7] as its protocol, an operation like the `HealthCheckOperation` will be rendered as:
```rust
let capture_pokemon_operation_request_spec = aws_smithy_http_server::routing::request_spec::RequestSpec::new(
http::Method::POST,
aws_smithy_http_server::routing::request_spec::UriSpec::new(
aws_smithy_http_server::routing::request_spec::PathAndQuerySpec::new(
aws_smithy_http_server::routing::request_spec::PathSpec::from_vector_unchecked(vec![
aws_smithy_http_server::routing::request_spec::PathSegment::Literal(String::from("capture-pokemon-event")),
aws_smithy_http_server::routing::request_spec::PathSegment::Label,
]),
aws_smithy_http_server::routing::request_spec::QuerySpec::from_vector_unchecked(vec![]))),);
```
because the URI is `/capture-pokemon-event/{region}`, with method `POST` and `region` a `Label` (then passed to the operation with its `CapturePokemonOperationInput` input struct).
Finally, it creates a RestJSON `Router`, because that is the service's protocol.
You will have noticed, each operation is implemented as a `pub async fn`. Each operation is wrapped into an `OperationHandler`, generated [here][8].
`OperationHandler` implements tower's `Service` [trait][9]. Implementing `Service` means that
the business logic is written as protocol-agnostic and clients request a service by calling into them, similar to an RPC call.
```rust
aws_smithy_http_server::routing::Router::new_rest_json_router(vec![
{
let svc = crate::server_operation_handler_trait::operation(
registry.capture_pokemon_operation,
);
```
At this level, logging might be prohibited by the [`@sensitive`][10] trait. If there are no `@sensitive` shapes, the generated code looks like:
```rust
let request_fmt = aws_smithy_http_server::instrumentation::sensitivity::RequestFmt::new();
let response_fmt = aws_smithy_http_server::instrumentation::sensitivity::ResponseFmt::new();
let svc = aws_smithy_http_server::instrumentation::InstrumentOperation::new(
svc,
"capture_pokemon_operation",
)
.request_fmt(request_fmt)
.response_fmt(response_fmt);
```
Accessing the Pokédex is modeled as a restricted operation: a passcode is needed by the Pokémon trainer.
To not log the passcode, the code will be generated [here][11] as:
```rust
let request_fmt = aws_smithy_http_server::instrumentation::sensitivity::RequestFmt::new()
.header(|name: &http::header::HeaderName| {
#[allow(unused_variables)]
let name = name.as_str();
let name_match = matches!(name, "passcode");
let key_suffix = None;
let value = name_match;
aws_smithy_http_server::instrumentation::sensitivity::headers::HeaderMarker {
value,
key_suffix,
}
})
.label(|index: usize| matches!(index, 1));
let response_fmt = aws_smithy_http_server::instrumentation::sensitivity::ResponseFmt::new();
```
Each route is a pair, [`BoxCloneService`][12] wrapping the service operation (the implementation) and
the information to consume the service operation.
```rust
(tower::util::BoxCloneService::new(svc), capture_pokemon_operation_request_spec)
```
Now the `Router` is built. `Router` is not code generated, it instead lives in the [`aws-smithy-http-server`][13] crate.
We write Rust code in the runtime to:
- Aid development of the project
- Have service-specific code that the majority of services share in the runtime
In Kotlin we generate code that is service-specific.
A `Router` is a [`tower::Service`][9] that routes requests to the implemented services; hence it [implements][14] `Service`
like the other operations.
The `Router` [implements][15]
the `tower::Layer` trait. Middleware are added as layers. The Pokémon example uses them:
```rust
let shared_state = Arc::new(State::default());
let app = app.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(AddExtensionLayer::new(shared_state)),
);
```
The service is run by a [Hyper server][16]:
```rust
hyper::Server::bind(&bind).serve(app.into_make_service());
```
Generation of objects common to services, such as shapes, is described in [Code Generation][17].
[1]: https://github.com/awslabs/smithy-rs/tree/db48039065bec890ef387385773b37154b555b14
[2]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen-server-test/model/pokemon.smithy
[3]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/main.rs#L34
[4]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationRegistryGenerator.kt#L1
[5]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationRegistryGenerator.kt#L285
[6]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/protocols/Protocol.kt#L81
[7]: https://awslabs.github.io/smithy/1.0/spec/aws/aws-restjson1-protocol.html
[8]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationHandlerGenerator.kt#L30
[9]: https://docs.rs/tower-service/latest/tower_service/trait.Service.html
[10]: https://awslabs.github.io/smithy/1.0/spec/core/documentation-traits.html#sensitive-trait
[11]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerHttpSensitivityGenerator.kt#L58
[12]: https://docs.rs/tower/latest/tower/util/struct.BoxCloneService.html
[13]: https://docs.rs/aws-smithy-http-server/latest/aws_smithy_http_server/
[14]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/rust-runtime/aws-smithy-http-server/src/routing/mod.rs#L302
[15]: https://github.com/awslabs/smithy-rs/blob/db48039065bec890ef387385773b37154b555b14/rust-runtime/aws-smithy-http-server/src/routing/mod.rs#L146
[16]: https://docs.rs/hyper/latest/hyper/server/struct.Server.html
[17]: ./code_generation.md

View File

@ -28,7 +28,7 @@ Smithy `structure` becomes a `struct` in Rust. Backwards compatibility & usabili
2. All structs are marked `#[non_exhaustive]`
3. All structs derive `Debug` & `PartialEq`. Structs **do not** derive `Eq` because a `float` member may be added in the future.
4. Struct fields are public. Public struct fields allow for [split borrows](https://doc.rust-lang.org/nomicon/borrow-splitting.html). When working with output objects this significantly improves ergonomics, especially with optional fields.
```rust
```rust,ignore
let out = dynamo::ListTablesOutput::new();
out.some_field.unwrap(); // <- partial move, impossible with an accessor
```
@ -52,7 +52,7 @@ long ReadIOs
long WriteIOs
```
**Rust Output**:
```rust
```rust,ignore
/// <p>Contains I/O usage metrics for a command that was invoked.</p>
#[non_exhaustive]
#[derive(std::clone::Clone, std::cmp::PartialEq)]
@ -151,7 +151,7 @@ list BoolList {
}
```
**Rust**:
```rust
```rust,ignore
#[non_exhaustive]
#[derive(std::clone::Clone, std::cmp::PartialEq, std::fmt::Debug)]
pub enum AttributeValue {

View File

@ -62,7 +62,7 @@ Specifically, [`#[non_exhaustive]`](https://doc.rust-lang.org/reference/attribut
following patterns:
1. Direct structure instantiation:
```rust
```rust,ignore
# fn foo() {
let ip_addr = IpAddress { addr: "192.168.1.1" };
# }
@ -72,20 +72,20 @@ following patterns:
our structures while maintaining backwards compatibility, all structures expose a builder, accessible
at `SomeStruct::Builder`:
```rust
```rust,ignore
# fn foo() {
let ip_addr = IpAddress::builder().addr("192.168.1.1").build();
# }
```
2. Structure destructuring:
```rust
```rust,ignore
# fn foo() {
let IpAddress { addr } = some_ip_addr();
# }
```
This will also fail to compile if a new member is added, however, by adding `#[non_exhaustive]`, the `..` multifield
wildcard MUST be added to support new fields being added in the future:
```rust
```rust,ignore
# fn foo() {
let IpAddress { addr, .. } = some_ip_addr();
# }
@ -110,7 +110,7 @@ because new fields cannot be added to union variants, the union variants themsel
to be `#[non_exhaustive]`. To support new variants from services, each union contains an `Unknown` variant. By
marking `Unknown` as non_exhaustive, we prevent customers from instantiating it directly.
```rust
```rust,ignore
#[non_exhaustive]
#[derive(std::clone::Clone, std::cmp::PartialEq, std::fmt::Debug)]
pub enum AttributeValue {

View File

@ -4,7 +4,7 @@
The core codegen generates HTTP requests that do not contain an authority, scheme or post. These properties must be set later based on configuration. Existing AWS services have a number of requirements that increase the complexity:
1. Endpoints must support manual configuration by end users:
```rust
```rust,ignore
let config = dynamodb::Config::builder()
.endpoint(StaticEndpoint::for_uri("http://localhost:8000"))
```
@ -14,7 +14,7 @@ When a user specifies a custom endpoint URI, _typically_ they will want to avoid
2. Endpoints must support being customized on a per-operation basis by the endpoint trait. This will prefix the base endpoint, potentially driven by fields of the operation. [Docs](https://awslabs.github.io/smithy/1.0/spec/core/endpoint-traits.html#endpoint-trait)
3. Endpoints must support being customized by [endpoint discovery](https://awslabs.github.io/smithy/1.0/spec/aws/aws-core.html#client-endpoint-discovery). A request, customized by a predefined set of fields from the input operation is dispatched to a specific URI. That operation returns the endpoint that should be used. Endpoints must be cached by a cache key containing:
```
```markdown
(access_key_id, [all input fields], operation)
```
Endpoints retrieved in this way specify a TTL.
@ -29,7 +29,7 @@ Configuration objects for services _must_ contain an `Endpoint`. This endpoint m
During operation construction (see [Operation Construction](../transport/operation.md#operation-construction)) an `EndpointPrefix` may be set on the property bag. The eventual endpoint middleware will search for this in the property bag and (depending on the URI mutability) utilize this prefix when setting the endpoint.
In the case of endpoint discovery, we envision a different pattern:
```rust
```rust,ignore
// EndpointClient manages the endpoint cache
let (tx, rx) = dynamodb::EndpointClient::new();
let client = aws_hyper::Client::new();

View File

@ -51,7 +51,7 @@ Current models represent strings as `String`.
Smithy defines the concept of "Document Types":
> [Documents represent] protocol-agnostic open content that is accessed like JSON data. Open content is useful for modeling unstructured data that has no schema, data that can't be modeled using rigid types, or data that has a schema that evolves outside of the purview of a model. The serialization format of a document is an implementation detail of a protocol and MUST NOT have any effect on the types exposed by tooling to represent a document value.
```rust
```rust,ignore
{{#include ../../../rust-runtime/aws-smithy-types/src/lib.rs:document}}
```

View File

@ -16,7 +16,7 @@ This section details the flow of a request through the SDK until a response is r
A customer interacts with the SDK builders to construct an input. The `build()` method on an input returns
an `Operation<Output>`. This codifies the base HTTP request & all the configuration and middleware layers required to modify and dispatch the request.
```rust
```rust,ignore
pub struct Operation<H, R> {
request: Request,
response_handler: H,
@ -37,7 +37,7 @@ By using a property bag, we can define the `Operation` in Smithy core. AWS speci
In order to construct an operation, the generated code injects appropriate middleware & configuration via the configuration property bag. It does this by reading the configuration properties out of the service
config, copying them as necessary, and loading them into the `Request`:
```rust
```rust,ignore
// This is approximately the generated code, I've cleaned a few things up for readability.
pub fn build(self, config: &dynamodb::config::Config) -> Operation<BatchExecuteStatement> {
let op = BatchExecuteStatement::new(BatchExecuteStatementInput {

View File

@ -142,6 +142,17 @@ FROM install_rust AS cargo_semver_checks
ARG cargo_semver_checks_version=0.20.0
ARG rust_nightly_version
RUN cargo +${rust_nightly_version} -Z sparse-registry install cargo-semver-checks --locked --version ${cargo_semver_checks_version}
FROM install_rust AS cargo_mdbook
ARG cargo_mdbook_version=0.4.30
ARG rust_nightly_version
RUN cargo +${rust_nightly_version} -Z sparse-registry install mdbook --locked --version ${cargo_mdbook_version}
FROM install_rust AS cargo_mdbook_mermaid
ARG cargo_mdbook_mermaid_version=0.12.6
ARG rust_nightly_version
RUN cargo +${rust_nightly_version} -Z sparse-registry install mdbook-mermaid --locked --version ${cargo_mdbook_mermaid_version}
#
# Final image
#
@ -179,6 +190,8 @@ COPY --chown=build:build --from=wasmtime /opt/wasmtime /opt/cargo/bin/wasmtime
COPY --chown=build:build --from=cargo_wasi /opt/cargo/bin/cargo-wasi /opt/cargo/bin/cargo-wasi
COPY --chown=build:build --from=install_rust /opt/rustup /opt/rustup
COPY --chown=build:build --from=cargo_semver_checks /opt/cargo/bin/cargo-semver-checks /opt/cargo/bin/cargo-semver-checks
COPY --chown=build:build --from=cargo_mdbook /opt/cargo/bin/mdbook /opt/cargo/bin/mdbook
COPY --chown=build:build --from=cargo_mdbook_mermaid /opt/cargo/bin/mdbook-mermaid /opt/cargo/bin/mdbook-mermaid
COPY --chown=build:build --from=musl_toolchain /usr/local/musl/ /usr/local/musl/
ENV PATH=$PATH:/usr/local/musl/bin/
ENV PATH=/opt/cargo/bin:$PATH \

14
tools/ci-scripts/check-book Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
#
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
#
set -eux
cd smithy-rs/examples
make
cargo b
cd ..
mdbook test design -L target/debug/deps