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