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