mirror of https://github.com/smithy-lang/smithy-rs
Client examples that use the generic client have been added (#2799)
## Motivation and Context Example code that demonstrates the usage of pokemon-service-client. ## Description Examples have been added that show how to add middleware, configure retries, timeouts, and handle errors when calling operations on the pokemon-service. _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ --------- Co-authored-by: Fahad Zubair <fahadzub@amazon.com>
This commit is contained in:
parent
1f7cc8e69e
commit
66a3acf5e0
|
@ -7,6 +7,7 @@ members = [
|
|||
"pokemon-service-lambda",
|
||||
"pokemon-service-server-sdk",
|
||||
"pokemon-service-client",
|
||||
"pokemon-service-client-usage",
|
||||
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "pokemon-service-client-usage"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
|
||||
|
||||
[dependencies]
|
||||
# The generated client utilizes types defined in other crates, such as `aws_smithy_types`
|
||||
# and `aws_smithy_http`. However, most of these types are re-exported by the generated client,
|
||||
# eliminating the need to directly depend on the crates that provide them. In rare instances,
|
||||
# you may still need to include one of these crates as a dependency. Examples that require this
|
||||
# are specifically noted in comments above the corresponding dependency in this file.
|
||||
pokemon-service-client = { path = "../pokemon-service-client/" }
|
||||
|
||||
# Required for getting the operation name from the `Metadata`.
|
||||
aws-smithy-http = { path = "../../rust-runtime/aws-smithy-http/" }
|
||||
|
||||
# Required for `Storable` and `StoreReplace` in `response-header-interceptor` example.
|
||||
aws-smithy-types = { path = "../../rust-runtime/aws-smithy-types/" }
|
||||
|
||||
# Required for `HyperClientBuilder` in `client-connector` example.
|
||||
aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime/", features=["test-util"] }
|
||||
|
||||
|
||||
|
||||
hyper = { version = "0.14.25", features = ["client", "full"] }
|
||||
tokio = {version = "1.26.0", features=["full"]}
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
rustls = "0.21.7"
|
||||
hyper-rustls = "0.24.1"
|
||||
http = "0.2.9"
|
||||
uuid = {version="1.4.1", features = ["v4"]}
|
||||
thiserror = "1.0.49"
|
|
@ -0,0 +1,49 @@
|
|||
# smithy-rs Client Examples
|
||||
|
||||
This package contains some examples on how to use the Smithy Client to communicate
|
||||
with a Smithy-based service.
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
1. Build the `pokemon-service-client` and `pokemon-service` by invoking `make` in the
|
||||
[examples](https://github.com/awslabs/smithy-rs/tree/main/examples) folder.
|
||||
|
||||
```console
|
||||
make
|
||||
```
|
||||
|
||||
2. Run the Pokemon service locally by issuing the following command from the
|
||||
[examples](https://github.com/awslabs/smithy-rs/tree/main/examples) folder. This
|
||||
will launch the Smithy-Rs based service on TCP port 13734.
|
||||
|
||||
```console
|
||||
cargo run --bin pokemon-service
|
||||
```
|
||||
|
||||
## Running the examples
|
||||
|
||||
You can view a list of examples by running `cargo run --example` from the
|
||||
[pokemon-service-client-usage](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage)
|
||||
folder. To run an example, pass its name to the `cargo run --example` command, e.g.:
|
||||
|
||||
```console
|
||||
cargo run --example simple-client
|
||||
```
|
||||
|
||||
## List of examples
|
||||
|
||||
| Rust Example | Description |
|
||||
|--------------------------------|-------------------------------------------------------------------------|
|
||||
| simple-client | Creates a Smithy Client and calls an operation on it. |
|
||||
| endpoint-resolver | How to set a custom endpoint resolver. |
|
||||
| handling-errors | How to send an input parameter to an operation, and to handle errors. |
|
||||
| custom-header | How to add headers to a request. |
|
||||
| custom-header-using-interceptor| How to access operation name being called in an interceptor. |
|
||||
| response-header-interceptor | How to get operation name and access response before it is deserialized.|
|
||||
| use-config-bag | How to use the property bag to pass data across interceptors. |
|
||||
| retries-customize | Customize retry settings. |
|
||||
| retries-disable | How to disable retries. |
|
||||
| timeout-config | How to configure timeouts. |
|
||||
| mock-request | Use a custom HttpConnector / Client to generate mock responses. |
|
||||
| trace-serialize | Trace request and response as they are serialized / deserialized. |
|
||||
| client-connector | Shows how to change TLS related configuration. |
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how to set connector settings. For example, how to set
|
||||
/// trusted root certificates to use for HTTPs communication.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example client-connector`.
|
||||
///
|
||||
use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder;
|
||||
use hyper_rustls::ConfigBuilderExt;
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon
|
||||
/// service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
let tls_config = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
// `with_native_roots()`: Load platform trusted root certificates.
|
||||
// `with_webpki_roots()`: Load Mozilla’s set of trusted roots.
|
||||
.with_native_roots()
|
||||
// To use client side certificates, you can use
|
||||
// `.with_client_auth_cert(client_cert, client_key)` instead of `.with_no_client_auth()`
|
||||
.with_no_client_auth();
|
||||
|
||||
let tls_connector = hyper_rustls::HttpsConnectorBuilder::new()
|
||||
.with_tls_config(tls_config)
|
||||
// This can be changed to `.https_only()` to ensure that the client always uses HTTPs
|
||||
.https_or_http()
|
||||
.enable_http1()
|
||||
.enable_http2()
|
||||
.build();
|
||||
|
||||
// Create a hyper-based HTTP client that uses this TLS connector.
|
||||
let http_client = HyperClientBuilder::new().build(tls_connector);
|
||||
|
||||
// Pass the smithy connector to the Client::ConfigBuilder
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.http_client(http_client)
|
||||
.build();
|
||||
|
||||
// Instantiate a client by applying the configuration.
|
||||
pokemon_service_client::Client::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
tracing::info!(?response, "Response from service")
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// In this example, a custom header `x-amzn-client-ttl-seconds` is set for all outgoing requests.
|
||||
/// It serves as a demonstration of how an operation name can be retrieved and utilized within
|
||||
/// the interceptor.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example custom-header-using-interceptor`.
|
||||
///
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use pokemon_service_client::config::{ConfigBag, Intercept};
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
use pokemon_service_client::{
|
||||
config::{interceptors::BeforeTransmitInterceptorContextMut, RuntimeComponents},
|
||||
error::BoxError,
|
||||
};
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
|
||||
// The `TtlHeaderInterceptor` keeps a map of operation specific value to send
|
||||
// in the header for each Request.
|
||||
#[derive(Debug)]
|
||||
pub struct TtlHeaderInterceptor {
|
||||
/// Default time-to-live for an operation.
|
||||
default_ttl: hyper::http::HeaderValue,
|
||||
/// Operation specific time-to-live.
|
||||
operation_ttl: HashMap<&'static str, hyper::http::HeaderValue>,
|
||||
}
|
||||
|
||||
// Helper function to format duration as fractional seconds.
|
||||
fn format_ttl_value(ttl: Duration) -> String {
|
||||
format!("{:.2}", ttl.as_secs_f64())
|
||||
}
|
||||
|
||||
impl TtlHeaderInterceptor {
|
||||
fn new(default_ttl: Duration) -> Self {
|
||||
let duration_str = format_ttl_value(default_ttl);
|
||||
let default_ttl_value = hyper::http::HeaderValue::from_str(duration_str.as_str())
|
||||
.expect("could not create a header value for the default ttl");
|
||||
|
||||
Self {
|
||||
default_ttl: default_ttl_value,
|
||||
operation_ttl: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an operation name specific timeout value that needs to be set in the header.
|
||||
fn add_operation_ttl(&mut self, operation_name: &'static str, ttl: Duration) {
|
||||
let duration_str = format_ttl_value(ttl);
|
||||
|
||||
self.operation_ttl.insert(
|
||||
operation_name,
|
||||
hyper::http::HeaderValue::from_str(duration_str.as_str())
|
||||
.expect("cannot create header value for the given ttl duration"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends the header `x-amzn-client-ttl-seconds` using either the default time-to-live value
|
||||
/// or an operation-specific value if it was set earlier using `add_operation_ttl`.
|
||||
//impl aws_smithy_runtime_api::client::interceptors::Interceptor for TtlHeaderInterceptor {
|
||||
impl Intercept for TtlHeaderInterceptor {
|
||||
fn name(&self) -> &'static str {
|
||||
"TtlHeaderInterceptor"
|
||||
}
|
||||
|
||||
/// Before the request is signed, add the header to the outgoing request.
|
||||
fn modify_before_signing(
|
||||
&self,
|
||||
context: &mut BeforeTransmitInterceptorContextMut<'_>,
|
||||
_runtime_components: &RuntimeComponents,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
// Metadata in the ConfigBag has the operation name.
|
||||
let metadata = cfg
|
||||
.load::<aws_smithy_http::operation::Metadata>()
|
||||
.expect("metadata should exist");
|
||||
let operation_name = metadata.name();
|
||||
|
||||
// Get operation specific or default HeaderValue to set for the header key.
|
||||
let ttl = self
|
||||
.operation_ttl
|
||||
.get(operation_name)
|
||||
.unwrap_or(&self.default_ttl);
|
||||
|
||||
context
|
||||
.request_mut()
|
||||
.headers_mut()
|
||||
.insert("x-amzn-client-ttl-seconds", ttl.clone());
|
||||
|
||||
tracing::info!("{operation_name} header set to {ttl:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// By default set the value of all operations to 6 seconds.
|
||||
const DEFAULT_TTL: Duration = Duration::from_secs(6);
|
||||
|
||||
// Set up the interceptor to add an operation specific value of 3.5 seconds to be added
|
||||
// for GetStorage operation.
|
||||
let mut ttl_headers_interceptor = TtlHeaderInterceptor::new(DEFAULT_TTL);
|
||||
ttl_headers_interceptor.add_operation_ttl("GetStorage", Duration::from_millis(3500));
|
||||
|
||||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which
|
||||
// allows configuring endpoint-resolver, timeouts, retries etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.interceptor(ttl_headers_interceptor)
|
||||
.build();
|
||||
|
||||
pokemon_service_client::Client::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response for get_server_statistics()");
|
||||
|
||||
// Call the operation `get_storage` on the Pokémon service. The `TtlHeaderInterceptor`
|
||||
// interceptor will add a specific header name / value pair for this operation.
|
||||
let response = client
|
||||
.get_storage()
|
||||
.user("ash")
|
||||
.passcode("pikachu123")
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
// Print the response received from the service.
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how to create a `smithy-rs` client, and call an operation with custom
|
||||
/// headers in the request.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example custom-header`
|
||||
///
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon
|
||||
/// service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which
|
||||
// allows configuring endpoint-resolver, timeouts, retries etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
pokemon_service_client::Client::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.customize()
|
||||
.mutate_request(|req| {
|
||||
// For demonstration purposes, add a header `x-ttl-seconds` to the outgoing request.
|
||||
let headers = req.headers_mut();
|
||||
headers.insert(
|
||||
hyper::header::HeaderName::from_static("x-ttl-seconds"),
|
||||
hyper::header::HeaderValue::from(30),
|
||||
);
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how a custom `ResolveEndpoint` can be implemented for resolving
|
||||
/// endpoint of a request. Additionally, it shows how a header can be added using the endpoint
|
||||
/// builder.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example endpoint-resolver`.
|
||||
///
|
||||
use pokemon_service_client::config::endpoint::{Endpoint, EndpointFuture, Params, ResolveEndpoint};
|
||||
use pokemon_service_client::primitives::{DateTime, DateTimeFormat};
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
use pokemon_service_client_usage::setup_tracing_subscriber;
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
// This struct, provided as an example, constructs the URL that should be set on each request during initialization.
|
||||
// It also implements the `ResolveEndpoint` trait, enabling it to be assigned as the endpoint_resolver in the `Config`.
|
||||
#[derive(Debug)]
|
||||
struct RegionalEndpoint {
|
||||
url_to_use: String,
|
||||
}
|
||||
|
||||
impl RegionalEndpoint {
|
||||
fn new(regional_url: &str, port: u16) -> Self {
|
||||
let url_to_use = format!("{}:{}", regional_url, port);
|
||||
RegionalEndpoint { url_to_use }
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveEndpoint for RegionalEndpoint {
|
||||
fn resolve_endpoint<'a>(&'a self, _params: &'a Params) -> EndpointFuture<'a> {
|
||||
// Construct an endpoint using the Endpoint::Builder. Set the URL and,
|
||||
// optionally, any headers to be sent with the request. For this example,
|
||||
// we'll set the 'x-amz-date' header to the current date for all outgoing requests.
|
||||
// `DateTime` can be used for formatting an RFC 3339 date time.
|
||||
let now = SystemTime::now();
|
||||
let date_time = DateTime::from(now);
|
||||
|
||||
let endpoint = Endpoint::builder()
|
||||
.url(self.url_to_use.clone())
|
||||
.header(
|
||||
"x-amz-date",
|
||||
date_time
|
||||
.fmt(DateTimeFormat::DateTimeWithOffset)
|
||||
.expect("Could not create a date in UTC format"),
|
||||
)
|
||||
.build();
|
||||
tracing::info!(?endpoint, "Resolving endpoint");
|
||||
EndpointFuture::ready(Ok(endpoint))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
const DEFAULT_PORT: u16 = 13734;
|
||||
|
||||
// Use the environment variable `REGIONAL_URL` for the URL.
|
||||
let resolver = RegionalEndpoint::new(
|
||||
std::env::var("REGIONAL_URL")
|
||||
.as_deref()
|
||||
.unwrap_or("http://localhost"),
|
||||
DEFAULT_PORT,
|
||||
);
|
||||
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_resolver(resolver)
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
tracing::info!(?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// Copyright © 2023, Amazon, LLC.
|
||||
///
|
||||
/// This example demonstrates how to handle service generated errors.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example handling-errors`.
|
||||
///
|
||||
use pokemon_service_client::{error::SdkError, operation::get_storage::GetStorageError};
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which
|
||||
// allows configuring endpoint-resolver, timeouts, retries etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// The following example sends an incorrect passcode to the operation `get_storage`,
|
||||
// which will return
|
||||
// [StorageAccessNotAuthorized](https://github.com/awslabs/smithy-rs/blob/main/codegen-core/common-test-models/pokemon.smithy#L48)
|
||||
let response_result = client
|
||||
.get_storage()
|
||||
.user("ash")
|
||||
// Give a wrong password to generate a service error.
|
||||
.passcode("pkachu123")
|
||||
.send()
|
||||
.await;
|
||||
|
||||
// All errors are consolidated into an `SdkError<T, R>`
|
||||
match response_result {
|
||||
Ok(response) => {
|
||||
tracing::info!(?response, "Response from service")
|
||||
}
|
||||
Err(SdkError::ServiceError(se)) => {
|
||||
// When an error response is received from the service, it is modeled
|
||||
// as a `SdkError::ServiceError`.
|
||||
match se.err() {
|
||||
// Not authorized to access Pokémon storage.
|
||||
GetStorageError::StorageAccessNotAuthorized(_) => {
|
||||
tracing::error!("You do not have access to this resource.");
|
||||
}
|
||||
GetStorageError::ResourceNotFoundError(rnfe) => {
|
||||
let message = rnfe.message();
|
||||
tracing::error!(error = %message,
|
||||
"Given Pikachu does not exist on the server."
|
||||
)
|
||||
}
|
||||
GetStorageError::ValidationError(ve) => {
|
||||
tracing::error!(error = %ve, "A required field has not been set.");
|
||||
}
|
||||
// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code).
|
||||
GetStorageError::Unhandled(uh) => {
|
||||
tracing::error!(error = %uh, "An unhandled error has occurred.")
|
||||
}
|
||||
// The SdkError is marked as `#[non_exhaustive]`. Therefore, a catch-all pattern is required to handle
|
||||
// potential future variants introduced in SdkError.
|
||||
_ => {
|
||||
tracing::error!(error = %se.err(), "Some other error has occurred on the server")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(SdkError::TimeoutError(_)) => {
|
||||
tracing::error!("The request timed out and could not be completed");
|
||||
}
|
||||
Err(SdkError::ResponseError(re)) => {
|
||||
// Raw response received from the service can be retrieved using
|
||||
// the `raw()` method.
|
||||
tracing::error!(
|
||||
"An unparsable response was received. Raw response: {:?}",
|
||||
re.raw()
|
||||
);
|
||||
}
|
||||
Err(sdk_error) => {
|
||||
// To retrieve the `source()` of an error within the following match statements,
|
||||
// we work with the parent `SdkError` type, as individual variants don't directly provide it.
|
||||
// Converting the parent error to its source transfers ownership of the variable.
|
||||
match sdk_error {
|
||||
SdkError::DispatchFailure(ref failure) => {
|
||||
if failure.is_io() {
|
||||
tracing::error!("An I/O error occurred");
|
||||
} else if failure.is_timeout() {
|
||||
tracing::error!("Request timed out");
|
||||
} else if failure.is_user() {
|
||||
tracing::error!("An invalid HTTP request has been provided");
|
||||
} else {
|
||||
tracing::error!("Some other dispatch error occurred.");
|
||||
};
|
||||
|
||||
if let Ok(source) = sdk_error.into_source() {
|
||||
tracing::error!(%source, "Error source");
|
||||
}
|
||||
}
|
||||
SdkError::ConstructionFailure(_) => {
|
||||
if let Ok(source) = sdk_error.into_source() {
|
||||
tracing::error!(%source, "Request could not be constructed.");
|
||||
} else {
|
||||
tracing::error!("Request could not be constructed for unknown reasons");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
tracing::error!("An unknown error has occurred");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how to use a mock connector with `capture_request`. This allows for
|
||||
/// responding with a static `Response` while capturing the incoming request. The captured request
|
||||
/// can later be asserted to verify that the correct headers and body were sent to the server.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example mock-request`.
|
||||
///
|
||||
use aws_smithy_runtime::client::http::test_util::capture_request;
|
||||
use aws_smithy_types::body::SdkBody;
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Build a response that should be sent when the operation is called.
|
||||
let response = http::Response::builder()
|
||||
.status(200)
|
||||
.body(SdkBody::from(r#"{"calls_count":100}"#))
|
||||
.expect("response could not be constructed");
|
||||
|
||||
// Call `capture_request` to obtain a HTTP connector and a request receiver.
|
||||
// The request receiver captures the incoming request, while the connector can be passed
|
||||
// to `Config::builder().http_client`.
|
||||
let (http_client, captured_request) = capture_request(Some(response));
|
||||
|
||||
// Pass the `http_client` connector to `Config::builder`. The connector won't send
|
||||
// the request over the network; instead, it will return the static response provided
|
||||
// during its initialization.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.http_client(http_client)
|
||||
.build();
|
||||
|
||||
// Instantiate a client by applying the configuration.
|
||||
let client = PokemonClient::from_conf(config);
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.customize()
|
||||
.mutate_request(|req| {
|
||||
// For demonstration, send an extra header that can be verified to confirm
|
||||
// that the client actually sends it.
|
||||
let headers = req.headers_mut();
|
||||
headers.insert(
|
||||
hyper::header::HeaderName::from_static("user-agent"),
|
||||
hyper::header::HeaderName::from_static("sample-client"),
|
||||
);
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
// Print the response received from the service.
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
|
||||
// The captured request can be verified to have certain headers.
|
||||
let req = captured_request.expect_request();
|
||||
assert_eq!(req.headers().get("user-agent"), Some("sample-client"));
|
||||
|
||||
// As an example, you can verify the URL matches.
|
||||
assert_eq!(req.uri(), "http://localhost:13734/stats");
|
||||
|
||||
// You can convert the captured body into a &str and use assert!
|
||||
// on it if you want to verify the contents of the request body.
|
||||
// let str_body = std::str::from_utf8(req.body().bytes().unwrap()).unwrap();
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how response headers can be examined before they are deserialized
|
||||
/// into the output type.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example response-header-interceptor`.
|
||||
///
|
||||
//use aws_smithy_types::config_bag::{Storable, StoreReplace};
|
||||
use aws_smithy_types::config_bag::{Storable, StoreReplace};
|
||||
use pokemon_service_client::{
|
||||
config::{
|
||||
interceptors::{
|
||||
BeforeDeserializationInterceptorContextRef, BeforeTransmitInterceptorContextMut,
|
||||
},
|
||||
ConfigBag, Intercept, RuntimeComponents,
|
||||
},
|
||||
error::BoxError,
|
||||
Client as PokemonClient,
|
||||
};
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct RequestId {
|
||||
client_id: String,
|
||||
server_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Storable for RequestId {
|
||||
type Storer = StoreReplace<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum RequestIdError {
|
||||
/// The server-sent request ID cannot be converted into a string during parsing.
|
||||
#[error("RequestID sent by the server cannot be parsed into a string. Error: {0}")]
|
||||
NonParsableServerRequestId(String),
|
||||
/// Client side
|
||||
#[error("Client side request ID has not been set")]
|
||||
ClientRequestIdMissing(),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ResponseHeaderLoggingInterceptor;
|
||||
|
||||
impl ResponseHeaderLoggingInterceptor {
|
||||
/// Creates a new `ResponseHeaderLoggingInterceptor`
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Intercept for ResponseHeaderLoggingInterceptor {
|
||||
fn name(&self) -> &'static str {
|
||||
"ResponseHeaderLoggingInterceptor"
|
||||
}
|
||||
|
||||
/// Before the request is signed, add the header to the outgoing request.
|
||||
fn modify_before_signing(
|
||||
&self,
|
||||
context: &mut BeforeTransmitInterceptorContextMut<'_>,
|
||||
_runtime_components: &RuntimeComponents,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
let client_id = Uuid::new_v4().to_string();
|
||||
|
||||
let request_id = hyper::header::HeaderValue::from_str(&client_id)
|
||||
.expect("failed to construct a header value from UUID");
|
||||
context
|
||||
.request_mut()
|
||||
.headers_mut()
|
||||
.insert("x-amzn-requestid", request_id);
|
||||
|
||||
cfg.interceptor_state().store_put(RequestId {
|
||||
client_id,
|
||||
server_id: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_before_deserialization(
|
||||
&self,
|
||||
context: &BeforeDeserializationInterceptorContextRef<'_>,
|
||||
_runtime_components: &RuntimeComponents,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
// `Metadata` in the `ConfigBag` has the operation name in it.
|
||||
let metadata = cfg
|
||||
.load::<aws_smithy_http::operation::Metadata>()
|
||||
.expect("metadata should exist");
|
||||
let operation_name = metadata.name().to_string();
|
||||
|
||||
// Get the server side request ID and set it in the RequestID data type
|
||||
// that is in the ConfigBag. This way any other interceptor that requires the mapping
|
||||
// can easily find it from the bag.
|
||||
let response = context.response();
|
||||
let header_received = response
|
||||
.headers()
|
||||
.iter()
|
||||
.find(|(header_name, _)| *header_name == "x-request-id");
|
||||
|
||||
if let Some((_, server_id)) = header_received {
|
||||
let server_id = server_id
|
||||
.to_str()
|
||||
.map_err(|e| Box::new(RequestIdError::NonParsableServerRequestId(e.to_string())))?;
|
||||
|
||||
let request_details = cfg
|
||||
.get_mut::<RequestId>()
|
||||
.ok_or_else(|| Box::new(RequestIdError::ClientRequestIdMissing()))?;
|
||||
|
||||
tracing::info!(operation = %operation_name,
|
||||
"RequestID Mapping: {} = {server_id}",
|
||||
request_details.client_id,
|
||||
);
|
||||
|
||||
request_details.server_id = Some(server_id.into());
|
||||
} else {
|
||||
tracing::info!(operation = %operation_name, "Server RequestID missing in response");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon
|
||||
/// service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.interceptor(ResponseHeaderLoggingInterceptor)
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
// If you need to access the `RequestIdError` raised by the interceptor,
|
||||
// you can convert `SdkError::DispatchFailure` to a `ConnectorError`
|
||||
// and then use `downcast_ref` on its source to get a `RequestIdError`.
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how a custom RetryClassifier can be written to decide
|
||||
/// which error conditions should be retried.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example retry-classifier`.
|
||||
///
|
||||
use http::StatusCode;
|
||||
use pokemon_service_client::{
|
||||
config::{
|
||||
interceptors::InterceptorContext,
|
||||
retry::{ClassifyRetry, RetryAction, RetryConfig},
|
||||
},
|
||||
operation::get_server_statistics::GetServerStatisticsError,
|
||||
};
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
use std::time::Duration;
|
||||
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SampleRetryClassifier;
|
||||
|
||||
// By default, the generated client uses the `aws_http::retry::AwsResponseRetryClassifier`
|
||||
// to determine whether an error should be retried. To use a custom retry classifier,
|
||||
// implement the `ClassifyRetry` trait and pass it to the retry_classifier method
|
||||
// of the `Config::builder`.
|
||||
impl ClassifyRetry for SampleRetryClassifier {
|
||||
fn name(&self) -> &'static str {
|
||||
"SampleRetryClassifier"
|
||||
}
|
||||
|
||||
// For this example, the classifier should retry in case the error is GetServerStatisticsError
|
||||
// and the status code is 503.
|
||||
fn classify_retry(&self, ctx: &InterceptorContext) -> RetryAction {
|
||||
// Get the output or error that has been deserialized from the response.
|
||||
let output_or_error = ctx.output_or_error();
|
||||
|
||||
let error = match output_or_error {
|
||||
Some(Ok(_)) | None => return RetryAction::NoActionIndicated,
|
||||
Some(Err(err)) => err,
|
||||
};
|
||||
|
||||
// Retry in case the error returned is GetServerStatisticsError and StatusCode is 503.
|
||||
if let Some(_err) = error
|
||||
.as_operation_error()
|
||||
.and_then(|err| err.downcast_ref::<GetServerStatisticsError>())
|
||||
{
|
||||
if let Some(response) = ctx.response() {
|
||||
if response.status() == StatusCode::SERVICE_UNAVAILABLE {
|
||||
return RetryAction::server_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let other classifiers run and decide if the request should be retried.
|
||||
// Returning RetryAction::RetryForbidden will forbid any retries.
|
||||
RetryAction::NoActionIndicated
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// By default the Smithy client uses RetryConfig::standard() strategy, with 3 retries, and
|
||||
// an initial exponential back off of 1 second. To turn it off use RetryConfig::disabled().
|
||||
let retry_config = RetryConfig::standard()
|
||||
.with_initial_backoff(Duration::from_secs(3))
|
||||
.with_max_attempts(5);
|
||||
|
||||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which
|
||||
// allows configuring endpoint-resolver, timeouts, retries etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.retry_config(retry_config)
|
||||
// Add the retry classifier.
|
||||
.retry_classifier(SampleRetryClassifier {})
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how to customize retry settings on a Smithy client.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example retry-customize`.
|
||||
///
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
use std::time::Duration;
|
||||
|
||||
use pokemon_service_client::{config::retry::RetryConfig, Client as PokemonClient};
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// By default the Smithy client uses `RetryConfig::standard()` strategy, with 3 retries, and
|
||||
// an initial exponential back off of 1 second. To turn it off use `RetryConfig::disabled()`.
|
||||
let retry_config = RetryConfig::standard()
|
||||
.with_initial_backoff(Duration::from_secs(3))
|
||||
.with_max_attempts(5);
|
||||
|
||||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which
|
||||
// allows configuring endpoint-resolver, timeouts, retries etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.retry_config(retry_config)
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how to create a `smithy-rs` Client and call an
|
||||
/// [operation](https://smithy.io/2.0/spec/idl.html?highlight=operation#operation-shape).
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example simple-client`.
|
||||
///
|
||||
use pokemon_service_client::Client as PokemonClient;
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon
|
||||
/// service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// The generated client contains a type `config::Builder` for constructing a `Config` instance.
|
||||
// This enables configuration of endpoint resolvers, timeouts, retries, etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.build();
|
||||
|
||||
// Instantiate a client by applying the configuration.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
// Print the response received from the service.
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how to create a `smithy-rs` Client and set connection
|
||||
/// and operation related timeouts on the client.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example timeout-config`
|
||||
///
|
||||
use std::time::Duration;
|
||||
|
||||
use pokemon_service_client::{config::timeout::TimeoutConfig, Client as PokemonClient};
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// Different type of timeouts can be set on the client. These are:
|
||||
// operation_attempt_timeout - If retries are enabled, this represents the timeout
|
||||
// for each individual operation attempt.
|
||||
// operation_timeout - Overall timeout for the operation to complete.
|
||||
// connect timeout - The amount of time allowed for a connection to be established.
|
||||
let timeout_config = TimeoutConfig::builder()
|
||||
.operation_attempt_timeout(Duration::from_secs(1))
|
||||
.operation_timeout(Duration::from_secs(5))
|
||||
.connect_timeout(Duration::from_millis(500))
|
||||
.build();
|
||||
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.timeout_config(timeout_config)
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("Pokemon service does not seem to be running on localhost:13734");
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how an interceptor can be written to trace what is being
|
||||
/// serialized / deserialized on the wire.
|
||||
///
|
||||
/// Please beware that this may log sensitive information! This example is meant for pedagogical
|
||||
/// purposes and may be useful in debugging scenarios. Please don't use this as-is in production.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example trace-serialize`.
|
||||
///
|
||||
use http::StatusCode;
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
use std::str;
|
||||
|
||||
use pokemon_service_client::{
|
||||
config::{
|
||||
interceptors::{
|
||||
BeforeDeserializationInterceptorContextRef, BeforeTransmitInterceptorContextRef,
|
||||
},
|
||||
ConfigBag, Intercept, RuntimeComponents,
|
||||
},
|
||||
error::BoxError,
|
||||
Client as PokemonClient,
|
||||
};
|
||||
|
||||
/// An example interceptor that logs the request and response as they're sent and received.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WireFormatInterceptor;
|
||||
|
||||
impl Intercept for WireFormatInterceptor {
|
||||
fn name(&self) -> &'static str {
|
||||
"WireFormatInterceptor"
|
||||
}
|
||||
|
||||
// Called after the operation input has been serialized but before it's dispatched over the wire.
|
||||
fn read_after_serialization(
|
||||
&self,
|
||||
context: &BeforeTransmitInterceptorContextRef<'_>,
|
||||
_runtime_components: &RuntimeComponents,
|
||||
_cfg: &mut ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
// Get the request type from the context.
|
||||
let request = context.request();
|
||||
// Print the request to the debug tracing log.
|
||||
tracing::debug!(?request);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Called after the operation's response has been received but before it's deserialized into the
|
||||
// operation's output type.
|
||||
fn read_before_deserialization(
|
||||
&self,
|
||||
context: &BeforeDeserializationInterceptorContextRef<'_>,
|
||||
_runtime_components: &RuntimeComponents,
|
||||
_cfg: &mut ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
// Get the response type from the context.
|
||||
let response = context.response();
|
||||
// Print the response.
|
||||
if response.status() == StatusCode::OK {
|
||||
tracing::info!(?response, "Response received:");
|
||||
} else {
|
||||
tracing::error!(?response);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which
|
||||
// allows configuring endpoint-resolver, timeouts, retries etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.interceptor(WireFormatInterceptor {})
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("operation failed");
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/// This example demonstrates how different interceptor can use a property bag to pass
|
||||
/// state from one interceptor to the next.
|
||||
///
|
||||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
|
||||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
|
||||
/// file for instructions on how to launch the service locally.
|
||||
///
|
||||
/// The example can be run using `cargo run --example use-config-bag`.
|
||||
///
|
||||
//use aws_smithy_types::config_bag::{Storable, StoreReplace};
|
||||
use aws_smithy_types::config_bag::{Storable, StoreReplace};
|
||||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
|
||||
use std::time::Instant;
|
||||
|
||||
use pokemon_service_client::{
|
||||
config::{
|
||||
interceptors::{
|
||||
BeforeDeserializationInterceptorContextRef, FinalizerInterceptorContextRef,
|
||||
},
|
||||
ConfigBag, Intercept, RuntimeComponents,
|
||||
},
|
||||
error::BoxError,
|
||||
Client as PokemonClient,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RequestTimestamp(Instant);
|
||||
|
||||
impl Storable for RequestTimestamp {
|
||||
type Storer = StoreReplace<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SetTimeInterceptor;
|
||||
|
||||
/// Note: This is merely an example demonstrating how state can
|
||||
/// be shared between two different interceptors. In a practical
|
||||
/// scenario, there wouldn't be a need to write two interceptors
|
||||
/// merely to display the duration from the start of the lifecycle
|
||||
/// to the receipt of the response. This task can be accomplished
|
||||
/// within a single interceptor by overriding both
|
||||
/// read_before_execution and read_before_deserialization.
|
||||
impl Intercept for SetTimeInterceptor {
|
||||
fn name(&self) -> &'static str {
|
||||
"SetTimeInterceptor"
|
||||
}
|
||||
|
||||
fn read_before_execution(
|
||||
&self,
|
||||
_context: &pokemon_service_client::config::interceptors::BeforeSerializationInterceptorContextRef<'_>,
|
||||
cfg: &mut aws_smithy_types::config_bag::ConfigBag,
|
||||
) -> Result<(), pokemon_service_client::error::BoxError> {
|
||||
cfg.interceptor_state()
|
||||
.store_put(RequestTimestamp(Instant::now()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GetTimeInterceptor;
|
||||
|
||||
impl Intercept for GetTimeInterceptor {
|
||||
fn name(&self) -> &'static str {
|
||||
"GetTimeInterceptor"
|
||||
}
|
||||
|
||||
fn read_before_deserialization(
|
||||
&self,
|
||||
_context: &BeforeDeserializationInterceptorContextRef<'_>,
|
||||
_runtime_components: &RuntimeComponents,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
let stop_watch = cfg
|
||||
.load::<RequestTimestamp>()
|
||||
.expect("StopWatch not found in the ConfigBag");
|
||||
|
||||
let time_taken = stop_watch.0.elapsed();
|
||||
tracing::info!(time_taken = %time_taken.as_micros(), "Microseconds:");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_after_execution(
|
||||
&self,
|
||||
_context: &FinalizerInterceptorContextRef<'_>,
|
||||
_runtime_components: &RuntimeComponents,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), pokemon_service_client::error::BoxError> {
|
||||
let timestamp = cfg
|
||||
.load::<RequestTimestamp>()
|
||||
.expect("RequestTimeStamp not found in the ConfigBag");
|
||||
|
||||
let time_taken = timestamp.0.elapsed();
|
||||
tracing::info!(time_taken = %time_taken.as_micros(), "Microseconds:");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// let client = create_client();
|
||||
/// ```
|
||||
fn create_client() -> PokemonClient {
|
||||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which
|
||||
// allows configuring endpoint-resolver, timeouts, retries etc.
|
||||
let config = pokemon_service_client::Config::builder()
|
||||
.endpoint_url(POKEMON_SERVICE_URL)
|
||||
.interceptor(SetTimeInterceptor)
|
||||
.interceptor(GetTimeInterceptor)
|
||||
.build();
|
||||
|
||||
// Apply the configuration on the client, and return that.
|
||||
PokemonClient::from_conf(config)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_tracing_subscriber();
|
||||
|
||||
// Create a configured `smithy-rs` client.
|
||||
let client = create_client();
|
||||
|
||||
// Call an operation `get_server_statistics` on the Pokémon service.
|
||||
let response = client
|
||||
.get_server_statistics()
|
||||
.send()
|
||||
.await
|
||||
.expect("Pokemon service does not seem to be running on localhost:13734");
|
||||
|
||||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received");
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
pub static POKEMON_SERVICE_URL: &str = "http://localhost:13734";
|
||||
|
||||
/// Sets up the tracing subscriber to print `tracing::info!` and `tracing::error!` messages on the console.
|
||||
pub fn setup_tracing_subscriber() {
|
||||
// Add a tracing subscriber that uses the environment variable RUST_LOG
|
||||
// to figure out which log level should be emitted. By default use `tracing::info!`
|
||||
// as the logging level.
|
||||
let filter = tracing_subscriber::EnvFilter::builder()
|
||||
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
|
||||
.from_env_lossy();
|
||||
|
||||
tracing_subscriber::fmt::fmt()
|
||||
.with_env_filter(filter)
|
||||
.init();
|
||||
}
|
Loading…
Reference in New Issue