Skip to content

Commit

Permalink
Add an interceptor error example
Browse files Browse the repository at this point in the history
  • Loading branch information
Fahad Zubair committed Oct 10, 2023
1 parent 8f918ed commit 19c34c3
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 0 deletions.
179 changes: 179 additions & 0 deletions examples/pokemon-service-client-usage/examples/interceptor-errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use aws_smithy_http::{body::SdkBody, result::ConnectorError};
/*
* 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 Pokemon 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 tracing::{debug, error, info};

use aws_smithy_client::SdkError;
use pokemon_service_client::{config::Interceptor, Client as PokemonClient};

static BASE_URL: &str = "http://localhost:13734";

// Header to check that each request has this set.
const HEADER_TO_CHECK: hyper::header::HeaderName =
hyper::header::HeaderName::from_static("x-amzn-date");

#[derive(Debug, thiserror::Error)]
enum CustomErrors {
#[error("A required header `{0}` has not been set on the request")]
HeaderMissing(String),
}

#[derive(Debug, Default)]
pub struct CheckHeaderInterceptor;

impl CheckHeaderInterceptor {
/// Creates a new `CheckHeaderInterceptor`
pub fn new() -> Self {
Self::default()
}
}

impl Interceptor for CheckHeaderInterceptor {
fn name(&self) -> &'static str {
"CheckHeaderInterceptor"
}

fn read_before_signing(
&self,
context: &pokemon_service_client::config::interceptors::BeforeTransmitInterceptorContextRef<
'_,
>,
_runtime_components: &pokemon_service_client::config::RuntimeComponents,
_cfg: &mut aws_smithy_types::config_bag::ConfigBag,
) -> Result<(), pokemon_service_client::error::BoxError> {
if !context
.request()
.headers()
.iter()
.any(|(ref header_name, _)| *header_name == HEADER_TO_CHECK)
{
debug!("Header not found in request, raising an error");

return Err(Box::new(CustomErrors::HeaderMissing(
HEADER_TO_CHECK.to_string(),
)));
}

Ok(())
}
}

/// Creates a new Smithy client that is configured to communicate with a locally running Pokemon 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_resolver(BASE_URL)
.interceptor(CheckHeaderInterceptor {})
.build();

// Apply the configuration on the Client, and return that.
PokemonClient::from_conf(config)
}

#[tokio::main]
async fn main() {
tracing_setup();

// Create a configured Smithy client.
let client = create_client();

// Call an operation `get_server_statistics` on Pokemon service.
let response_result = client.get_server_statistics().send().await;

match response_result {
Ok(response) => {
info!(%BASE_URL, ?response, "Response received");
}
Err(e) => match e {
SdkError::ServiceError(ref e) => {
error!(?e, "Service error");
}
SdkError::DispatchFailure(_) => handle_dispatch_failure(e),
some_error => {
error!(?some_error, "A generic error occurred");
}
},
}
}

fn handle_dispatch_failure<E>(error: SdkError<E, hyper::Response<SdkBody>>)
where
E: std::error::Error + Send + Sync + 'static,
{
match error.into_source() {
// Convert the DispatchFailure into a ConnectorError, and then convert that
// into the CustomError that has been raised by the CheckHeaderInterceptor.
Ok(dispatch_failure_source) => {
match dispatch_failure_source.downcast::<ConnectorError>() {
// ConnectorError can be due to Io, Timeout, User or Other.
Ok(ce) => {
// Any error raised by an Interceptor is available as the source of the ConnectorError.
let connector_error = ce.into_source();
match connector_error.source() {
// We can downcast from the source error to the CustomError that was raised
// by the Interceptor.
Some(source_of_error) => {
match source_of_error.downcast_ref::<CustomErrors>() {
Some(custom_error) => {
// Specific match statements can be written here in case
// you want to find out which variant was raised.
error!("Request could not be dispatched. {}", custom_error);
}
None => {
error!(
?source_of_error,
"A ConnectorError has occurred. Source:"
)
}
}
}
None => {
error!(?connector_error, "ConnectorError");
}
}
}
Err(non_connect_error) => {
error!(?non_connect_error, "Request failed to dispatch");
}
}
}
Err(org_error) => {
// The original error is returned when into_source() fails.
error!(?org_error);
}
}
}

fn tracing_setup() {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1");
}
let _ = color_eyre::install();

if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}
tracing_subscriber::fmt::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
}
16 changes: 16 additions & 0 deletions examples/pokemon-service-client-usage/examples/use-config-bag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ impl Interceptor for GetTimeInterceptor {

Ok(())
}

fn read_after_execution(
&self,
_context: &pokemon_service_client::config::interceptors::FinalizerInterceptorContextRef<'_>,
_runtime_components: &pokemon_service_client::config::RuntimeComponents,
cfg: &mut aws_smithy_types::config_bag::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();
info!(time_taken = %time_taken.as_micros(), "Microseconds:");

Ok(())
}
}

/// Creates a new Smithy client that is configured to communicate with a locally running Pokemon service on TCP port 13734.
Expand Down

0 comments on commit 19c34c3

Please sign in to comment.