aws_types/
request_id.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! AWS-specific request ID support
7
8use aws_smithy_runtime_api::client::result::SdkError;
9use aws_smithy_runtime_api::http::Headers;
10use aws_smithy_runtime_api::http::Response;
11use aws_smithy_types::error::metadata::{Builder as ErrorMetadataBuilder, ErrorMetadata};
12
13/// Constant for the [`ErrorMetadata`] extra field that contains the request ID
14const AWS_REQUEST_ID: &str = "aws_request_id";
15
16/// Implementers add a function to return an AWS request ID
17pub trait RequestId {
18    /// Returns the request ID, or `None` if the service could not be reached.
19    fn request_id(&self) -> Option<&str>;
20}
21
22impl<E> RequestId for SdkError<E, Response> {
23    fn request_id(&self) -> Option<&str> {
24        match self {
25            Self::ResponseError(err) => err.raw().headers().request_id(),
26            Self::ServiceError(err) => err.raw().headers().request_id(),
27            _ => None,
28        }
29    }
30}
31
32impl RequestId for ErrorMetadata {
33    fn request_id(&self) -> Option<&str> {
34        self.extra(AWS_REQUEST_ID)
35    }
36}
37
38impl<B> RequestId for Response<B> {
39    fn request_id(&self) -> Option<&str> {
40        self.headers().request_id()
41    }
42}
43
44impl RequestId for Headers {
45    fn request_id(&self) -> Option<&str> {
46        self.get("x-amzn-requestid")
47            .or(self.get("x-amz-request-id"))
48    }
49}
50
51impl<O, E> RequestId for Result<O, E>
52where
53    O: RequestId,
54    E: RequestId,
55{
56    fn request_id(&self) -> Option<&str> {
57        match self {
58            Ok(ok) => ok.request_id(),
59            Err(err) => err.request_id(),
60        }
61    }
62}
63
64/// Applies a request ID to a generic error builder
65pub fn apply_request_id(builder: ErrorMetadataBuilder, headers: &Headers) -> ErrorMetadataBuilder {
66    if let Some(request_id) = headers.request_id() {
67        builder.custom(AWS_REQUEST_ID, request_id)
68    } else {
69        builder
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use crate::request_id::{apply_request_id, RequestId, AWS_REQUEST_ID};
76    use aws_smithy_runtime_api::client::orchestrator::HttpResponse;
77    use aws_smithy_runtime_api::client::result::SdkError;
78    use aws_smithy_runtime_api::http::Headers;
79    use aws_smithy_types::body::SdkBody;
80    use aws_smithy_types::error::ErrorMetadata;
81    use http::{HeaderValue, Response};
82
83    #[test]
84    fn test_request_id_sdk_error() {
85        let without_request_id =
86            || HttpResponse::try_from(Response::builder().body(SdkBody::empty()).unwrap()).unwrap();
87        let with_request_id = || {
88            HttpResponse::try_from(
89                Response::builder()
90                    .header(
91                        "x-amzn-requestid",
92                        HeaderValue::from_static("some-request-id"),
93                    )
94                    .body(SdkBody::empty())
95                    .unwrap(),
96            )
97            .unwrap()
98        };
99        assert_eq!(
100            None,
101            SdkError::<(), _>::response_error("test", without_request_id()).request_id()
102        );
103        assert_eq!(
104            Some("some-request-id"),
105            SdkError::<(), _>::response_error("test", with_request_id()).request_id()
106        );
107        assert_eq!(
108            None,
109            SdkError::service_error((), without_request_id()).request_id()
110        );
111        assert_eq!(
112            Some("some-request-id"),
113            SdkError::service_error((), with_request_id()).request_id()
114        );
115    }
116
117    #[test]
118    fn test_extract_request_id() {
119        let mut headers = Headers::new();
120        assert_eq!(None, headers.request_id());
121
122        headers.append(
123            "x-amzn-requestid",
124            HeaderValue::from_static("some-request-id"),
125        );
126        assert_eq!(Some("some-request-id"), headers.request_id());
127
128        headers.append(
129            "x-amz-request-id",
130            HeaderValue::from_static("other-request-id"),
131        );
132        assert_eq!(Some("some-request-id"), headers.request_id());
133
134        headers.remove("x-amzn-requestid");
135        assert_eq!(Some("other-request-id"), headers.request_id());
136    }
137
138    #[test]
139    fn test_apply_request_id() {
140        let mut headers = Headers::new();
141        assert_eq!(
142            ErrorMetadata::builder().build(),
143            apply_request_id(ErrorMetadata::builder(), &headers).build(),
144        );
145
146        headers.append(
147            "x-amzn-requestid",
148            HeaderValue::from_static("some-request-id"),
149        );
150        assert_eq!(
151            ErrorMetadata::builder()
152                .custom(AWS_REQUEST_ID, "some-request-id")
153                .build(),
154            apply_request_id(ErrorMetadata::builder(), &headers).build(),
155        );
156    }
157
158    #[test]
159    fn test_error_metadata_request_id_impl() {
160        let err = ErrorMetadata::builder()
161            .custom(AWS_REQUEST_ID, "some-request-id")
162            .build();
163        assert_eq!(Some("some-request-id"), err.request_id());
164    }
165}