Skip to content

Commit b54fd60

Browse files
committed
Address code review feedback: improve tests, use type-safe OTel detection, and remove redundant code
1 parent 341c4ef commit b54fd60

File tree

15 files changed

+197
-215
lines changed

15 files changed

+197
-215
lines changed

aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ val DECORATORS: List<ClientCodegenDecorator> =
6666
AwsRequestIdDecorator(),
6767
DisabledAuthDecorator(),
6868
RecursionDetectionDecorator(),
69-
EndpointOverrideDecorator(),
7069
ObservabilityDetectionDecorator(),
7170
InvocationIdDecorator(),
7271
RetryInformationHeaderDecorator(),

aws/rust-runtime/aws-runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" }
2222
aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true }
2323
aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" }
2424
aws-smithy-observability = { path = "../../../rust-runtime/aws-smithy-observability" }
25+
aws-smithy-observability-otel = { path = "../../../rust-runtime/aws-smithy-observability-otel" }
2526
aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", features = ["client"] }
2627
aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] }
2728
aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" }

aws/rust-runtime/aws-runtime/src/endpoint_override.rs

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
//! Endpoint override detection for business metrics tracking
77
88
use 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;
1010
use aws_smithy_runtime_api::client::interceptors::Intercept;
11+
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
1112
use 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

1415
use 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]
1924
pub 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

