Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client examples that use the generic client have been added #2799

Merged
merged 6 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"pokemon-service-lambda",
"pokemon-service-server-sdk",
"pokemon-service-client",
"pokemon-service-client-usage",

]

Expand Down
37 changes: 37 additions & 0 deletions examples/pokemon-service-client-usage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
49 changes: 49 additions & 0 deletions examples/pokemon-service-client-usage/README.md
Original file line number Diff line number Diff line change
@@ -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
drganjoo marked this conversation as resolved.
Show resolved Hide resolved

| 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. |
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -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");
}
62 changes: 62 additions & 0 deletions examples/pokemon-service-client-usage/examples/custom-header.rs
Original file line number Diff line number Diff line change
@@ -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");
}
Loading
Loading