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