From 29a900e74d1037306821fecd8295dbc1ca735dc8 Mon Sep 17 00:00:00 2001 From: Harry Barber <106155934+hlbarber@users.noreply.github.com> Date: Wed, 7 Jun 2023 10:01:16 +0100 Subject: [PATCH] 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 Co-authored-by: 82marbag <69267416+82marbag@users.noreply.github.com> --- .github/workflows/ci.yml | 2 + ci.mk | 4 + design/book.toml | 5 +- design/src/SUMMARY.md | 1 - design/src/client/identity_and_auth.md | 8 +- design/src/client/orchestrator.md | 6 +- ...a_low-level_feature_that_relies_on_HTTP.md | 16 +- design/src/overview.md | 4 +- design/src/rfcs/rfc0001_shared_config.md | 14 +- design/src/rfcs/rfc0002_http_versions.md | 16 +- design/src/rfcs/rfc0003_presigning_api.md | 6 +- design/src/rfcs/rfc0004_retry_behavior.md | 10 +- design/src/rfcs/rfc0008_paginators.md | 10 +- design/src/rfcs/rfc0010_waiters.md | 12 +- design/src/rfcs/rfc0013_body_callback_apis.md | 8 +- design/src/rfcs/rfc0014_timeout_config.md | 6 +- .../rfc0015_using_features_responsibly.md | 2 +- .../rfcs/rfc0016_flexible_checksum_support.md | 20 +- .../rfc0017_customizable_client_operations.md | 12 +- design/src/rfcs/rfc0018_logging_sensitive.md | 18 +- .../src/rfcs/rfc0019_event_streams_errors.md | 10 +- design/src/rfcs/rfc0020_service_builder.md | 58 ++--- ...rfc0022_error_context_and_compatibility.md | 16 +- design/src/rfcs/rfc0023_refine_builder.md | 60 ++--- design/src/rfcs/rfc0024_request_id.md | 8 +- design/src/rfcs/rfc0025_constraint_traits.md | 10 +- .../rfcs/rfc0026_client_crate_organization.md | 4 +- design/src/rfcs/rfc0027_endpoints_20.md | 16 +- ...fc0028_sdk_credential_cache_type_safety.md | 22 +- .../rfcs/rfc0029_new_home_for_cred_types.md | 10 +- ...c0030_serialization_and_deserialization.md | 10 +- ...oviding_fallback_credentials_on_timeout.md | 22 +- .../rfc0032_better_constraint_violations.md | 30 +-- .../rfc0033_improve_sdk_request_id_access.md | 8 +- .../src/rfcs/rfc0034_smithy_orchestrator.md | 20 +- design/src/server/anatomy.md | 56 ++--- design/src/server/code_generation.md | 98 ++++---- design/src/server/from_parts.md | 10 +- design/src/server/instrumentation.md | 6 +- design/src/server/middleware.md | 37 ++- design/src/server/overview.md | 1 - design/src/server/pokemon_service.md | 236 ------------------ design/src/smithy/aggregate_shapes.md | 6 +- design/src/smithy/backwards-compat.md | 10 +- design/src/smithy/endpoint.md | 6 +- design/src/smithy/simple_shapes.md | 2 +- design/src/transport/operation.md | 4 +- tools/ci-build/Dockerfile | 13 + tools/ci-scripts/check-book | 14 ++ 49 files changed, 396 insertions(+), 587 deletions(-) delete mode 100644 design/src/server/pokemon_service.md create mode 100755 tools/ci-scripts/check-book diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3ce1bd51..fa47463ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/ci.mk b/ci.mk index ecf6a3363..a7022355e 100644 --- a/ci.mk +++ b/ci.mk @@ -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) diff --git a/design/book.toml b/design/book.toml index f8cfdd021..3c6fffdaf 100644 --- a/design/book.toml +++ b/design/book.toml @@ -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" diff --git a/design/src/SUMMARY.md b/design/src/SUMMARY.md index 6b3510a64..ea3bcb65c 100644 --- a/design/src/SUMMARY.md +++ b/design/src/SUMMARY.md @@ -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) diff --git a/design/src/client/identity_and_auth.md b/design/src/client/identity_and_auth.md index e6cfb2cda..87aa8b9c6 100644 --- a/design/src/client/identity_and_auth.md +++ b/design/src/client/identity_and_auth.md @@ -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` 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, @@ -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; } diff --git a/design/src/client/orchestrator.md b/design/src/client/orchestrator.md index b773d5b13..d23088dcc 100644 --- a/design/src/client/orchestrator.md +++ b/design/src/client/orchestrator.md @@ -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 Client 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; diff --git a/design/src/contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md b/design/src/contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md index 8a15f29f4..3e9fa6a76 100644 --- a/design/src/contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md +++ b/design/src/contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md @@ -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 { 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}, diff --git a/design/src/overview.md b/design/src/overview.md index 975532c61..6ae40d735 100644 --- a/design/src/overview.md +++ b/design/src/overview.md @@ -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(); diff --git a/design/src/rfcs/rfc0001_shared_config.md b/design/src/rfcs/rfc0001_shared_config.md index bd2a04ebb..b0abd7750 100644 --- a/design/src/rfcs/rfc0001_shared_config.md +++ b/design/src/rfcs/rfc0001_shared_config.md @@ -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 │ └─────────────────┘ └───────────────┘ diff --git a/design/src/rfcs/rfc0002_http_versions.md b/design/src/rfcs/rfc0002_http_versions.md index 3c8b87ab6..1acfe48f3 100644 --- a/design/src/rfcs/rfc0002_http_versions.md +++ b/design/src/rfcs/rfc0002_http_versions.md @@ -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 { client: aws_smithy_client::Client, conf: crate::Config, @@ -59,7 +59,7 @@ pub struct Client { 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 { fluent_builders::AssumeRole::new(self.handle.clone()) } @@ -68,7 +68,7 @@ pub fn assume_role(&self) -> fluent_builders::AssumeRole { 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 { handle: std::sync::Arc>, inner: crate::input::assume_role_input::Builder, @@ -86,7 +86,7 @@ impl AssumeRole 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 AssumeRole where ... { pub async fn send(self) -> Result> where ... { let input = self.create_input()?; diff --git a/design/src/rfcs/rfc0003_presigning_api.md b/design/src/rfcs/rfc0003_presigning_api.md index e8c6cef4b..c75230751 100644 --- a/design/src/rfcs/rfc0003_presigning_api.md +++ b/design/src/rfcs/rfc0003_presigning_api.md @@ -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() diff --git a/design/src/rfcs/rfc0004_retry_behavior.md b/design/src/rfcs/rfc0004_retry_behavior.md index ee3672ca3..3da28afdc 100644 --- a/design/src/rfcs/rfc0004_retry_behavior.md +++ b/design/src/rfcs/rfc0004_retry_behavior.md @@ -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; diff --git a/design/src/rfcs/rfc0008_paginators.md b/design/src/rfcs/rfc0008_paginators.md index a387af4b0..fc0a0c8b6 100644 --- a/design/src/rfcs/rfc0008_paginators.md +++ b/design/src/rfcs/rfc0008_paginators.md @@ -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 { // holds the low-level client and configuration @@ -55,7 +55,7 @@ struct ListTablesPaginator { 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 ListTablesPaginator { @@ -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). diff --git a/design/src/rfcs/rfc0010_waiters.md b/design/src/rfcs/rfc0010_waiters.md index f738f0f35..3d0f80b29 100644 --- a/design/src/rfcs/rfc0010_waiters.md +++ b/design/src/rfcs/rfc0010_waiters.md @@ -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, retry::Standard>>, input: HeadBucketInput, diff --git a/design/src/rfcs/rfc0013_body_callback_apis.md b/design/src/rfcs/rfc0013_body_callback_apis.md index b6b8e93e6..ea9b024c1 100644 --- a/design/src/rfcs/rfc0013_body_callback_apis.md +++ b/design/src/rfcs/rfc0013_body_callback_apis.md @@ -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, diff --git a/design/src/rfcs/rfc0014_timeout_config.md b/design/src/rfcs/rfc0014_timeout_config.md index d4295f379..52b1496ec 100644 --- a/design/src/rfcs/rfc0014_timeout_config.md +++ b/design/src/rfcs/rfc0014_timeout_config.md @@ -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 Client 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 Client where C: bounds::SmithyConnector, diff --git a/design/src/rfcs/rfc0015_using_features_responsibly.md b/design/src/rfcs/rfc0015_using_features_responsibly.md index 2b018c64f..336b25ca8 100644 --- a/design/src/rfcs/rfc0015_using_features_responsibly.md +++ b/design/src/rfcs/rfc0015_using_features_responsibly.md @@ -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> { Some(sleep_tokio()) diff --git a/design/src/rfcs/rfc0016_flexible_checksum_support.md b/design/src/rfcs/rfc0016_flexible_checksum_support.md index aa0851c61..ca906057a 100644 --- a/design/src/rfcs/rfc0016_flexible_checksum_support.md +++ b/design/src/rfcs/rfc0016_flexible_checksum_support.md @@ -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 { 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` 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`, checksum algorithm name, and pre-calculated checksum, return a /// `Response` where the body will processed with the checksum algorithm and checked /// against the pre-calculated checksum. diff --git a/design/src/rfcs/rfc0017_customizable_client_operations.md b/design/src/rfcs/rfc0017_customizable_client_operations.md index e91f4d625..260635b79 100644 --- a/design/src/rfcs/rfc0017_customizable_client_operations.md +++ b/design/src/rfcs/rfc0017_customizable_client_operations.md @@ -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 { handle: Arc, operation: Operation, @@ -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() diff --git a/design/src/rfcs/rfc0018_logging_sensitive.md b/design/src/rfcs/rfc0018_logging_sensitive.md index 4ae6a27dd..9598c823c 100644 --- a/design/src/rfcs/rfc0018_logging_sensitive.md +++ b/design/src/rfcs/rfc0018_logging_sensitive.md @@ -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>` that we provide to them. -```rust +```rust,ignore // The API provides us with a `Service>` 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); impl Debug for Sensitive @@ -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 { @@ -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 Service> for Middleware { 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 Service> for SensitivityInserter where S: Service> @@ -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 { inner: S, sensitivity: Sensitivity diff --git a/design/src/rfcs/rfc0019_event_streams_errors.md b/design/src/rfcs/rfc0019_event_streams_errors.md index 35274276f..83311d902 100644 --- a/design/src/rfcs/rfc0019_event_streams_errors.md +++ b/design/src/rfcs/rfc0019_event_streams_errors.md @@ -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() diff --git a/design/src/rfcs/rfc0020_service_builder.md b/design/src/rfcs/rfc0020_service_builder.md index 35bf97f1f..c6e18b9c1 100644 --- a/design/src/rfcs/rfc0020_service_builder.md +++ b/design/src/rfcs/rfc0020_service_builder.md @@ -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 { 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 Handler<(), Operation0Input> for Fun where Fun: FnOnce(Operation0Input) -> Fut, @@ -122,7 +122,7 @@ The `request.extensions().get::()` 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 { 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 { operation1: Option, operation2: Option, @@ -165,7 +165,7 @@ pub struct OperationRegistry { The `OperationRegistryBuilder` includes a setter per operation, and a fallible `build` method: -```rust +```rust,ignore impl OperationRegistryBuilder { pub fn operation0(mut self, value: Op0) -> Self { self.operation0 = Some(value); @@ -188,7 +188,7 @@ impl OperationRegistryBuilder { The `OperationRegistry` does not include any methods of its own, however it does enjoy a `From for Router` implementation: -```rust +```rust,ignore impl From> for Router where Op0: Handler, @@ -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>, } @@ -242,7 +242,7 @@ pub struct Router { and enjoys the following `Service` implementation: -```rust +```rust,ignore impl Service for Router { type Response = http::Response; @@ -268,7 +268,7 @@ impl Service 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, Query(params): Query>, 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) -> /* 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, 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 { 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 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 for Router` (see [Handlers](#handlers)). The setter would then become: -```rust +```rust,ignore pub struct OperationRegistryBuilder { operation1: Option, operation2: Option @@ -494,7 +494,7 @@ impl OperationRegistryBuilder { 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 OperationRegistryBuilder { fn operation0_handler(self, handler: H) -> OperationRegistryBuilder, 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 { 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 Operation0 { @@ -594,7 +594,7 @@ impl Operation0> { 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`, allows us to write: -```rust +```rust,ignore impl Router { fn layer(self, layer: &L) -> Router 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 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 */ } diff --git a/design/src/rfcs/rfc0022_error_context_and_compatibility.md b/design/src/rfcs/rfc0022_error_context_and_compatibility.md index cf03b2ed8..c8791dbfa 100644 --- a/design/src/rfcs/rfc0022_error_context_and_compatibility.md +++ b/design/src/rfcs/rfc0022_error_context_and_compatibility.md @@ -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(pub E); diff --git a/design/src/rfcs/rfc0023_refine_builder.md b/design/src/rfcs/rfc0023_refine_builder.md index d93cd6bf7..1c0b6f978 100644 --- a/design/src/rfcs/rfc0023_refine_builder.md +++ b/design/src/rfcs/rfc0023_refine_builder.md @@ -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( builder: PokemonServiceBuilder, ) -> PokemonServiceBuilder { @@ -432,7 +432,7 @@ fn partial_setup( That compiles, at last. Let's try to register an operation handler now: -```rust +```rust,ignore fn partial_setup( builder: PokemonServiceBuilder, ) -> PokemonServiceBuilder { @@ -468,7 +468,7 @@ Can we get rid of them? Yes! Let's look at one possible approach: -```rust +```rust,ignore pub struct PokemonServiceBuilder { check_health: Option>, do_nothing: Option>, @@ -483,7 +483,7 @@ pub struct PokemonServiceBuilder { 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 PokemonServiceBuilder { pub fn get_pokemon_species(mut self, handler: Handler) -> Self /* Complex trait bounds */ @@ -500,7 +500,7 @@ impl PokemonServiceBuilder { 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(plugin: Plugin) -> PokemonServiceBuilder { @@ -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 Pluggable for Operation where Pl: Plugin { type Output = Operation; @@ -561,7 +561,7 @@ impl Pluggable for Operation where Pl: 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`, 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` 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( builder: PokemonServiceBuilder, ) -> PokemonServiceBuilder { @@ -693,7 +693,7 @@ fn partial_setup( 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( builder: PokemonServiceBuilder, ) -> PokemonServiceBuilder< @@ -706,7 +706,7 @@ fn partial_setup( 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( builder: PokemonServiceBuilder, ) -> PokemonServiceBuilder, Route, Route, Route, Route, Route> { @@ -716,7 +716,7 @@ fn partial_setup( The compiler would reject it since it can't guarantee that all other operations can be erased to a `Route`. This is likely to require something along the lines of: -```rust +```rust,ignore fn partial_setup( builder: PokemonServiceBuilder, ) -> PokemonServiceBuilder<::Erased, ::Erased, ::Erased, ::Erased, ::Erased, ::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 { /// Causes all operations to print the operation name when called. @@ -760,7 +760,7 @@ pub trait PrintExt: aws_smithy_http_server::plugin::Pluggable { 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() diff --git a/design/src/rfcs/rfc0024_request_id.md b/design/src/rfcs/rfc0024_request_id.md index a10d5e92e..5c06d1061 100644 --- a/design/src/rfcs/rfc0024_request_id.md +++ b/design/src/rfcs/rfc0024_request_id.md @@ -43,13 +43,13 @@ To aid customers already relying on clients' request IDs, there will be two type 1. Implementing `FromParts` for `Extension` gives customers the ability to write their handlers: -```rust +```rust,ignore pub async fn handler( input: input::Input, request_id: Extension, ) -> ... ``` -```rust +```rust,ignore pub async fn handler( input: input::Input, request_id: Extension, @@ -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 Service> for ServerRequestIdProvider where S: Service>, @@ -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 Service> for ClientRequestIdProvider where S: Service>, diff --git a/design/src/rfcs/rfc0025_constraint_traits.md b/design/src/rfcs/rfc0025_constraint_traits.md index a60b24e57..e3aaa3231 100644 --- a/design/src/rfcs/rfc0025_constraint_traits.md +++ b/design/src/rfcs/rfc0025_constraint_traits.md @@ -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 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 { diff --git a/design/src/rfcs/rfc0026_client_crate_organization.md b/design/src/rfcs/rfc0026_client_crate_organization.md index 672eed876..5611d0ebf 100644 --- a/design/src/rfcs/rfc0026_client_crate_organization.md +++ b/design/src/rfcs/rfc0026_client_crate_organization.md @@ -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> { diff --git a/design/src/rfcs/rfc0027_endpoints_20.md b/design/src/rfcs/rfc0027_endpoints_20.md index b5428c1ed..90739d347 100644 --- a/design/src/rfcs/rfc0027_endpoints_20.md +++ b/design/src/rfcs/rfc0027_endpoints_20.md @@ -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: Send + Sync { fn resolve_endpoint(&self, params: &T) -> Result; } @@ -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: 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 }, diff --git a/design/src/rfcs/rfc0028_sdk_credential_cache_type_safety.md b/design/src/rfcs/rfc0028_sdk_credential_cache_type_safety.md index d16b24e22..719f2ef9f 100644 --- a/design/src/rfcs/rfc0028_sdk_credential_cache_type_safety.md +++ b/design/src/rfcs/rfc0028_sdk_credential_cache_type_safety.md @@ -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; } @@ -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, // ... @@ -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); diff --git a/design/src/rfcs/rfc0029_new_home_for_cred_types.md b/design/src/rfcs/rfc0029_new_home_for_cred_types.md index 734ef7408..e864bd7d5 100644 --- a/design/src/rfcs/rfc0029_new_home_for_cred_types.md +++ b/design/src/rfcs/rfc0029_new_home_for_cred_types.md @@ -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 { diff --git a/design/src/rfcs/rfc0030_serialization_and_deserialization.md b/design/src/rfcs/rfc0030_serialization_and_deserialization.md index 1e4c6c3bf..46ad1a4cb 100644 --- a/design/src/rfcs/rfc0030_serialization_and_deserialization.md +++ b/design/src/rfcs/rfc0030_serialization_and_deserialization.md @@ -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 @@ -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> { diff --git a/design/src/rfcs/rfc0031_providing_fallback_credentials_on_timeout.md b/design/src/rfcs/rfc0031_providing_fallback_credentials_on_timeout.md index 98ccfad3d..f6e76e87a 100644 --- a/design/src/rfcs/rfc0031_providing_fallback_credentials_on_timeout.md +++ b/design/src/rfcs/rfc0031_providing_fallback_credentials_on_timeout.md @@ -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

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 = /* ... */ diff --git a/design/src/rfcs/rfc0032_better_constraint_violations.md b/design/src/rfcs/rfc0032_better_constraint_violations.md index d8648df31..6b44d2783 100644 --- a/design/src/rfcs/rfc0032_better_constraint_violations.md +++ b/design/src/rfcs/rfc0032_better_constraint_violations.md @@ -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 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, ); @@ -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(pub(crate) Vec); impl IntoIterator for ConstraintViolations { ... } @@ -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(pub(crate) Vec); impl IntoIterator for ConstraintViolationExceptions { ... } @@ -604,8 +604,8 @@ string LengthPatternString This would yield: -```rust -pub ConstraintViolations(pub(crate) Vec); +```rust,ignore +pub struct ConstraintViolations(pub(crate) Vec); impl IntoIterator for ConstraintViolations { ... } @@ -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, @@ -693,7 +693,7 @@ map LengthMap { This gives us: -```rust +```rust,ignore pub mod length_map { pub struct ConstraintViolations { pub length: Option, @@ -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`, diff --git a/design/src/rfcs/rfc0033_improve_sdk_request_id_access.md b/design/src/rfcs/rfc0033_improve_sdk_request_id_access.md index 3e78f4070..9ec673248 100644 --- a/design/src/rfcs/rfc0033_improve_sdk_request_id_access.md +++ b/design/src/rfcs/rfc0033_improve_sdk_request_id_access.md @@ -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 { diff --git a/design/src/rfcs/rfc0034_smithy_orchestrator.md b/design/src/rfcs/rfc0034_smithy_orchestrator.md index 84d595225..075a83e33 100644 --- a/design/src/rfcs/rfc0034_smithy_orchestrator.md +++ b/design/src/rfcs/rfc0034_smithy_orchestrator.md @@ -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 { // 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: 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, } @@ -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` /// `Res`: The transport response message e.g. `http::Response` @@ -452,7 +452,7 @@ async fn make_an_attempt( 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<()>; } diff --git a/design/src/server/anatomy.md b/design/src/server/anatomy.md index 51f7b68f2..9c65c87ba 100644 --- a/design/src/server/anatomy.md +++ b/design/src/server/anatomy.md @@ -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 { /* 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`](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 { The `S` here is a model service, this is specified during construction of the `Operation`. 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 { /* 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`, is a _model service_ constructed fr Now, what about the `L` in `Operation`? 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 Operation { /// Applies a [`Layer`] to the operation _after_ it has been upgraded via [`Operation::upgrade`]. pub fn layer(self, layer: NewL) -> Operation> { @@ -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` 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: Sized { @@ -240,7 +240,7 @@ pub trait IntoResponse { 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`](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 Service for Upgrade 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 { /// 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 { 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 { @@ -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 PokemonService { /// Applies a [`Layer`](tower::Layer) uniformly to all routes. pub fn layer(self, layer: &L) -> PokemonService @@ -587,7 +587,7 @@ The plugin system solves the general problem of modifying `Operation` 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 { The `Upgradable::upgrade` method on `Operation`, previously presented in [Upgrading a Model Service](#upgrading-a-model-service), is more accurately: -```rust +```rust,ignore /// Takes the [`Operation`](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: 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. diff --git a/design/src/server/code_generation.md b/design/src/server/code_generation.md index b04008cb4..f65c08d2f 100644 --- a/design/src/server/code_generation.md +++ b/design/src/server/code_generation.md @@ -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`, `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`, `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 diff --git a/design/src/server/from_parts.md b/design/src/server/from_parts.md index a03386cc9..98b5365ac 100644 --- a/design/src/server/from_parts.md +++ b/design/src/server/from_parts.md @@ -6,7 +6,7 @@ But what if we, the customer, want to access data in the handler which is _not_ -```rust +```rust,ignore /// Provides a protocol aware extraction from a [`Request`]. This borrows the /// [`Parts`], in contrast to [`FromRequest`]. pub trait FromParts: 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`: -```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) -> 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, ext2: Extension, other: Other /* : FromParts */, /* ... */) -> ModelOutput { /* ... */ } @@ -81,7 +81,7 @@ Note that the `parts.extensions.remove::()` in `Extensions::from_parts` will The `FromParts` trait is public so customers have the ability specify their own implementations: -```rust +```rust,ignore struct CustomerDefined { /* ... */ } diff --git a/design/src/server/instrumentation.md b/design/src/server/instrumentation.md index d946e51b0..b24ba6775 100644 --- a/design/src/server/instrumentation.md +++ b/design/src/server/instrumentation.md @@ -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(); diff --git a/design/src/server/middleware.md b/design/src/server/middleware.md index 45334adf9..eb8d46c9a 100644 --- a/design/src/server/middleware.md +++ b/design/src/server/middleware.md @@ -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 { inner: S, /* auxillary data */ @@ -58,7 +58,7 @@ pub struct NewService { 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` implementation. A `Layer` can be applied around the entire `Service`. -```rust +```rust,ignore // This is a HTTP `Service`. let app /* : PokemonService> */ = 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::::Ok(todo!()) }; +# struct BufferLayer; +# impl BufferLayer { pub fn new(size: usize) -> Self { Self } } +# impl Layer 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> */ = PokemonService::builder_without_plugins() .get_pokemon_species_operation(layered_handler) /* ... */ - .build(); + .build() + # ;Result::<(), ()>::Ok(()) + .unwrap(); +# let app: Result, _> = 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 { @@ -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 Plugin 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 { /// Causes all operations to print the operation name when called. @@ -327,7 +340,7 @@ impl PrintExt for PluginPipeline { - 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 {...} - ``` - -3. If the operation is not fallible and shares some state: - - ```rust - pub async fn get_server_statistics( - _input: input::GetServerStatisticsInput, - state: Extension>, - ) -> output::GetServerStatisticsOutput {...} - ``` - -4. If the operation is fallible and shares some state: - - ```rust - pub async fn get_storage( - input: input::GetStorageInput, - _state: Extension>, - ) -> Result {...} - ``` - -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 - std::convert::From< - OperationRegistry, - > for aws_smithy_http_server::routing::Router -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, - ) -> 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 diff --git a/design/src/smithy/aggregate_shapes.md b/design/src/smithy/aggregate_shapes.md index 627ea875b..0dafac77f 100644 --- a/design/src/smithy/aggregate_shapes.md +++ b/design/src/smithy/aggregate_shapes.md @@ -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 ///

Contains I/O usage metrics for a command that was invoked.

#[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 { diff --git a/design/src/smithy/backwards-compat.md b/design/src/smithy/backwards-compat.md index d39c071d6..85a3ea5fd 100644 --- a/design/src/smithy/backwards-compat.md +++ b/design/src/smithy/backwards-compat.md @@ -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 { diff --git a/design/src/smithy/endpoint.md b/design/src/smithy/endpoint.md index 699f17020..bef52bdfe 100644 --- a/design/src/smithy/endpoint.md +++ b/design/src/smithy/endpoint.md @@ -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(); diff --git a/design/src/smithy/simple_shapes.md b/design/src/smithy/simple_shapes.md index 171196d0d..9e2c99b43 100644 --- a/design/src/smithy/simple_shapes.md +++ b/design/src/smithy/simple_shapes.md @@ -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}} ``` diff --git a/design/src/transport/operation.md b/design/src/transport/operation.md index 332e60765..9ca8a4dfc 100644 --- a/design/src/transport/operation.md +++ b/design/src/transport/operation.md @@ -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`. 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 { 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 { let op = BatchExecuteStatement::new(BatchExecuteStatementInput { diff --git a/tools/ci-build/Dockerfile b/tools/ci-build/Dockerfile index f5ed4869d..045805f47 100644 --- a/tools/ci-build/Dockerfile +++ b/tools/ci-build/Dockerfile @@ -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 \ diff --git a/tools/ci-scripts/check-book b/tools/ci-scripts/check-book new file mode 100755 index 000000000..e26aae552 --- /dev/null +++ b/tools/ci-scripts/check-book @@ -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