Skip to main content

aws_smithy_runtime_api/client/
waiters.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/// Error types for waiters.
7pub mod error {
8    use crate::client::{
9        orchestrator::HttpResponse,
10        result::{ConstructionFailure, SdkError},
11    };
12    use crate::{box_error::BoxError, client::waiters::FinalPoll};
13    use aws_smithy_types::error::{
14        metadata::{ProvideErrorMetadata, EMPTY_ERROR_METADATA},
15        ErrorMetadata,
16    };
17    use std::{fmt, time::Duration};
18
19    /// An error occurred while waiting.
20    ///
21    /// This error type is useful for distinguishing between the max wait
22    /// time being exceeded, or some other failure occurring.
23    #[derive(Debug)]
24    #[non_exhaustive]
25    #[allow(clippy::large_enum_variant)] // For `OperationFailed` variant
26    pub enum WaiterError<O, E> {
27        /// An error occurred during waiter initialization.
28        ///
29        /// This can happen if the input/config is invalid.
30        ConstructionFailure(ConstructionFailure),
31
32        /// The maximum wait time was exceeded without completion.
33        ExceededMaxWait(ExceededMaxWait),
34
35        /// Waiting ended in a failure state.
36        ///
37        /// A failed waiter state can occur on a successful response from the server
38        /// if, for example, that response indicates that the thing being waited for
39        /// won't succeed/finish.
40        ///
41        /// A failure state error will only occur for successful or modeled error responses.
42        /// Unmodeled error responses will never make it into this error case.
43        FailureState(FailureState<O, E>),
44
45        /// A polling operation failed while waiting.
46        ///
47        /// This error will only occur for unmodeled errors. Modeled errors can potentially
48        /// be handled by the waiter logic, and will therefore end up in [`WaiterError::FailureState`].
49        ///
50        /// Note: If retry is configured, this means that the operation failed
51        /// after retrying the configured number of attempts.
52        OperationFailed(OperationFailed<E>),
53    }
54
55    impl<O, E> WaiterError<O, E> {
56        /// Construct a waiter construction failure with the given error source.
57        pub fn construction_failure(source: impl Into<BoxError>) -> Self {
58            Self::ConstructionFailure(ConstructionFailure::builder().source(source).build())
59        }
60    }
61
62    impl<O, E> std::error::Error for WaiterError<O, E>
63    where
64        O: fmt::Debug,
65        E: std::error::Error + fmt::Debug + 'static,
66    {
67        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
68            match self {
69                Self::ConstructionFailure(inner) => Some(&*inner.source),
70                Self::ExceededMaxWait(_) => None,
71                Self::FailureState(inner) => match &inner.final_poll.result {
72                    Ok(_) => None,
73                    Err(err) => Some(err),
74                },
75                Self::OperationFailed(inner) => Some(&inner.source),
76            }
77        }
78    }
79
80    impl<O, E> fmt::Display for WaiterError<O, E> {
81        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82            match self {
83                Self::ConstructionFailure(_) => f.write_str("failed to construct waiter"),
84                Self::ExceededMaxWait(ctx) => {
85                    write!(f, "exceeded max wait time ({:?})", ctx.max_wait)
86                }
87                Self::FailureState(_) => f.write_str("waiting failed"),
88                Self::OperationFailed(_) => f.write_str("operation failed while waiting"),
89            }
90        }
91    }
92
93    // Implement `ProvideErrorMetadata` so that request IDs can be discovered from waiter failures.
94    impl<O, E> ProvideErrorMetadata for WaiterError<O, E>
95    where
96        E: ProvideErrorMetadata,
97    {
98        fn meta(&self) -> &ErrorMetadata {
99            match self {
100                WaiterError::ConstructionFailure(_) | WaiterError::ExceededMaxWait(_) => {
101                    &EMPTY_ERROR_METADATA
102                }
103                WaiterError::FailureState(inner) => inner
104                    .final_poll()
105                    .as_result()
106                    .err()
107                    .map(ProvideErrorMetadata::meta)
108                    .unwrap_or(&EMPTY_ERROR_METADATA),
109                WaiterError::OperationFailed(inner) => inner.error().meta(),
110            }
111        }
112    }
113
114    /// Error context for [`WaiterError::ExceededMaxWait`].
115    #[derive(Debug)]
116    pub struct ExceededMaxWait {
117        max_wait: Duration,
118        elapsed: Duration,
119        poll_count: u32,
120    }
121
122    impl ExceededMaxWait {
123        /// Creates new error context.
124        pub fn new(max_wait: Duration, elapsed: Duration, poll_count: u32) -> Self {
125            Self {
126                max_wait,
127                elapsed,
128                poll_count,
129            }
130        }
131
132        /// Returns the configured max wait time that was exceeded.
133        pub fn max_wait(&self) -> Duration {
134            self.max_wait
135        }
136
137        /// How much time actually elapsed before max wait was triggered.
138        pub fn elapsed(&self) -> Duration {
139            self.elapsed
140        }
141
142        /// Returns the number of polling operations that were made before exceeding the max wait time.
143        pub fn poll_count(&self) -> u32 {
144            self.poll_count
145        }
146    }
147
148    /// Error context for [`WaiterError::FailureState`].
149    #[derive(Debug)]
150    #[non_exhaustive]
151    pub struct FailureState<O, E> {
152        final_poll: FinalPoll<O, E>,
153    }
154
155    impl<O, E> FailureState<O, E> {
156        /// Creates new error context given a final poll result.
157        pub fn new(final_poll: FinalPoll<O, E>) -> Self {
158            Self { final_poll }
159        }
160
161        /// Returns the result of the final polling attempt.
162        pub fn final_poll(&self) -> &FinalPoll<O, E> {
163            &self.final_poll
164        }
165
166        /// Grants ownership of the result of the final polling attempt.
167        pub fn into_final_poll(self) -> FinalPoll<O, E> {
168            self.final_poll
169        }
170    }
171
172    /// Error context for [`WaiterError::OperationFailed`].
173    #[derive(Debug)]
174    #[non_exhaustive]
175    pub struct OperationFailed<E> {
176        source: SdkError<E, HttpResponse>,
177    }
178
179    impl<E> OperationFailed<E> {
180        /// Creates new error context given a source [`SdkError`].
181        pub fn new(source: SdkError<E, HttpResponse>) -> Self {
182            Self { source }
183        }
184
185        /// Returns the underlying source [`SdkError`].
186        pub fn error(&self) -> &SdkError<E, HttpResponse> {
187            &self.source
188        }
189
190        /// Grants ownership of the underlying source [`SdkError`].
191        pub fn into_error(self) -> SdkError<E, HttpResponse> {
192            self.source
193        }
194    }
195}
196
197/// Result of the final polling attempt made by a waiter.
198///
199/// Waiters make several requests ("polls") to the remote service, and this
200/// struct holds the result of the final poll attempt that was made by the
201/// waiter so that it can be inspected.
202#[non_exhaustive]
203#[derive(Debug)]
204pub struct FinalPoll<O, E> {
205    result: Result<O, E>,
206}
207
208impl<O, E> FinalPoll<O, E> {
209    /// Creates a new `FinalPoll` from a result.
210    pub fn new(result: Result<O, E>) -> Self {
211        Self { result }
212    }
213
214    /// Grants ownership of the underlying result.
215    pub fn into_result(self) -> Result<O, E> {
216        self.result
217    }
218
219    /// Returns the underlying result.
220    pub fn as_result(&self) -> Result<&O, &E> {
221        self.result.as_ref()
222    }
223
224    /// Maps the operation type with a function.
225    pub fn map<O2, F: FnOnce(O) -> O2>(self, mapper: F) -> FinalPoll<O2, E> {
226        FinalPoll::new(self.result.map(mapper))
227    }
228
229    /// Maps the error type with a function.
230    pub fn map_err<E2, F: FnOnce(E) -> E2>(self, mapper: F) -> FinalPoll<O, E2> {
231        FinalPoll::new(self.result.map_err(mapper))
232    }
233}