1use 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
43pub mod endpoints;
45
46mod http;
48
49pub 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
96pub 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#[non_exhaustive]
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum StopPoint {
126 None,
128
129 BeforeTransmit,
131}
132
133pub async fn invoke_with_stop_point(
140 _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 !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#[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 let components = client_rc_builder
198 .merge_from(&operation_rc_builder)
199 .build()?;
200
201 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 run_interceptors!(halt_on_err: {
231 modify_before_serialization(ctx, runtime_components, cfg);
232 read_before_serialization(ctx, runtime_components, cfg);
233 });
234
235 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 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 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 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 ctx.save_checkpoint();
301 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 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 cfg.interceptor_state()
322 .store_put::<RequestAttempts>(i.into());
323 if let Some((delay, sleep)) = retry_delay.take() {
325 debug!("delaying for {delay:?}");
326 sleep.await;
327 }
328 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 continue_on_err!([ctx] => maybe_timeout);
356
357 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 ShouldAttempt::Yes => continue,
366 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 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
391async 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 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 halt_on_err!([ctx] => apply_endpoint(&endpoint, ctx, cfg).map_err(OrchestratorError::other));
451 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 if let StopPoint::BeforeTransmit = stop_point {
477 debug!("ending orchestration early because the stop point is `BeforeTransmit`");
478 return;
479 }
480
481 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 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 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 #[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 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}