1 + | /*
|
2 + | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 + | * SPDX-License-Identifier: Apache-2.0
|
4 + | */
|
5 + |
|
6 + | //! Endpoint override detection for business metrics tracking
|
7 + |
|
8 + | use aws_smithy_runtime_api::box_error::BoxError;
|
9 + | use aws_smithy_runtime_api::client::interceptors::context::BeforeSerializationInterceptorContextRef;
|
10 + | use aws_smithy_runtime_api::client::interceptors::Intercept;
|
11 + | use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
|
12 + | use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer};
|
13 + |
|
14 + | use crate::sdk_feature::AwsSdkFeature;
|
15 + |
|
16 + | /// Interceptor that detects custom endpoint URLs for business metrics
|
17 + | #[derive(Debug, Default)]
|
18 + | #[non_exhaustive]
|
19 + | pub struct EndpointOverrideInterceptor;
|
20 + |
|
21 + | impl EndpointOverrideInterceptor {
|
22 + | /// Creates a new EndpointOverrideInterceptor
|
23 + | pub fn new() -> Self {
|
24 + | Self
|
25 + | }
|
26 + | }
|
27 + |
|
28 + | impl Intercept for EndpointOverrideInterceptor {
|
29 + | fn name(&self) -> &'static str {
|
30 + | "EndpointOverrideInterceptor"
|
31 + | }
|
32 + |
|
33 + | fn read_before_execution(
|
34 + | &self,
|
35 + | _context: &BeforeSerializationInterceptorContextRef<'_>,
|
36 + | cfg: &mut ConfigBag,
|
37 + | ) -> 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 + | {
|
43 + | cfg.interceptor_state()
|
44 + | .store_append(AwsSdkFeature::EndpointOverride);
|
45 + | }
|
46 + | Ok(())
|
47 + | }
|
48 + | }
|
49 + |
|
50 + | /// Runtime plugin that detects when a custom endpoint URL has been configured
|
51 + | /// and tracks it for business metrics.
|
52 + | ///
|
53 + | /// This plugin is created by the codegen decorator when a user explicitly
|
54 + | /// sets an endpoint URL via `.endpoint_url()`. It stores the
|
55 + | /// `AwsSdkFeature::EndpointOverride` feature flag in the ConfigBag for
|
56 + | /// business metrics tracking.
|
57 + | #[derive(Debug)]
|
58 + | #[non_exhaustive]
|
59 + | pub struct EndpointOverrideRuntimePlugin {
|
60 + | config: Option<FrozenLayer>,
|
61 + | }
|
62 + |
|
63 + | impl EndpointOverrideRuntimePlugin {
|
64 + | /// Creates a new `EndpointOverrideRuntimePlugin` with the given config layer
|
65 + | pub fn new(config: Option<FrozenLayer>) -> Self {
|
66 + | Self { config }
|
67 + | }
|
68 + | }
|
69 + |
|
70 + | impl Default for EndpointOverrideRuntimePlugin {
|
71 + | fn default() -> Self {
|
72 + | Self { config: None }
|
73 + | }
|
74 + | }
|
75 + |
|
76 + | impl RuntimePlugin for EndpointOverrideRuntimePlugin {
|
77 + | fn config(&self) -> Option<FrozenLayer> {
|
78 + | self.config.clone()
|
79 + | }
|
80 + | }
|
81 + |
|
82 + | #[cfg(test)]
|
83 + | mod tests {
|
84 + | use super::*;
|
85 + | use crate::sdk_feature::AwsSdkFeature;
|
86 + | use aws_smithy_runtime_api::client::interceptors::context::{
|
87 + | BeforeSerializationInterceptorContextRef, Input, InterceptorContext,
|
88 + | };
|
89 + | use aws_smithy_types::config_bag::ConfigBag;
|
90 + |
|
91 + | #[test]
|
92 + | fn test_plugin_with_no_config() {
|
93 + | let plugin = EndpointOverrideRuntimePlugin::default();
|
94 + | assert!(plugin.config().is_none());
|
95 + | }
|
96 + |
|
97 + | #[test]
|
98 + | fn test_interceptor_detects_endpoint_url_when_present() {
|
99 + | let interceptor = EndpointOverrideInterceptor::new();
|
100 + | let mut cfg = ConfigBag::base();
|
101 + |
|
102 + | // Set endpoint URL in config
|
103 + | let endpoint_url =
|
104 + | aws_types::endpoint_config::EndpointUrl("https://custom.example.com".to_string());
|
105 + | cfg.interceptor_state().store_put(endpoint_url);
|
106 + |
|
107 + | // Create a dummy context
|
108 + | let input = Input::doesnt_matter();
|
109 + | let ctx = InterceptorContext::new(input);
|
110 + | let context = BeforeSerializationInterceptorContextRef::from(&ctx);
|
111 + |
|
112 + | // Run the interceptor
|
113 + | interceptor
|
114 + | .read_before_execution(&context, &mut cfg)
|
115 + | .unwrap();
|
116 + |
|
117 + | // Verify feature flag was set in interceptor_state
|
118 + | let features: Vec<_> = cfg
|
119 + | .interceptor_state()
|
120 + | .load::<AwsSdkFeature>()
|
121 + | .cloned()
|
122 + | .collect();
|
123 + | assert_eq!(features.len(), 1);
|
124 + | assert_eq!(features[0], AwsSdkFeature::EndpointOverride);
|
125 + | }
|
126 + |
|
127 + | #[test]
|
128 + | fn test_interceptor_does_not_set_flag_when_endpoint_url_absent() {
|
129 + | let interceptor = EndpointOverrideInterceptor::new();
|
130 + | let mut cfg = ConfigBag::base();
|
131 + |
|
132 + | // Create a dummy context
|
133 + | let input = Input::doesnt_matter();
|
134 + | let ctx = InterceptorContext::new(input);
|
135 + | let context = BeforeSerializationInterceptorContextRef::from(&ctx);
|
136 + |
|
137 + | // Run the interceptor without setting endpoint URL
|
138 + | interceptor
|
139 + | .read_before_execution(&context, &mut cfg)
|
140 + | .unwrap();
|
141 + |
|
142 + | // Verify no feature flag was set
|
143 + | let features: Vec<_> = cfg
|
144 + | .interceptor_state()
|
145 + | .load::<AwsSdkFeature>()
|
146 + | .cloned()
|
147 + | .collect();
|
148 + | assert_eq!(features.len(), 0);
|
149 + | }
|
150 + | }
|