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