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