66//! Endpoint override detection for business metrics tracking
77
88use aws_smithy_runtime_api:: box_error:: BoxError ;
9- use aws_smithy_runtime_api:: client:: interceptors:: context:: BeforeSerializationInterceptorContextRef ;
9+ use aws_smithy_runtime_api:: client:: interceptors:: context:: BeforeTransmitInterceptorContextRef ;
1010use aws_smithy_runtime_api:: client:: interceptors:: Intercept ;
11+ use aws_smithy_runtime_api:: client:: runtime_components:: RuntimeComponents ;
1112use aws_smithy_runtime_api:: client:: runtime_plugin:: RuntimePlugin ;
12- use aws_smithy_types:: config_bag:: { ConfigBag , FrozenLayer } ;
13+ use aws_smithy_types:: config_bag:: { ConfigBag , FrozenLayer , Layer } ;
1314
1415use crate :: sdk_feature:: AwsSdkFeature ;
1516
1617/// Interceptor that detects custom endpoint URLs for business metrics
18+ ///
19+ /// This interceptor checks at runtime if a `StaticUriEndpointResolver` is configured,
20+ /// which indicates that `.endpoint_url()` was called. When detected, it stores the
21+ /// `AwsSdkFeature::EndpointOverride` feature flag for business metrics tracking.
1722#[ derive( Debug , Default ) ]
1823#[ non_exhaustive]
1924pub struct EndpointOverrideInterceptor ;
@@ -30,19 +35,25 @@ impl Intercept for EndpointOverrideInterceptor {
3035 "EndpointOverrideInterceptor"
3136 }
3237
33- fn read_before_execution (
38+ fn read_after_serialization (
3439 & self ,
35- _context : & BeforeSerializationInterceptorContextRef < ' _ > ,
40+ _context : & BeforeTransmitInterceptorContextRef < ' _ > ,
41+ runtime_components : & RuntimeComponents ,
3642 cfg : & mut ConfigBag ,
3743 ) -> Result < ( ) , BoxError > {
38- // Check if endpoint_url was set in config
39- if cfg
40- . load :: < aws_types:: endpoint_config:: EndpointUrl > ( )
41- . is_some ( )
42- {
44+ // Check if the endpoint resolver is a StaticUriEndpointResolver
45+ // This indicates that .endpoint_url() was called
46+ let resolver = runtime_components. endpoint_resolver ( ) ;
47+
48+ // Check the resolver's debug string to see if it's StaticUriEndpointResolver
49+ let debug_str = format ! ( "{:?}" , resolver) ;
50+
51+ if debug_str. contains ( "StaticUriEndpointResolver" ) {
52+ // Store in interceptor_state
4353 cfg. interceptor_state ( )
4454 . store_append ( AwsSdkFeature :: EndpointOverride ) ;
4555 }
56+
4657 Ok ( ( ) )
4758 }
4859}
@@ -65,6 +76,15 @@ impl EndpointOverrideRuntimePlugin {
6576 pub fn new ( config : Option < FrozenLayer > ) -> Self {
6677 Self { config }
6778 }
79+
80+ /// Creates a new `EndpointOverrideRuntimePlugin` and marks that endpoint override is enabled
81+ pub fn new_with_feature_flag ( ) -> Self {
82+ let mut layer = Layer :: new ( "endpoint_override" ) ;
83+ layer. store_append ( AwsSdkFeature :: EndpointOverride ) ;
84+ Self {
85+ config : Some ( layer. freeze ( ) ) ,
86+ }
87+ }
6888}
6989
7090impl RuntimePlugin for EndpointOverrideRuntimePlugin {
@@ -77,10 +97,6 @@ impl RuntimePlugin for EndpointOverrideRuntimePlugin {
7797mod tests {
7898 use super :: * ;
7999 use crate :: sdk_feature:: AwsSdkFeature ;
80- use aws_smithy_runtime_api:: client:: interceptors:: context:: {
81- BeforeSerializationInterceptorContextRef , Input , InterceptorContext ,
82- } ;
83- use aws_smithy_types:: config_bag:: ConfigBag ;
84100
85101 #[ test]
86102 fn test_plugin_with_no_config ( ) {
@@ -89,56 +105,55 @@ mod tests {
89105 }
90106
91107 #[ test]
92- fn test_interceptor_detects_endpoint_url_when_present ( ) {
93- let interceptor = EndpointOverrideInterceptor :: new ( ) ;
94- let mut cfg = ConfigBag :: base ( ) ;
95-
96- // Set endpoint URL in config
97- let endpoint_url =
98- aws_types:: endpoint_config:: EndpointUrl ( "https://custom.example.com" . to_string ( ) ) ;
99- cfg. interceptor_state ( ) . store_put ( endpoint_url) ;
100-
101- // Create a dummy context
102- let input = Input :: doesnt_matter ( ) ;
103- let ctx = InterceptorContext :: new ( input) ;
104- let context = BeforeSerializationInterceptorContextRef :: from ( & ctx) ;
105-
106- // Run the interceptor
107- interceptor
108- . read_before_execution ( & context, & mut cfg)
109- . unwrap ( ) ;
108+ fn test_plugin_with_feature_flag ( ) {
109+ let plugin = EndpointOverrideRuntimePlugin :: new_with_feature_flag ( ) ;
110+ let config = plugin. config ( ) . expect ( "config should be set" ) ;
110111
111- // Verify feature flag was set in interceptor_state
112- let features: Vec < _ > = cfg
113- . interceptor_state ( )
114- . load :: < AwsSdkFeature > ( )
115- . cloned ( )
116- . collect ( ) ;
112+ // Verify the feature flag is present in the config
113+ let features: Vec < _ > = config. load :: < AwsSdkFeature > ( ) . cloned ( ) . collect ( ) ;
117114 assert_eq ! ( features. len( ) , 1 ) ;
118115 assert_eq ! ( features[ 0 ] , AwsSdkFeature :: EndpointOverride ) ;
119116 }
120117
121118 #[ test]
122- fn test_interceptor_does_not_set_flag_when_endpoint_url_absent ( ) {
123- let interceptor = EndpointOverrideInterceptor :: new ( ) ;
119+ fn test_interceptor_detects_static_uri_resolver ( ) {
120+ use aws_smithy_runtime:: client:: orchestrator:: endpoints:: StaticUriEndpointResolver ;
121+ use aws_smithy_runtime_api:: client:: endpoint:: SharedEndpointResolver ;
122+ use aws_smithy_runtime_api:: client:: interceptors:: context:: { Input , InterceptorContext } ;
123+ use aws_smithy_runtime_api:: client:: orchestrator:: HttpRequest ;
124+ use aws_smithy_runtime_api:: client:: runtime_components:: RuntimeComponentsBuilder ;
125+ use aws_smithy_types:: config_bag:: ConfigBag ;
126+
127+ // Create a StaticUriEndpointResolver
128+ let endpoint_resolver = SharedEndpointResolver :: new ( StaticUriEndpointResolver :: uri (
129+ "https://custom.example.com" ,
130+ ) ) ;
131+
132+ let mut context = InterceptorContext :: new ( Input :: doesnt_matter ( ) ) ;
133+ context. enter_serialization_phase ( ) ;
134+ context. set_request ( HttpRequest :: empty ( ) ) ;
135+ let _ = context. take_input ( ) ;
136+ context. enter_before_transmit_phase ( ) ;
137+
138+ let rc = RuntimeComponentsBuilder :: for_tests ( )
139+ . with_endpoint_resolver ( Some ( endpoint_resolver) )
140+ . build ( )
141+ . unwrap ( ) ;
124142 let mut cfg = ConfigBag :: base ( ) ;
125143
126- // Create a dummy context
127- let input = Input :: doesnt_matter ( ) ;
128- let ctx = InterceptorContext :: new ( input) ;
129- let context = BeforeSerializationInterceptorContextRef :: from ( & ctx) ;
130-
131- // Run the interceptor without setting endpoint URL
144+ let interceptor = EndpointOverrideInterceptor :: new ( ) ;
145+ let ctx = Into :: into ( & context) ;
132146 interceptor
133- . read_before_execution ( & context , & mut cfg)
147+ . read_after_serialization ( & ctx , & rc , & mut cfg)
134148 . unwrap ( ) ;
135149
136- // Verify no feature flag was set
150+ // Verify the feature flag was set
137151 let features: Vec < _ > = cfg
138152 . interceptor_state ( )
139153 . load :: < AwsSdkFeature > ( )
140154 . cloned ( )
141155 . collect ( ) ;
142- assert_eq ! ( features. len( ) , 0 ) ;
156+ assert_eq ! ( features. len( ) , 1 , "Expected 1 feature, got: {:?}" , features) ;
157+ assert_eq ! ( features[ 0 ] , AwsSdkFeature :: EndpointOverride ) ;
143158 }
144159}
0 commit comments