Skip to main content

aws_smithy_runtime/client/
orchestrator.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use crate::client::interceptors::Interceptors;
7use crate::client::orchestrator::http::{log_response_body, read_body};
8use crate::client::retries::LongPollingBackoff;
9use crate::client::timeout::{MaybeTimeout, MaybeTimeoutConfig, TimeoutKind};
10use crate::client::{
11    http::body::minimum_throughput::MaybeUploadThroughputCheckFuture,
12    orchestrator::endpoints::orchestrate_endpoint,
13};
14use auth::{resolve_identity, sign_request};
15use aws_smithy_async::rt::sleep::AsyncSleep;
16use aws_smithy_runtime_api::box_error::BoxError;
17use aws_smithy_runtime_api::client::http::{HttpClient, HttpConnector, HttpConnectorSettings};
18use aws_smithy_runtime_api::client::identity::ResolveCachedIdentity;
19use aws_smithy_runtime_api::client::interceptors::context::{
20    Error, Input, InterceptorContext, Output, RewindResult,
21};
22use aws_smithy_runtime_api::client::orchestrator::{
23    HttpResponse, LoadedRequestBody, OrchestratorError,
24};
25use aws_smithy_runtime_api::client::result::SdkError;
26use aws_smithy_runtime_api::client::retries::{RequestAttempts, RetryStrategy, ShouldAttempt};
27use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
28use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins;
29use aws_smithy_runtime_api::client::ser_de::{
30    DeserializeResponse, SerializeRequest, SharedRequestSerializer, SharedResponseDeserializer,
31};
32use aws_smithy_types::body::SdkBody;
33use aws_smithy_types::byte_stream::ByteStream;
34use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
35use aws_smithy_types::retry::{MergeRetryConfig, RetryConfig, RetrySpec};
36use aws_smithy_types::timeout::{MergeTimeoutConfig, TimeoutConfig};
37use endpoints::apply_endpoint;
38use std::mem;
39use tracing::{debug, debug_span, instrument, trace, Instrument};
40
41mod auth;
42pub use auth::AuthSchemeAndEndpointOrchestrationV2;
43
44/// Defines types that implement a trait for endpoint resolution
45pub mod endpoints;
46
47/// Defines types that work with HTTP types
48mod http;
49
50/// Utility for making one-off unmodeled requests with the orchestrator.
51pub mod operation;
52
53/// Config-bag marker requesting that the identity resolved for the current attempt be invalidated
54/// after the target service rejected it with an authentication failure.
55///
56/// Read (and consumed) at the end of `try_attempt`, while the resolved identity is still in
57/// scope, so it must be set no later than the `read_after_deserialization` interceptor hook.
58/// Setting it from `read_after_attempt` is too late: that hook runs afterward in `finally_attempt`,
59/// so the flag would be missed on the final (retries-exhausted) attempt.
60#[doc(hidden)]
61#[derive(Clone, Debug)]
62pub struct InvalidateResolvedIdentity;
63
64impl Storable for InvalidateResolvedIdentity {
65    type Storer = StoreReplace<Self>;
66}
67
68macro_rules! halt {
69    ([$ctx:ident] => $err:expr) => {{
70        debug!("encountered orchestrator error; halting");
71        $ctx.fail($err.into());
72        return;
73    }};
74}
75
76macro_rules! halt_on_err {
77    ([$ctx:ident] => $expr:expr) => {
78        match $expr {
79            Ok(ok) => ok,
80            Err(err) => halt!([$ctx] => err),
81        }
82    };
83}
84
85macro_rules! continue_on_err {
86    ([$ctx:ident] => $expr:expr) => {
87        if let Err(err) = $expr {
88            debug!(err = ?err, "encountered orchestrator error; continuing");
89            $ctx.fail(err.into());
90        }
91    };
92}
93
94macro_rules! run_interceptors {
95    (continue_on_err: { $($interceptor:ident($ctx:ident, $rc:ident, $cfg:ident);)+ }) => {
96        $(run_interceptors!(continue_on_err: $interceptor($ctx, $rc, $cfg));)+
97    };
98    (continue_on_err: $interceptor:ident($ctx:ident, $rc:ident, $cfg:ident)) => {
99        continue_on_err!([$ctx] => run_interceptors!(__private $interceptor($ctx, $rc, $cfg)))
100    };
101    (halt_on_err: { $($interceptor:ident($ctx:ident, $rc:ident, $cfg:ident);)+ }) => {
102        $(run_interceptors!(halt_on_err: $interceptor($ctx, $rc, $cfg));)+
103    };
104    (halt_on_err: $interceptor:ident($ctx:ident, $rc:ident, $cfg:ident)) => {
105        halt_on_err!([$ctx] => run_interceptors!(__private $interceptor($ctx, $rc, $cfg)))
106    };
107    (__private $interceptor:ident($ctx:ident, $rc:ident, $cfg:ident)) => {
108        Interceptors::new($rc.interceptors()).$interceptor($ctx, $rc, $cfg)
109    };
110}
111
112/// Orchestrates the execution of a request and handling of a response.
113///
114/// The given `runtime_plugins` will be used to generate a `ConfigBag` for this request,
115/// and then the given `input` will be serialized and transmitted. When a response is
116/// received, it will be deserialized and returned.
117///
118/// This orchestration handles retries, endpoint resolution, identity resolution, and signing.
119/// Each of these are configurable via the config and runtime components given by the runtime
120/// plugins.
121pub async fn invoke(
122    service_name: &str,
123    operation_name: &str,
124    input: Input,
125    runtime_plugins: &RuntimePlugins,
126) -> Result<Output, SdkError<Error, HttpResponse>> {
127    invoke_with_stop_point(
128        service_name,
129        operation_name,
130        input,
131        runtime_plugins,
132        StopPoint::None,
133    )
134    .await?
135    .finalize()
136}
137
138/// Allows for returning early at different points during orchestration.
139#[non_exhaustive]
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum StopPoint {
142    /// Don't stop orchestration early
143    None,
144
145    /// Stop the orchestrator before transmitting the request
146    BeforeTransmit,
147}
148
149/// Same as [`invoke`], but allows for returning early at different points during orchestration.
150///
151/// Orchestration will cease at the point specified by `stop_point`. This is useful for orchestrations
152/// that don't need to actually transmit requests, such as for generating presigned requests.
153///
154/// See the docs on [`invoke`] for more details.
155pub async fn invoke_with_stop_point(
156    // NOTE: service_name and operation_name were at one point used for instrumentation that is now
157    // handled as part of codegen. Manually constructed operations (e.g. via Operation::builder())
158    // are handled as part of Operation::invoke
159    _service_name: &str,
160    _operation_name: &str,
161    input: Input,
162    runtime_plugins: &RuntimePlugins,
163    stop_point: StopPoint,
164) -> Result<InterceptorContext, SdkError<Error, HttpResponse>> {
165    async move {
166        let mut cfg = ConfigBag::base();
167        let cfg = &mut cfg;
168
169        let mut ctx = InterceptorContext::new(input);
170
171        let runtime_components = apply_configuration(&mut ctx, cfg, runtime_plugins)
172            .map_err(SdkError::construction_failure)?;
173        trace!(runtime_components = ?runtime_components);
174
175        let operation_timeout_config =
176            MaybeTimeoutConfig::new(&runtime_components, cfg, TimeoutKind::Operation);
177        trace!(operation_timeout_config = ?operation_timeout_config);
178        async {
179            // If running the pre-execution interceptors failed, then we skip running the op and run the
180            // final interceptors instead.
181            if !ctx.is_failed() {
182                try_op(&mut ctx, cfg, &runtime_components, stop_point).await;
183            }
184            finally_op(&mut ctx, cfg, &runtime_components).await;
185            if ctx.is_failed() {
186                Err(ctx.finalize().expect_err("it is failed"))
187            } else {
188                Ok(ctx)
189            }
190        }
191        .maybe_timeout(operation_timeout_config)
192        .await
193    }
194    .await
195}
196
197/// Apply configuration is responsible for apply runtime plugins to the config bag, as well as running
198/// `read_before_execution` interceptors. If a failure occurs due to config construction, `invoke`
199/// will raise it to the user. If an interceptor fails, then `invoke`
200#[instrument(skip_all, level = "debug")]
201fn apply_configuration(
202    ctx: &mut InterceptorContext,
203    cfg: &mut ConfigBag,
204    runtime_plugins: &RuntimePlugins,
205) -> Result<RuntimeComponents, BoxError> {
206    let client_rc_builder = runtime_plugins.apply_client_configuration(cfg)?;
207    continue_on_err!([ctx] => Interceptors::new(client_rc_builder.interceptors()).read_before_execution(false, ctx, cfg));
208
209    let operation_rc_builder = runtime_plugins.apply_operation_configuration(cfg)?;
210    continue_on_err!([ctx] => Interceptors::new(operation_rc_builder.interceptors()).read_before_execution(true, ctx, cfg));
211
212    // The order below is important. Client interceptors must run before operation interceptors.
213    let components = client_rc_builder
214        .merge_from(&operation_rc_builder)
215        .build()?;
216
217    // In an ideal world, we'd simply update `cfg.load` to behave this way. Unfortunately, we can't
218    // do that without a breaking change. By overwriting the value in the config bag with a merged
219    // version, we can achieve a very similar behavior. `MergeTimeoutConfig`
220    let resolved_timeout_config = cfg.load::<MergeTimeoutConfig>();
221    debug!(
222        "timeout settings for this operation: {:?}",
223        resolved_timeout_config
224    );
225    cfg.interceptor_state().store_put(resolved_timeout_config);
226
227    let resolved_retry_config = cfg.load::<MergeRetryConfig>();
228    debug!(
229        "retry settings for this operation: {:?}",
230        resolved_retry_config
231    );
232    cfg.interceptor_state().store_put(resolved_retry_config);
233
234    components.validate_final_config(cfg)?;
235    Ok(components)
236}
237
238#[instrument(skip_all, level = "debug")]
239async fn try_op(
240    ctx: &mut InterceptorContext,
241    cfg: &mut ConfigBag,
242    runtime_components: &RuntimeComponents,
243    stop_point: StopPoint,
244) {
245    // Before serialization
246    run_interceptors!(halt_on_err: {
247        modify_before_serialization(ctx, runtime_components, cfg);
248        read_before_serialization(ctx, runtime_components, cfg);
249    });
250
251    // Serialization
252    ctx.enter_serialization_phase();
253    {
254        let _span = debug_span!("serialization").entered();
255        let request_serializer = cfg
256            .load::<SharedRequestSerializer>()
257            .expect("request serializer must be in the config bag")
258            .clone();
259        let input = ctx.take_input().expect("input set at this point");
260        let request = halt_on_err!([ctx] => request_serializer.serialize_input(input, cfg).map_err(OrchestratorError::other));
261        ctx.set_request(request);
262    }
263
264    // Load the request body into memory if configured to do so
265    if let Some(&LoadedRequestBody::Requested) = cfg.load::<LoadedRequestBody>() {
266        debug!("loading request body into memory");
267        let mut body = SdkBody::taken();
268        mem::swap(&mut body, ctx.request_mut().expect("set above").body_mut());
269        let loaded_body = halt_on_err!([ctx] =>
270            ByteStream::new(body).collect().await.map_err(OrchestratorError::other)
271        )
272        .into_bytes();
273        *ctx.request_mut().as_mut().expect("set above").body_mut() =
274            SdkBody::from(loaded_body.clone());
275        cfg.interceptor_state()
276            .store_put(LoadedRequestBody::Loaded(loaded_body));
277    }
278
279    // Before transmit
280    ctx.enter_before_transmit_phase();
281    run_interceptors!(halt_on_err: {
282        read_after_serialization(ctx, runtime_components, cfg);
283        modify_before_retry_loop(ctx, runtime_components, cfg);
284    });
285
286    // Loop to acquire a send token from the adaptive rate limiter.
287    // When capacity is insufficient, the strategy returns YesAfterDelay.
288    // After sleeping, we re-check because other tasks may have consumed
289    // tokens during our sleep.
290    let retry_strategy = runtime_components.retry_strategy();
291    loop {
292        let should_attempt = retry_strategy.should_attempt_initial_request(runtime_components, cfg);
293        match should_attempt {
294            Ok(ShouldAttempt::Yes) => {
295                debug!("retry strategy has OKed initial request");
296                break;
297            }
298            Ok(ShouldAttempt::No) => {
299                let err: BoxError = "the retry strategy indicates that an initial request shouldn't be made, but it didn't specify why".into();
300                halt!([ctx] => OrchestratorError::other(err));
301            }
302            Err(err) => halt!([ctx] => OrchestratorError::other(err)),
303            Ok(ShouldAttempt::YesAfterDelay(delay)) => {
304                let sleep_impl = halt_on_err!([ctx] => runtime_components.sleep_impl().ok_or_else(|| OrchestratorError::other(
305                    "the retry strategy requested a delay before sending the initial request, but no 'async sleep' implementation was set"
306                )));
307                debug!("retry strategy has OKed initial request after a {delay:?} delay");
308                sleep_impl.sleep(delay).await;
309                continue;
310            }
311        }
312    }
313
314    // Save a request checkpoint before we make the request. This will allow us to "rewind"
315    // the request in the case of retry attempts.
316    ctx.save_checkpoint();
317    // For long-polling operations, seed a shared slot so the retry strategy can
318    // communicate a backoff delay when the token bucket is empty.
319    if cfg
320        .load::<RetryConfig>()
321        .and_then(|rc| rc.retry_spec())
322        .is_some_and(|s| s.long_polling())
323    {
324        cfg.interceptor_state()
325            .store_put(LongPollingBackoff::default());
326    }
327    let mut retry_delay = None;
328    for i in 1u32.. {
329        // Break from the loop if we can't rewind the request's state. This will always succeed the
330        // first time, but will fail on subsequent iterations if the request body wasn't retryable.
331        trace!("checking if context can be rewound for attempt #{i}");
332        if let RewindResult::Impossible = ctx.rewind(cfg) {
333            debug!("request cannot be retried since the request body cannot be cloned");
334            break;
335        }
336        // Track which attempt we're currently on.
337        cfg.interceptor_state()
338            .store_put::<RequestAttempts>(i.into());
339        // Backoff time should not be included in the attempt timeout
340        if let Some((delay, sleep)) = retry_delay.take() {
341            debug!("delaying for {delay:?}");
342            sleep.await;
343        }
344        // Acquire an adaptive-rate-limiter send token before each retry send
345        // (the initial attempt acquired its token above). This is a no-op
346        // unless the client is in adaptive mode under Retry Behavior 2.1 -- see
347        // `acquire_adaptive_send_token`.
348        if i > 1 {
349            halt_on_err!([ctx] => acquire_adaptive_send_token(cfg, runtime_components)
350                .await
351                .map_err(OrchestratorError::other));
352        }
353        let attempt_timeout_config =
354            MaybeTimeoutConfig::new(runtime_components, cfg, TimeoutKind::OperationAttempt);
355        trace!(attempt_timeout_config = ?attempt_timeout_config);
356        let maybe_timeout = async {
357            debug!("beginning attempt #{i}");
358            try_attempt(ctx, cfg, runtime_components, stop_point)
359                .instrument(debug_span!("try_attempt", "attempt" = i))
360                .await;
361            finally_attempt(ctx, cfg, runtime_components)
362                .instrument(debug_span!("finally_attempt", "attempt" = i))
363                .await;
364            Result::<_, SdkError<Error, HttpResponse>>::Ok(())
365        }
366        .maybe_timeout(attempt_timeout_config)
367        .await
368        .map_err(|err| OrchestratorError::timeout(err.into_source().unwrap()));
369
370        // We continue when encountering a timeout error. The retry classifier will decide what to do with it.
371        continue_on_err!([ctx] => maybe_timeout);
372
373        // If we got a retry strategy from the bag, ask it what to do.
374        // If no strategy was set, we won't retry.
375        let should_attempt = halt_on_err!([ctx] => runtime_components
376            .retry_strategy()
377            .should_attempt_retry(ctx, runtime_components, cfg)
378            .map_err(OrchestratorError::other));
379        match should_attempt {
380            // Yes, let's retry the request
381            ShouldAttempt::Yes => continue,
382            // No, this request shouldn't be retried
383            ShouldAttempt::No => {
384                debug!("a retry is either unnecessary or not possible, exiting attempt loop");
385                if let Some(delay) = cfg.load::<LongPollingBackoff>().and_then(|h| h.take()) {
386                    if let Some(sleep_impl) = runtime_components.sleep_impl() {
387                        // This sleep is inside the operation timeout, so it gets cancelled
388                        // if one is set. Without an operation timeout, the delay is bounded
389                        // by `max_backoff` (enforced upstream in `calculate_backoff`).
390                        debug!("backing off {delay:?} before returning (no retry quota available)");
391                        sleep_impl.sleep(delay).await;
392                    }
393                }
394                break;
395            }
396            ShouldAttempt::YesAfterDelay(delay) => {
397                let sleep_impl = halt_on_err!([ctx] => runtime_components.sleep_impl().ok_or_else(|| OrchestratorError::other(
398                    "the retry strategy requested a delay before sending the retry request, but no 'async sleep' implementation was set"
399                )));
400                retry_delay = Some((delay, sleep_impl.sleep(delay)));
401                continue;
402            }
403        }
404    }
405}
406
407// Acquires one send token from the adaptive client-side rate limiter before a
408// retry send, mirroring the Retry Behavior 2.1 spec's `GetSendToken()` step,
409// which charges one token per attempt (the initial attempt is charged earlier,
410// in `try_op`'s send loop).
411//
412// This is meaningful only in `adaptive` retry mode under Retry Behavior 2.1, and
413// is a deliberate no-op otherwise:
414//   - non-adaptive modes (`standard`/`legacy`) have no rate limiter, so the
415//     retry strategy OKs the send immediately; and
416//   - pre-2.1 adaptive folds the limiter delay into the retry backoff instead
417//     (see `check_rate_limiter_for_delay`), so it is left untouched here.
418//
419// When the bucket has no capacity, the strategy returns a delay instead of
420// charging the token. We sleep that delay and then re-acquire, so capacity is
421// re-checked after the wait and is never driven negative -- concurrent attempts
422// may have drained the bucket while we waited, and it must stay >= 0.
423async fn acquire_adaptive_send_token(
424    cfg: &ConfigBag,
425    runtime_components: &RuntimeComponents,
426) -> Result<(), BoxError> {
427    let is_v2_1 = cfg
428        .load::<RetryConfig>()
429        .and_then(|rc| rc.retry_spec())
430        .is_some_and(|s| s.is_at_least(RetrySpec::V2_1));
431    if !is_v2_1 {
432        return Ok(());
433    }
434    let retry_strategy = runtime_components.retry_strategy();
435    loop {
436        match retry_strategy.should_attempt_initial_request(runtime_components, cfg)? {
437            // `GetSendToken` only gates on rate-limiter capacity; it never
438            // forbids the send, so both `Yes` and `No` mean "proceed".
439            ShouldAttempt::Yes | ShouldAttempt::No => return Ok(()),
440            ShouldAttempt::YesAfterDelay(delay) => {
441                let sleep_impl = runtime_components.sleep_impl().ok_or(
442                    "the retry strategy requested a delay before sending a retry, \
443                     but no 'async sleep' implementation was set",
444                )?;
445                debug!("adaptive rate limiter delayed a retry send by {delay:?}");
446                sleep_impl.sleep(delay).await;
447            }
448        }
449    }
450}
451
452async fn try_attempt(
453    ctx: &mut InterceptorContext,
454    cfg: &mut ConfigBag,
455    runtime_components: &RuntimeComponents,
456    stop_point: StopPoint,
457) {
458    run_interceptors!(halt_on_err: read_before_attempt(ctx, runtime_components, cfg));
459
460    let (scheme_id, identity, endpoint) = halt_on_err!([ctx] => resolve_identity(runtime_components, cfg).await.map_err(OrchestratorError::other));
461
462    match endpoint {
463        Some(endpoint) => {
464            // This branch is for backward compatibility when `AuthSchemeAndEndpointOrchestrationV2` is not present in the config bag.
465            // `resolve_identity` internally resolved an endpoint to determine the most suitable scheme ID, and returned that endpoint.
466            halt_on_err!([ctx] => apply_endpoint(&endpoint, ctx, cfg).map_err(OrchestratorError::other));
467            // Make the endpoint config available to interceptors
468            cfg.interceptor_state().store_put(endpoint);
469        }
470        None => {
471            halt_on_err!([ctx] => orchestrate_endpoint(identity.clone(), ctx, runtime_components, cfg)
472				    .instrument(debug_span!("orchestrate_endpoint"))
473				    .await
474				    .map_err(OrchestratorError::other));
475        }
476    }
477
478    run_interceptors!(halt_on_err: {
479        modify_before_signing(ctx, runtime_components, cfg);
480        read_before_signing(ctx, runtime_components, cfg);
481    });
482
483    halt_on_err!([ctx] => sign_request(&scheme_id, &identity, ctx, runtime_components, cfg).map_err(OrchestratorError::other));
484
485    run_interceptors!(halt_on_err: {
486        read_after_signing(ctx, runtime_components, cfg);
487        modify_before_transmit(ctx, runtime_components, cfg);
488        read_before_transmit(ctx, runtime_components, cfg);
489    });
490
491    // Return early if a stop point is set for before transmit
492    if let StopPoint::BeforeTransmit = stop_point {
493        debug!("ending orchestration early because the stop point is `BeforeTransmit`");
494        return;
495    }
496
497    // The connection consumes the request but we need to keep a copy of it
498    // within the interceptor context, so we clone it here.
499    ctx.enter_transmit_phase();
500    let response = halt_on_err!([ctx] => {
501        let request = ctx.take_request().expect("set during serialization");
502        trace!(request = ?request, "transmitting request");
503        let http_client = halt_on_err!([ctx] => runtime_components.http_client().ok_or_else(||
504            OrchestratorError::other("No HTTP client was available to send this request. \
505                Enable the `default-https-client` crate feature or configure an HTTP client to fix this.")
506        ));
507        let timeout_config = cfg.load::<TimeoutConfig>().expect("timeout config must be set");
508        let settings = {
509            let mut builder = HttpConnectorSettings::builder();
510            builder.set_connect_timeout(timeout_config.connect_timeout());
511            builder.set_read_timeout(timeout_config.read_timeout());
512            builder.build()
513        };
514        let connector = http_client.http_connector(&settings, runtime_components);
515        let response_future = MaybeUploadThroughputCheckFuture::new(
516            cfg,
517            runtime_components,
518            connector.call(request),
519        );
520        response_future.await.map_err(OrchestratorError::connector)
521    });
522    trace!(response = ?response, "received response from service");
523    ctx.set_response(response);
524    ctx.enter_before_deserialization_phase();
525
526    run_interceptors!(halt_on_err: {
527        read_after_transmit(ctx, runtime_components, cfg);
528        modify_before_deserialization(ctx, runtime_components, cfg);
529        read_before_deserialization(ctx, runtime_components, cfg);
530    });
531
532    ctx.enter_deserialization_phase();
533    let output_or_error = async {
534        let response = ctx.response_mut().expect("set during transmit");
535        let response_deserializer = cfg
536            .load::<SharedResponseDeserializer>()
537            .expect("a request deserializer must be in the config bag");
538        let maybe_deserialized = {
539            let _span = debug_span!("deserialize_streaming").entered();
540            response_deserializer.deserialize_streaming_with_config(response, cfg)
541        };
542        match maybe_deserialized {
543            Some(output_or_error) => output_or_error,
544            None => read_body(response)
545                .instrument(debug_span!("read_body"))
546                .await
547                .map_err(OrchestratorError::response)
548                .and_then(|_| {
549                    let _span = debug_span!("deserialize_nonstreaming").entered();
550                    log_response_body(response, cfg);
551                    response_deserializer.deserialize_nonstreaming_with_config(response, cfg)
552                }),
553        }
554    }
555    .instrument(debug_span!("deserialization"))
556    .await;
557    trace!(output_or_error = ?output_or_error);
558    ctx.set_output_or_error(output_or_error);
559
560    ctx.enter_after_deserialization_phase();
561    run_interceptors!(halt_on_err: read_after_deserialization(ctx, runtime_components, cfg));
562
563    // An interceptor may flag (via `InvalidateResolvedIdentity`) that the service rejected the
564    // resolved identity. Honor it with the in-scope signing identity, then consume the marker so a
565    // stale flag can't affect the next attempt. `invalidate` is a trait-default no-op for caches
566    // that don't override it.
567    if cfg.load::<InvalidateResolvedIdentity>().is_some() {
568        runtime_components.identity_cache().invalidate(&identity);
569        cfg.interceptor_state()
570            .unset::<InvalidateResolvedIdentity>();
571    }
572}
573
574async fn finally_attempt(
575    ctx: &mut InterceptorContext,
576    cfg: &mut ConfigBag,
577    runtime_components: &RuntimeComponents,
578) {
579    run_interceptors!(continue_on_err: {
580        modify_before_attempt_completion(ctx, runtime_components, cfg);
581        read_after_attempt(ctx, runtime_components, cfg);
582    });
583}
584
585#[instrument(skip_all, level = "debug")]
586async fn finally_op(
587    ctx: &mut InterceptorContext,
588    cfg: &mut ConfigBag,
589    runtime_components: &RuntimeComponents,
590) {
591    run_interceptors!(continue_on_err: {
592        modify_before_completion(ctx, runtime_components, cfg);
593        read_after_execution(ctx, runtime_components, cfg);
594    });
595}
596
597#[cfg(all(test, any(feature = "test-util", feature = "legacy-test-util")))]
598mod tests {
599    use crate::client::auth::no_auth::{NoAuthRuntimePluginV2, NO_AUTH_SCHEME_ID};
600    use crate::client::orchestrator::endpoints::StaticUriEndpointResolver;
601    use crate::client::orchestrator::{invoke, invoke_with_stop_point, StopPoint};
602    use crate::client::retries::strategy::NeverRetryStrategy;
603    use crate::client::test_util::{
604        deserializer::CannedResponseDeserializer, serializer::CannedRequestSerializer,
605    };
606    use aws_smithy_http_client::test_util::NeverClient;
607    use aws_smithy_runtime_api::box_error::BoxError;
608    use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolver;
609    use aws_smithy_runtime_api::client::auth::{
610        AuthSchemeOptionResolverParams, SharedAuthSchemeOptionResolver,
611    };
612    use aws_smithy_runtime_api::client::endpoint::{
613        EndpointResolverParams, SharedEndpointResolver,
614    };
615    use aws_smithy_runtime_api::client::http::{
616        http_client_fn, HttpConnector, HttpConnectorFuture,
617    };
618    use aws_smithy_runtime_api::client::interceptors::context::{
619        AfterDeserializationInterceptorContextRef, BeforeDeserializationInterceptorContextMut,
620        BeforeDeserializationInterceptorContextRef, BeforeSerializationInterceptorContextMut,
621        BeforeSerializationInterceptorContextRef, BeforeTransmitInterceptorContextMut,
622        BeforeTransmitInterceptorContextRef, FinalizerInterceptorContextMut,
623        FinalizerInterceptorContextRef, Input, Output,
624    };
625    use aws_smithy_runtime_api::client::interceptors::{Intercept, SharedInterceptor};
626    use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, OrchestratorError};
627    use aws_smithy_runtime_api::client::retries::SharedRetryStrategy;
628    use aws_smithy_runtime_api::client::runtime_components::{
629        RuntimeComponents, RuntimeComponentsBuilder,
630    };
631    use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, RuntimePlugins};
632    use aws_smithy_runtime_api::client::ser_de::{
633        SharedRequestSerializer, SharedResponseDeserializer,
634    };
635    use aws_smithy_runtime_api::shared::IntoShared;
636    use aws_smithy_types::body::SdkBody;
637    use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer};
638    use aws_smithy_types::timeout::TimeoutConfig;
639    use http_1x::{Response, StatusCode};
640    use std::borrow::Cow;
641    use std::sync::atomic::{AtomicBool, Ordering};
642    use std::sync::Arc;
643    use tracing_test::traced_test;
644
645    fn new_request_serializer() -> CannedRequestSerializer {
646        CannedRequestSerializer::success(HttpRequest::empty())
647    }
648
649    fn new_response_deserializer() -> CannedResponseDeserializer {
650        CannedResponseDeserializer::new(
651            Response::builder()
652                .status(StatusCode::OK)
653                .body(SdkBody::empty())
654                .map_err(|err| OrchestratorError::other(Box::new(err)))
655                .map(Output::erase),
656        )
657    }
658
659    #[derive(Debug, Default)]
660    struct OkConnector {}
661
662    impl OkConnector {
663        fn new() -> Self {
664            Self::default()
665        }
666    }
667
668    impl HttpConnector for OkConnector {
669        fn call(&self, _request: HttpRequest) -> HttpConnectorFuture {
670            HttpConnectorFuture::ready(Ok(http_1x::Response::builder()
671                .status(200)
672                .body(SdkBody::empty())
673                .expect("OK response is valid")
674                .try_into()
675                .unwrap()))
676        }
677    }
678
679    #[derive(Debug)]
680    struct TestOperationRuntimePlugin {
681        builder: RuntimeComponentsBuilder,
682    }
683
684    impl TestOperationRuntimePlugin {
685        fn new() -> Self {
686            Self {
687                builder: RuntimeComponentsBuilder::for_tests()
688                    .with_retry_strategy(Some(SharedRetryStrategy::new(NeverRetryStrategy::new())))
689                    .with_endpoint_resolver(Some(SharedEndpointResolver::new(
690                        StaticUriEndpointResolver::http_localhost(8080),
691                    )))
692                    .with_http_client(Some(http_client_fn(|_, _| {
693                        OkConnector::new().into_shared()
694                    })))
695                    .with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new(
696                        StaticAuthSchemeOptionResolver::new(vec![NO_AUTH_SCHEME_ID]),
697                    ))),
698            }
699        }
700    }
701
702    impl RuntimePlugin for TestOperationRuntimePlugin {
703        fn config(&self) -> Option<FrozenLayer> {
704            let mut layer = Layer::new("TestOperationRuntimePlugin");
705            layer.store_put(AuthSchemeOptionResolverParams::new("idontcare"));
706            layer.store_put(EndpointResolverParams::new("dontcare"));
707            layer.store_put(SharedRequestSerializer::new(new_request_serializer()));
708            layer.store_put(SharedResponseDeserializer::new(new_response_deserializer()));
709            layer.store_put(TimeoutConfig::builder().build());
710            Some(layer.freeze())
711        }
712
713        fn runtime_components(
714            &self,
715            _: &RuntimeComponentsBuilder,
716        ) -> Cow<'_, RuntimeComponentsBuilder> {
717            Cow::Borrowed(&self.builder)
718        }
719    }
720
721    macro_rules! interceptor_error_handling_test {
722        (read_before_execution, $ctx:ty, $expected:expr,) => {
723            interceptor_error_handling_test!(__private read_before_execution, $ctx, $expected,);
724        };
725        ($interceptor:ident, $ctx:ty, $expected:expr) => {
726            interceptor_error_handling_test!(__private $interceptor, $ctx, $expected, _rc: &RuntimeComponents,);
727        };
728        (__private $interceptor:ident, $ctx:ty, $expected:expr, $($rc_arg:tt)*) => {
729            #[derive(Debug)]
730            struct FailingInterceptorA;
731            impl Intercept for FailingInterceptorA {
732                fn name(&self) -> &'static str { "FailingInterceptorA" }
733
734                fn $interceptor(
735                    &self,
736                    _ctx: $ctx,
737                    $($rc_arg)*
738                    _cfg: &mut ConfigBag,
739                ) -> Result<(), BoxError> {
740                    tracing::debug!("FailingInterceptorA called!");
741                    Err("FailingInterceptorA".into())
742                }
743            }
744
745            #[derive(Debug)]
746            struct FailingInterceptorB;
747            impl Intercept for FailingInterceptorB {
748                fn name(&self) -> &'static str { "FailingInterceptorB" }
749
750                fn $interceptor(
751                    &self,
752                    _ctx: $ctx,
753                    $($rc_arg)*
754                    _cfg: &mut ConfigBag,
755                ) -> Result<(), BoxError> {
756                    tracing::debug!("FailingInterceptorB called!");
757                    Err("FailingInterceptorB".into())
758                }
759            }
760
761            #[derive(Debug)]
762            struct FailingInterceptorC;
763            impl Intercept for FailingInterceptorC {
764                fn name(&self) -> &'static str { "FailingInterceptorC" }
765
766                fn $interceptor(
767                    &self,
768                    _ctx: $ctx,
769                    $($rc_arg)*
770                    _cfg: &mut ConfigBag,
771                ) -> Result<(), BoxError> {
772                    tracing::debug!("FailingInterceptorC called!");
773                    Err("FailingInterceptorC".into())
774                }
775            }
776
777            #[derive(Debug)]
778            struct FailingInterceptorsClientRuntimePlugin(RuntimeComponentsBuilder);
779            impl FailingInterceptorsClientRuntimePlugin {
780                fn new() -> Self {
781                    Self(RuntimeComponentsBuilder::new("test").with_interceptor(SharedInterceptor::new(FailingInterceptorA)))
782                }
783            }
784            impl RuntimePlugin for FailingInterceptorsClientRuntimePlugin {
785                fn runtime_components(&self, _: &RuntimeComponentsBuilder) -> Cow<'_, RuntimeComponentsBuilder> {
786                    Cow::Borrowed(&self.0)
787                }
788            }
789
790            #[derive(Debug)]
791            struct FailingInterceptorsOperationRuntimePlugin(RuntimeComponentsBuilder);
792            impl FailingInterceptorsOperationRuntimePlugin {
793                fn new() -> Self {
794                    Self(
795                        RuntimeComponentsBuilder::new("test")
796                            .with_interceptor(SharedInterceptor::new(FailingInterceptorB))
797                            .with_interceptor(SharedInterceptor::new(FailingInterceptorC))
798                    )
799                }
800            }
801            impl RuntimePlugin for FailingInterceptorsOperationRuntimePlugin {
802                fn runtime_components(&self, _: &RuntimeComponentsBuilder) -> Cow<'_, RuntimeComponentsBuilder> {
803                    Cow::Borrowed(&self.0)
804                }
805            }
806
807            let input = Input::doesnt_matter();
808            let runtime_plugins = RuntimePlugins::new()
809                .with_client_plugin(FailingInterceptorsClientRuntimePlugin::new())
810                .with_operation_plugin(TestOperationRuntimePlugin::new())
811                .with_operation_plugin(NoAuthRuntimePluginV2::new())
812                .with_operation_plugin(FailingInterceptorsOperationRuntimePlugin::new());
813            let actual = invoke("test", "test", input, &runtime_plugins)
814                .await
815                .expect_err("should error");
816            let actual = format!("{:?}", actual);
817            assert!(
818                actual.starts_with(&$expected),
819                "\nActual error:      {actual}\nShould start with: {}\n",
820                $expected
821            );
822
823            assert!(logs_contain("FailingInterceptorA called!"));
824            assert!(logs_contain("FailingInterceptorB called!"));
825            assert!(logs_contain("FailingInterceptorC called!"));
826        };
827    }
828
829    #[tokio::test]
830    #[traced_test]
831    async fn test_read_before_execution_error_handling() {
832        let expected = r#"ConstructionFailure(ConstructionFailure { source: InterceptorError { kind: ReadBeforeExecution, interceptor_name: Some("FailingInterceptorC"), source: Some("FailingInterceptorC") } })"#.to_string();
833        interceptor_error_handling_test!(
834            read_before_execution,
835            &BeforeSerializationInterceptorContextRef<'_>,
836            expected,
837        );
838    }
839
840    #[tokio::test]
841    #[traced_test]
842    async fn test_modify_before_serialization_error_handling() {
843        let expected = r#"ConstructionFailure(ConstructionFailure { source: InterceptorError { kind: ModifyBeforeSerialization, interceptor_name: Some("FailingInterceptorC"), source: Some("FailingInterceptorC") } })"#.to_string();
844        interceptor_error_handling_test!(
845            modify_before_serialization,
846            &mut BeforeSerializationInterceptorContextMut<'_>,
847            expected
848        );
849    }
850
851    #[tokio::test]
852    #[traced_test]
853    async fn test_read_before_serialization_error_handling() {
854        let expected = r#"ConstructionFailure(ConstructionFailure { source: InterceptorError { kind: ReadBeforeSerialization, interceptor_name: Some("FailingInterceptorC"), source: Some("FailingInterceptorC") } })"#.to_string();
855        interceptor_error_handling_test!(
856            read_before_serialization,
857            &BeforeSerializationInterceptorContextRef<'_>,
858            expected
859        );
860    }
861
862    #[tokio::test]
863    #[traced_test]
864    async fn test_read_after_serialization_error_handling() {
865        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ReadAfterSerialization, interceptor_name: Some("FailingInterceptorC")"#.to_string();
866        interceptor_error_handling_test!(
867            read_after_serialization,
868            &BeforeTransmitInterceptorContextRef<'_>,
869            expected
870        );
871    }
872
873    #[tokio::test]
874    #[traced_test]
875    async fn test_modify_before_retry_loop_error_handling() {
876        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeRetryLoop, interceptor_name: Some("FailingInterceptorC")"#.to_string();
877        interceptor_error_handling_test!(
878            modify_before_retry_loop,
879            &mut BeforeTransmitInterceptorContextMut<'_>,
880            expected
881        );
882    }
883
884    #[tokio::test]
885    #[traced_test]
886    async fn test_read_before_attempt_error_handling() {
887        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ReadBeforeAttempt, interceptor_name: Some("FailingInterceptorC")"#;
888        interceptor_error_handling_test!(
889            read_before_attempt,
890            &BeforeTransmitInterceptorContextRef<'_>,
891            expected
892        );
893    }
894
895    #[tokio::test]
896    #[traced_test]
897    async fn test_modify_before_signing_error_handling() {
898        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeSigning, interceptor_name: Some("FailingInterceptorC")"#;
899        interceptor_error_handling_test!(
900            modify_before_signing,
901            &mut BeforeTransmitInterceptorContextMut<'_>,
902            expected
903        );
904    }
905
906    #[tokio::test]
907    #[traced_test]
908    async fn test_read_before_signing_error_handling() {
909        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ReadBeforeSigning, interceptor_name: Some("FailingInterceptorC")"#;
910        interceptor_error_handling_test!(
911            read_before_signing,
912            &BeforeTransmitInterceptorContextRef<'_>,
913            expected
914        );
915    }
916
917    #[tokio::test]
918    #[traced_test]
919    async fn test_read_after_signing_error_handling() {
920        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ReadAfterSigning, interceptor_name: Some("FailingInterceptorC")"#;
921        interceptor_error_handling_test!(
922            read_after_signing,
923            &BeforeTransmitInterceptorContextRef<'_>,
924            expected
925        );
926    }
927
928    #[tokio::test]
929    #[traced_test]
930    async fn test_modify_before_transmit_error_handling() {
931        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeTransmit, interceptor_name: Some("FailingInterceptorC")"#;
932        interceptor_error_handling_test!(
933            modify_before_transmit,
934            &mut BeforeTransmitInterceptorContextMut<'_>,
935            expected
936        );
937    }
938
939    #[tokio::test]
940    #[traced_test]
941    async fn test_read_before_transmit_error_handling() {
942        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ReadBeforeTransmit, interceptor_name: Some("FailingInterceptorC")"#;
943        interceptor_error_handling_test!(
944            read_before_transmit,
945            &BeforeTransmitInterceptorContextRef<'_>,
946            expected
947        );
948    }
949
950    #[tokio::test]
951    #[traced_test]
952    async fn test_read_after_transmit_error_handling() {
953        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ReadAfterTransmit, interceptor_name: Some("FailingInterceptorC")"#;
954        interceptor_error_handling_test!(
955            read_after_transmit,
956            &BeforeDeserializationInterceptorContextRef<'_>,
957            expected
958        );
959    }
960
961    #[tokio::test]
962    #[traced_test]
963    async fn test_modify_before_deserialization_error_handling() {
964        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ModifyBeforeDeserialization, interceptor_name: Some("FailingInterceptorC")"#;
965        interceptor_error_handling_test!(
966            modify_before_deserialization,
967            &mut BeforeDeserializationInterceptorContextMut<'_>,
968            expected
969        );
970    }
971
972    #[tokio::test]
973    #[traced_test]
974    async fn test_read_before_deserialization_error_handling() {
975        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ReadBeforeDeserialization, interceptor_name: Some("FailingInterceptorC")"#;
976        interceptor_error_handling_test!(
977            read_before_deserialization,
978            &BeforeDeserializationInterceptorContextRef<'_>,
979            expected
980        );
981    }
982
983    #[tokio::test]
984    #[traced_test]
985    async fn test_read_after_deserialization_error_handling() {
986        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ReadAfterDeserialization, interceptor_name: Some("FailingInterceptorC")"#;
987        interceptor_error_handling_test!(
988            read_after_deserialization,
989            &AfterDeserializationInterceptorContextRef<'_>,
990            expected
991        );
992    }
993
994    #[tokio::test]
995    #[traced_test]
996    async fn test_modify_before_attempt_completion_error_handling() {
997        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("FailingInterceptorC")"#;
998        interceptor_error_handling_test!(
999            modify_before_attempt_completion,
1000            &mut FinalizerInterceptorContextMut<'_>,
1001            expected
1002        );
1003    }
1004
1005    #[tokio::test]
1006    #[traced_test]
1007    async fn test_read_after_attempt_error_handling() {
1008        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ReadAfterAttempt, interceptor_name: Some("FailingInterceptorC")"#;
1009        interceptor_error_handling_test!(
1010            read_after_attempt,
1011            &FinalizerInterceptorContextRef<'_>,
1012            expected
1013        );
1014    }
1015
1016    #[tokio::test]
1017    #[traced_test]
1018    async fn test_modify_before_completion_error_handling() {
1019        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ModifyBeforeCompletion, interceptor_name: Some("FailingInterceptorC")"#;
1020        interceptor_error_handling_test!(
1021            modify_before_completion,
1022            &mut FinalizerInterceptorContextMut<'_>,
1023            expected
1024        );
1025    }
1026
1027    #[tokio::test]
1028    #[traced_test]
1029    async fn test_read_after_execution_error_handling() {
1030        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ReadAfterExecution, interceptor_name: Some("FailingInterceptorC")"#;
1031        interceptor_error_handling_test!(
1032            read_after_execution,
1033            &FinalizerInterceptorContextRef<'_>,
1034            expected
1035        );
1036    }
1037
1038    macro_rules! interceptor_error_redirection_test {
1039        (read_before_execution, $origin_ctx:ty, $destination_interceptor:ident, $destination_ctx:ty, $expected:expr) => {
1040            interceptor_error_redirection_test!(__private read_before_execution, $origin_ctx, $destination_interceptor, $destination_ctx, $expected,);
1041        };
1042        ($origin_interceptor:ident, $origin_ctx:ty, $destination_interceptor:ident, $destination_ctx:ty, $expected:expr) => {
1043            interceptor_error_redirection_test!(__private $origin_interceptor, $origin_ctx, $destination_interceptor, $destination_ctx, $expected, _rc: &RuntimeComponents,);
1044        };
1045        (__private $origin_interceptor:ident, $origin_ctx:ty, $destination_interceptor:ident, $destination_ctx:ty, $expected:expr, $($rc_arg:tt)*) => {
1046            #[derive(Debug)]
1047            struct OriginInterceptor;
1048            impl Intercept for OriginInterceptor {
1049                fn name(&self) -> &'static str { "OriginInterceptor" }
1050
1051                fn $origin_interceptor(
1052                    &self,
1053                    _ctx: $origin_ctx,
1054                    $($rc_arg)*
1055                    _cfg: &mut ConfigBag,
1056                ) -> Result<(), BoxError> {
1057                    tracing::debug!("OriginInterceptor called!");
1058                    Err("OriginInterceptor".into())
1059                }
1060            }
1061
1062            #[derive(Debug)]
1063            struct DestinationInterceptor;
1064            impl Intercept for DestinationInterceptor {
1065                fn name(&self) -> &'static str { "DestinationInterceptor" }
1066
1067                fn $destination_interceptor(
1068                    &self,
1069                    _ctx: $destination_ctx,
1070                    _runtime_components: &RuntimeComponents,
1071                    _cfg: &mut ConfigBag,
1072                ) -> Result<(), BoxError> {
1073                    tracing::debug!("DestinationInterceptor called!");
1074                    Err("DestinationInterceptor".into())
1075                }
1076            }
1077
1078            #[derive(Debug)]
1079            struct InterceptorsTestOperationRuntimePlugin(RuntimeComponentsBuilder);
1080            impl InterceptorsTestOperationRuntimePlugin {
1081                fn new() -> Self {
1082                    Self(
1083                        RuntimeComponentsBuilder::new("test")
1084                            .with_interceptor(SharedInterceptor::new(OriginInterceptor))
1085                            .with_interceptor(SharedInterceptor::new(DestinationInterceptor))
1086                    )
1087                }
1088            }
1089            impl RuntimePlugin for InterceptorsTestOperationRuntimePlugin {
1090                fn runtime_components(&self, _: &RuntimeComponentsBuilder) -> Cow<'_, RuntimeComponentsBuilder> {
1091                    Cow::Borrowed(&self.0)
1092                }
1093            }
1094
1095            let input = Input::doesnt_matter();
1096            let runtime_plugins = RuntimePlugins::new()
1097                .with_operation_plugin(TestOperationRuntimePlugin::new())
1098                .with_operation_plugin(NoAuthRuntimePluginV2::new())
1099                .with_operation_plugin(InterceptorsTestOperationRuntimePlugin::new());
1100            let actual = invoke("test", "test", input, &runtime_plugins)
1101                .await
1102                .expect_err("should error");
1103            let actual = format!("{:?}", actual);
1104            assert!(
1105                actual.starts_with(&$expected),
1106                "\nActual error:      {actual}\nShould start with: {}\n",
1107                $expected
1108            );
1109
1110            assert!(logs_contain("OriginInterceptor called!"));
1111            assert!(logs_contain("DestinationInterceptor called!"));
1112        };
1113    }
1114
1115    #[tokio::test]
1116    #[traced_test]
1117    async fn test_read_before_execution_error_causes_jump_to_modify_before_completion() {
1118        let expected = r#"ConstructionFailure(ConstructionFailure { source: InterceptorError { kind: ModifyBeforeCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1119        interceptor_error_redirection_test!(
1120            read_before_execution,
1121            &BeforeSerializationInterceptorContextRef<'_>,
1122            modify_before_completion,
1123            &mut FinalizerInterceptorContextMut<'_>,
1124            expected
1125        );
1126    }
1127
1128    #[tokio::test]
1129    #[traced_test]
1130    async fn test_modify_before_serialization_error_causes_jump_to_modify_before_completion() {
1131        let expected = r#"ConstructionFailure(ConstructionFailure { source: InterceptorError { kind: ModifyBeforeCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1132        interceptor_error_redirection_test!(
1133            modify_before_serialization,
1134            &mut BeforeSerializationInterceptorContextMut<'_>,
1135            modify_before_completion,
1136            &mut FinalizerInterceptorContextMut<'_>,
1137            expected
1138        );
1139    }
1140
1141    #[tokio::test]
1142    #[traced_test]
1143    async fn test_read_before_serialization_error_causes_jump_to_modify_before_completion() {
1144        let expected = r#"ConstructionFailure(ConstructionFailure { source: InterceptorError { kind: ModifyBeforeCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1145        interceptor_error_redirection_test!(
1146            read_before_serialization,
1147            &BeforeSerializationInterceptorContextRef<'_>,
1148            modify_before_completion,
1149            &mut FinalizerInterceptorContextMut<'_>,
1150            expected
1151        );
1152    }
1153
1154    #[tokio::test]
1155    #[traced_test]
1156    async fn test_read_after_serialization_error_causes_jump_to_modify_before_completion() {
1157        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1158        interceptor_error_redirection_test!(
1159            read_after_serialization,
1160            &BeforeTransmitInterceptorContextRef<'_>,
1161            modify_before_completion,
1162            &mut FinalizerInterceptorContextMut<'_>,
1163            expected
1164        );
1165    }
1166
1167    #[tokio::test]
1168    #[traced_test]
1169    async fn test_modify_before_retry_loop_error_causes_jump_to_modify_before_completion() {
1170        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1171        interceptor_error_redirection_test!(
1172            modify_before_retry_loop,
1173            &mut BeforeTransmitInterceptorContextMut<'_>,
1174            modify_before_completion,
1175            &mut FinalizerInterceptorContextMut<'_>,
1176            expected
1177        );
1178    }
1179
1180    #[tokio::test]
1181    #[traced_test]
1182    async fn test_read_before_attempt_error_causes_jump_to_modify_before_attempt_completion() {
1183        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1184        interceptor_error_redirection_test!(
1185            read_before_attempt,
1186            &BeforeTransmitInterceptorContextRef<'_>,
1187            modify_before_attempt_completion,
1188            &mut FinalizerInterceptorContextMut<'_>,
1189            expected
1190        );
1191    }
1192
1193    #[tokio::test]
1194    #[traced_test]
1195    async fn test_modify_before_signing_error_causes_jump_to_modify_before_attempt_completion() {
1196        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1197        interceptor_error_redirection_test!(
1198            modify_before_signing,
1199            &mut BeforeTransmitInterceptorContextMut<'_>,
1200            modify_before_attempt_completion,
1201            &mut FinalizerInterceptorContextMut<'_>,
1202            expected
1203        );
1204    }
1205
1206    #[tokio::test]
1207    #[traced_test]
1208    async fn test_read_before_signing_error_causes_jump_to_modify_before_attempt_completion() {
1209        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1210        interceptor_error_redirection_test!(
1211            read_before_signing,
1212            &BeforeTransmitInterceptorContextRef<'_>,
1213            modify_before_attempt_completion,
1214            &mut FinalizerInterceptorContextMut<'_>,
1215            expected
1216        );
1217    }
1218
1219    #[tokio::test]
1220    #[traced_test]
1221    async fn test_read_after_signing_error_causes_jump_to_modify_before_attempt_completion() {
1222        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1223        interceptor_error_redirection_test!(
1224            read_after_signing,
1225            &BeforeTransmitInterceptorContextRef<'_>,
1226            modify_before_attempt_completion,
1227            &mut FinalizerInterceptorContextMut<'_>,
1228            expected
1229        );
1230    }
1231
1232    #[tokio::test]
1233    #[traced_test]
1234    async fn test_modify_before_transmit_error_causes_jump_to_modify_before_attempt_completion() {
1235        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1236        interceptor_error_redirection_test!(
1237            modify_before_transmit,
1238            &mut BeforeTransmitInterceptorContextMut<'_>,
1239            modify_before_attempt_completion,
1240            &mut FinalizerInterceptorContextMut<'_>,
1241            expected
1242        );
1243    }
1244
1245    #[tokio::test]
1246    #[traced_test]
1247    async fn test_read_before_transmit_error_causes_jump_to_modify_before_attempt_completion() {
1248        let expected = r#"DispatchFailure(DispatchFailure { source: ConnectorError { kind: Other(None), source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1249        interceptor_error_redirection_test!(
1250            read_before_transmit,
1251            &BeforeTransmitInterceptorContextRef<'_>,
1252            modify_before_attempt_completion,
1253            &mut FinalizerInterceptorContextMut<'_>,
1254            expected
1255        );
1256    }
1257
1258    #[tokio::test]
1259    #[traced_test]
1260    async fn test_read_after_transmit_error_causes_jump_to_modify_before_attempt_completion() {
1261        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1262        interceptor_error_redirection_test!(
1263            read_after_transmit,
1264            &BeforeDeserializationInterceptorContextRef<'_>,
1265            modify_before_attempt_completion,
1266            &mut FinalizerInterceptorContextMut<'_>,
1267            expected
1268        );
1269    }
1270
1271    #[tokio::test]
1272    #[traced_test]
1273    async fn test_modify_before_deserialization_error_causes_jump_to_modify_before_attempt_completion(
1274    ) {
1275        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1276        interceptor_error_redirection_test!(
1277            modify_before_deserialization,
1278            &mut BeforeDeserializationInterceptorContextMut<'_>,
1279            modify_before_attempt_completion,
1280            &mut FinalizerInterceptorContextMut<'_>,
1281            expected
1282        );
1283    }
1284
1285    #[tokio::test]
1286    #[traced_test]
1287    async fn test_read_before_deserialization_error_causes_jump_to_modify_before_attempt_completion(
1288    ) {
1289        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1290        interceptor_error_redirection_test!(
1291            read_before_deserialization,
1292            &BeforeDeserializationInterceptorContextRef<'_>,
1293            modify_before_attempt_completion,
1294            &mut FinalizerInterceptorContextMut<'_>,
1295            expected
1296        );
1297    }
1298
1299    #[tokio::test]
1300    #[traced_test]
1301    async fn test_read_after_deserialization_error_causes_jump_to_modify_before_attempt_completion()
1302    {
1303        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ModifyBeforeAttemptCompletion, interceptor_name: Some("DestinationInterceptor")"#;
1304        interceptor_error_redirection_test!(
1305            read_after_deserialization,
1306            &AfterDeserializationInterceptorContextRef<'_>,
1307            modify_before_attempt_completion,
1308            &mut FinalizerInterceptorContextMut<'_>,
1309            expected
1310        );
1311    }
1312
1313    #[tokio::test]
1314    #[traced_test]
1315    async fn test_modify_before_attempt_completion_error_causes_jump_to_read_after_attempt() {
1316        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ReadAfterAttempt, interceptor_name: Some("DestinationInterceptor")"#;
1317        interceptor_error_redirection_test!(
1318            modify_before_attempt_completion,
1319            &mut FinalizerInterceptorContextMut<'_>,
1320            read_after_attempt,
1321            &FinalizerInterceptorContextRef<'_>,
1322            expected
1323        );
1324    }
1325
1326    #[tokio::test]
1327    #[traced_test]
1328    async fn test_modify_before_completion_error_causes_jump_to_read_after_execution() {
1329        let expected = r#"ResponseError(ResponseError { source: InterceptorError { kind: ReadAfterExecution, interceptor_name: Some("DestinationInterceptor")"#;
1330        interceptor_error_redirection_test!(
1331            modify_before_completion,
1332            &mut FinalizerInterceptorContextMut<'_>,
1333            read_after_execution,
1334            &FinalizerInterceptorContextRef<'_>,
1335            expected
1336        );
1337    }
1338
1339    #[tokio::test]
1340    async fn test_stop_points() {
1341        let runtime_plugins = || {
1342            RuntimePlugins::new()
1343                .with_operation_plugin(TestOperationRuntimePlugin::new())
1344                .with_operation_plugin(NoAuthRuntimePluginV2::new())
1345        };
1346
1347        // StopPoint::None should result in a response getting set since orchestration doesn't stop
1348        let context = invoke_with_stop_point(
1349            "test",
1350            "test",
1351            Input::doesnt_matter(),
1352            &runtime_plugins(),
1353            StopPoint::None,
1354        )
1355        .await
1356        .expect("success");
1357        assert!(context.response().is_some());
1358
1359        // StopPoint::BeforeTransmit will exit right before sending the request, so there should be no response
1360        let context = invoke_with_stop_point(
1361            "test",
1362            "test",
1363            Input::doesnt_matter(),
1364            &runtime_plugins(),
1365            StopPoint::BeforeTransmit,
1366        )
1367        .await
1368        .expect("success");
1369        assert!(context.response().is_none());
1370    }
1371
1372    /// The "finally" interceptors should run upon error when the StopPoint is set to BeforeTransmit
1373    #[tokio::test]
1374    async fn test_stop_points_error_handling() {
1375        #[derive(Debug, Default)]
1376        struct Inner {
1377            modify_before_retry_loop_called: AtomicBool,
1378            modify_before_completion_called: AtomicBool,
1379            read_after_execution_called: AtomicBool,
1380        }
1381        #[derive(Clone, Debug, Default)]
1382        struct TestInterceptor {
1383            inner: Arc<Inner>,
1384        }
1385
1386        impl Intercept for TestInterceptor {
1387            fn name(&self) -> &'static str {
1388                "TestInterceptor"
1389            }
1390
1391            fn modify_before_retry_loop(
1392                &self,
1393                _context: &mut BeforeTransmitInterceptorContextMut<'_>,
1394                _rc: &RuntimeComponents,
1395                _cfg: &mut ConfigBag,
1396            ) -> Result<(), BoxError> {
1397                self.inner
1398                    .modify_before_retry_loop_called
1399                    .store(true, Ordering::Relaxed);
1400                Err("test error".into())
1401            }
1402
1403            fn modify_before_completion(
1404                &self,
1405                _context: &mut FinalizerInterceptorContextMut<'_>,
1406                _rc: &RuntimeComponents,
1407                _cfg: &mut ConfigBag,
1408            ) -> Result<(), BoxError> {
1409                self.inner
1410                    .modify_before_completion_called
1411                    .store(true, Ordering::Relaxed);
1412                Ok(())
1413            }
1414
1415            fn read_after_execution(
1416                &self,
1417                _context: &FinalizerInterceptorContextRef<'_>,
1418                _rc: &RuntimeComponents,
1419                _cfg: &mut ConfigBag,
1420            ) -> Result<(), BoxError> {
1421                self.inner
1422                    .read_after_execution_called
1423                    .store(true, Ordering::Relaxed);
1424                Ok(())
1425            }
1426        }
1427
1428        #[derive(Debug)]
1429        struct TestInterceptorRuntimePlugin {
1430            builder: RuntimeComponentsBuilder,
1431        }
1432
1433        impl RuntimePlugin for TestInterceptorRuntimePlugin {
1434            fn runtime_components(
1435                &self,
1436                _: &RuntimeComponentsBuilder,
1437            ) -> Cow<'_, RuntimeComponentsBuilder> {
1438                Cow::Borrowed(&self.builder)
1439            }
1440        }
1441
1442        let interceptor = TestInterceptor::default();
1443        let client = NeverClient::new();
1444        let runtime_plugins = || {
1445            RuntimePlugins::new()
1446                .with_operation_plugin(TestOperationRuntimePlugin::new())
1447                .with_operation_plugin(NoAuthRuntimePluginV2::new())
1448                .with_operation_plugin(TestInterceptorRuntimePlugin {
1449                    builder: RuntimeComponentsBuilder::new("test")
1450                        .with_interceptor(SharedInterceptor::new(interceptor.clone()))
1451                        .with_http_client(Some(client.clone())),
1452                })
1453        };
1454
1455        // StopPoint::BeforeTransmit will exit right before sending the request, so there should be no response
1456        let _err = invoke_with_stop_point(
1457            "test",
1458            "test",
1459            Input::doesnt_matter(),
1460            &runtime_plugins(),
1461            StopPoint::BeforeTransmit,
1462        )
1463        .await
1464        .expect_err("an error was returned");
1465        assert_eq!(client.num_calls(), 0);
1466
1467        assert!(interceptor
1468            .inner
1469            .modify_before_retry_loop_called
1470            .load(Ordering::Relaxed));
1471        assert!(interceptor
1472            .inner
1473            .modify_before_completion_called
1474            .load(Ordering::Relaxed));
1475        assert!(interceptor
1476            .inner
1477            .read_after_execution_called
1478            .load(Ordering::Relaxed));
1479    }
1480}