From 19c34c310118d44baa902f0bfb8d71e0160e9fb2 Mon Sep 17 00:00:00 2001 From: Fahad Zubair Date: Tue, 10 Oct 2023 14:48:13 -0400 Subject: [PATCH] Add an interceptor error example --- .../examples/interceptor-errors.rs | 179 ++++++++++++++++++ .../examples/use-config-bag.rs | 16 ++ 2 files changed, 195 insertions(+) create mode 100644 examples/pokemon-service-client-usage/examples/interceptor-errors.rs diff --git a/examples/pokemon-service-client-usage/examples/interceptor-errors.rs b/examples/pokemon-service-client-usage/examples/interceptor-errors.rs new file mode 100644 index 00000000000..028f95b20fd --- /dev/null +++ b/examples/pokemon-service-client-usage/examples/interceptor-errors.rs @@ -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(error: SdkError>) +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 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::() { + 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(); +} diff --git a/examples/pokemon-service-client-usage/examples/use-config-bag.rs b/examples/pokemon-service-client-usage/examples/use-config-bag.rs index 14187bc3258..9457881aec9 100644 --- a/examples/pokemon-service-client-usage/examples/use-config-bag.rs +++ b/examples/pokemon-service-client-usage/examples/use-config-bag.rs @@ -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::() + .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.