|
| 1 | +/* |
| 2 | + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | + * SPDX-License-Identifier: Apache-2.0 |
| 4 | + */ |
| 5 | + |
| 6 | +use aws_smithy_runtime_api::client::interceptors::error::BoxError; |
| 7 | +use aws_smithy_runtime_api::client::interceptors::{Interceptor, InterceptorContext}; |
| 8 | +use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; |
| 9 | +use aws_smithy_runtime_api::config_bag::ConfigBag; |
| 10 | +use http::{HeaderName, HeaderValue}; |
| 11 | +use uuid::Uuid; |
| 12 | + |
| 13 | +#[allow(clippy::declare_interior_mutable_const)] // we will never mutate this |
| 14 | +const AMZ_SDK_INVOCATION_ID: HeaderName = HeaderName::from_static("amz-sdk-invocation-id"); |
| 15 | + |
| 16 | +/// This interceptor generates a UUID and attaches it to all request attempts made as part of this operation. |
| 17 | +#[non_exhaustive] |
| 18 | +#[derive(Debug)] |
| 19 | +pub struct InvocationIdInterceptor { |
| 20 | + id: HeaderValue, |
| 21 | +} |
| 22 | + |
| 23 | +impl InvocationIdInterceptor { |
| 24 | + /// Creates a new `InvocationIdInterceptor` |
| 25 | + pub fn new() -> Self { |
| 26 | + Self::default() |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +impl Default for InvocationIdInterceptor { |
| 31 | + fn default() -> Self { |
| 32 | + let id = Uuid::new_v4(); |
| 33 | + let id = id |
| 34 | + .to_string() |
| 35 | + .parse() |
| 36 | + .expect("UUIDs always produce a valid header value"); |
| 37 | + Self { id } |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +impl Interceptor<HttpRequest, HttpResponse> for InvocationIdInterceptor { |
| 42 | + fn modify_before_retry_loop( |
| 43 | + &self, |
| 44 | + context: &mut InterceptorContext<HttpRequest, HttpResponse>, |
| 45 | + _cfg: &mut ConfigBag, |
| 46 | + ) -> Result<(), BoxError> { |
| 47 | + let headers = context.request_mut()?.headers_mut(); |
| 48 | + headers.append(AMZ_SDK_INVOCATION_ID, self.id.clone()); |
| 49 | + Ok(()) |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +#[cfg(test)] |
| 54 | +mod tests { |
| 55 | + use crate::invocation_id::InvocationIdInterceptor; |
| 56 | + use aws_smithy_http::body::SdkBody; |
| 57 | + use aws_smithy_runtime_api::client::interceptors::{Interceptor, InterceptorContext}; |
| 58 | + use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; |
| 59 | + use aws_smithy_runtime_api::config_bag::ConfigBag; |
| 60 | + use aws_smithy_runtime_api::type_erasure::TypedBox; |
| 61 | + use http::HeaderValue; |
| 62 | + |
| 63 | + fn expect_header<'a>( |
| 64 | + context: &'a InterceptorContext<HttpRequest, HttpResponse>, |
| 65 | + header_name: &str, |
| 66 | + ) -> &'a HeaderValue { |
| 67 | + context |
| 68 | + .request() |
| 69 | + .unwrap() |
| 70 | + .headers() |
| 71 | + .get(header_name) |
| 72 | + .unwrap() |
| 73 | + } |
| 74 | + |
| 75 | + #[test] |
| 76 | + fn test_id_is_generated_and_set() { |
| 77 | + let mut context = InterceptorContext::new(TypedBox::new("doesntmatter").erase()); |
| 78 | + context.set_request(http::Request::builder().body(SdkBody::empty()).unwrap()); |
| 79 | + |
| 80 | + let mut config = ConfigBag::base(); |
| 81 | + let interceptor = InvocationIdInterceptor::new(); |
| 82 | + interceptor |
| 83 | + .modify_before_retry_loop(&mut context, &mut config) |
| 84 | + .unwrap(); |
| 85 | + |
| 86 | + let header = expect_header(&context, "amz-sdk-invocation-id"); |
| 87 | + assert_eq!(&interceptor.id, header); |
| 88 | + // UUID should include 32 chars and 4 dashes |
| 89 | + assert_eq!(interceptor.id.len(), 36); |
| 90 | + } |
| 91 | +} |
0 commit comments