7090
impl RuntimePlugin for EndpointOverrideRuntimePlugin {
@@ -77,10 +97,6 @@ impl RuntimePlugin for EndpointOverrideRuntimePlugin {
7797
mod 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
}

aws/rust-runtime/aws-runtime/src/observability_detection.rs

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
*/
55

66
//! Observability feature detection for business metrics tracking
7+
//!
8+
//! This module provides an interceptor for detecting observability features in the AWS SDK:
9+
//!
10+
//! - [`ObservabilityDetectionInterceptor`]: Detects observability features during
11+
//! request processing and tracks them for business metrics in the User-Agent header.
712
13+
use aws_smithy_observability_otel::meter::OtelMeterProvider;
814
use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature;
915
use aws_smithy_runtime_api::box_error::BoxError;
1016
use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef;
1117
use aws_smithy_runtime_api::client::interceptors::Intercept;
1218
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
1319
use aws_smithy_types::config_bag::ConfigBag;
1420

15-
use crate::sdk_feature::AwsSdkFeature;
16-
1721
/// Interceptor that detects when observability features are being used
1822
/// and tracks them for business metrics.
1923
#[derive(Debug, Default)]
@@ -40,32 +44,18 @@ impl Intercept for ObservabilityDetectionInterceptor {
4044
) -> Result<(), BoxError> {
4145
// Try to get the global telemetry provider
4246
if let Ok(provider) = aws_smithy_observability::global::get_telemetry_provider() {
43-
// Check if it's not a noop provider
44-
if !provider.is_noop() {
47+
// Use type-safe downcasting to detect OpenTelemetry meter provider
48+
// This works with any ProvideMeter implementation and doesn't require
49+
// implementation-specific boolean flags
50+
if provider
51+
.meter_provider()
52+
.as_any()
53+
.downcast_ref::<OtelMeterProvider>()
54+
.is_some()
55+
{
4556
// Track that observability metrics are enabled
4657
cfg.interceptor_state()
47-
.store_append(AwsSdkFeature::ObservabilityMetrics);
48-
49-
// PRAGMATIC APPROACH: Track tracing based on meter provider state
50-
//
51-
// The SDK uses Rust's `tracing` crate globally but doesn't have a
52-
// configurable tracer provider yet. We make the reasonable assumption
53-
// that if a user has configured a meter provider, they've also set up
54-
// tracing as part of their observability stack.
55-
//
56-
// This is a pragmatic workaround until a proper tracer provider is added.
57-
cfg.interceptor_state()
58-
.store_append(SmithySdkFeature::ObservabilityTracing);
59-
60-
// Check if it's using OpenTelemetry
61-
if provider.is_otel() {
62-
cfg.interceptor_state()
63-
.store_append(AwsSdkFeature::ObservabilityOtelMetrics);
64-
65-
// If using OpenTelemetry for metrics, likely using it for tracing too
66-
cfg.interceptor_state()
67-
.store_append(AwsSdkFeature::ObservabilityOtelTracing);
68-
}
58+
.store_append(SmithySdkFeature::ObservabilityMetrics);
6959
}
7060
}
7161

@@ -76,6 +66,7 @@ impl Intercept for ObservabilityDetectionInterceptor {
7666
#[cfg(test)]
7767
mod tests {
7868
use super::*;
69+
use crate::sdk_feature::AwsSdkFeature;
7970
use aws_smithy_observability::TelemetryProvider;
8071
use aws_smithy_runtime_api::client::interceptors::context::{Input, InterceptorContext};
8172
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
@@ -102,12 +93,85 @@ mod tests {
10293
.read_before_signing(&ctx, &rc, &mut cfg)
10394
.unwrap();
10495

105-
// Should not track any features for noop provider
106-
let features: Vec<_> = cfg.load::<AwsSdkFeature>().collect();
107-
assert_eq!(features.len(), 0);
96+
// Should not track any features for noop provider since it doesn't downcast to OtelMeterProvider
97+
let smithy_features: Vec<_> = cfg.load::<SmithySdkFeature>().collect();
98+
assert_eq!(smithy_features.len(), 0);
99+
100+
let aws_features: Vec<_> = cfg.load::<AwsSdkFeature>().collect();
101+
assert_eq!(aws_features.len(), 0);
108102
}
109103

110-
// Note: We cannot easily test non-noop providers without creating a custom meter provider
111-
// implementation, which would require exposing internal types. The noop test above
112-
// is sufficient to verify the detection logic works correctly.
104+
#[test]
105+
fn test_custom_provider_not_detected_as_otel() {
106+
use aws_smithy_observability::meter::{Meter, ProvideMeter};
107+
use aws_smithy_observability::noop::NoopMeterProvider;
108+
use aws_smithy_observability::Attributes;
109+
use std::sync::Arc;
110+
111+
// Create a custom (non-OTel, non-noop) meter provider
112+
// This simulates a user implementing their own metrics provider
113+
#[derive(Debug)]
114+
struct CustomMeterProvider {
115+
inner: NoopMeterProvider,
116+
}
117+
118+
impl ProvideMeter for CustomMeterProvider {
119+
fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter {
120+
// Delegate to noop for simplicity, but this is a distinct type
121+
self.inner.get_meter(scope, attributes)
122+
}
123+
124+
fn as_any(&self) -> &dyn std::any::Any {
125+
self
126+
}
127+
}
128+
129+
let mut context = InterceptorContext::new(Input::doesnt_matter());
130+
context.enter_serialization_phase();
131+
context.set_request(HttpRequest::empty());
132+
let _ = context.take_input();
133+
context.enter_before_transmit_phase();
134+
135+
let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
136+
let mut cfg = ConfigBag::base();
137+
138+
// Set the custom provider
139+
let custom_provider = Arc::new(CustomMeterProvider {
140+
inner: NoopMeterProvider,
141+
});
142+
let telemetry_provider = TelemetryProvider::builder()
143+
.meter_provider(custom_provider)
144+
.build();
145+
let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider);
146+
147+
let interceptor = ObservabilityDetectionInterceptor::new();
148+
let ctx = Into::into(&context);
149+
interceptor
150+
.read_before_signing(&ctx, &rc, &mut cfg)
151+
.unwrap();
152+
153+
// Should NOT track any features for custom provider since it doesn't downcast to OtelMeterProvider
154+
// The new implementation only emits metrics when OTel is detected
155+
let smithy_features: Vec<_> = cfg
156+
.interceptor_state()
157+
.load::<SmithySdkFeature>()
158+
.cloned()
159+
.collect();
160+
assert!(
161+
!smithy_features.iter().any(|f| *f == SmithySdkFeature::ObservabilityMetrics),
162+
"Should not detect custom provider as having observability metrics (only OTel is tracked)"
163+
);
164+
165+
// Verify no AWS-specific features are tracked for custom provider
166+
let aws_features: Vec<_> = cfg
167+
.interceptor_state()
168+
.load::<AwsSdkFeature>()
169+
.cloned()
170+
.collect();
171+
assert_eq!(
172+
aws_features.len(),
173+
0,
174+
"Should not track any AWS-specific features for custom provider"
175+
);
176+
}
113177
}

0 commit comments

Comments
 (0)