1use std::borrow::Cow;
7use std::fmt;
8
9use http_1x::header::{HeaderName, HeaderValue, InvalidHeaderValue, USER_AGENT};
10
11use aws_credential_types::credential_feature::AwsCredentialFeature;
12use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature;
13use aws_smithy_runtime_api::box_error::BoxError;
14use aws_smithy_runtime_api::client::http::HttpClient;
15use aws_smithy_runtime_api::client::interceptors::context::{
16 BeforeTransmitInterceptorContextMut, BeforeTransmitInterceptorContextRef,
17};
18use aws_smithy_runtime_api::client::interceptors::{dyn_dispatch_hint, Intercept};
19use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
20use aws_smithy_types::config_bag::ConfigBag;
21use aws_types::app_name::AppName;
22use aws_types::os_shim_internal::Env;
23
24use crate::sdk_feature::AwsSdkFeature;
25use crate::user_agent::metrics::ProvideBusinessMetric;
26use crate::user_agent::{AdditionalMetadata, ApiMetadata, AwsUserAgent, InvalidMetadataValue};
27
28macro_rules! add_metrics_unique {
29 ($features:expr, $ua:expr, $added:expr) => {
30 for feature in $features {
31 if let Some(m) = feature.provide_business_metric() {
32 if !$added.contains(&m) {
33 $added.insert(m.clone());
34 $ua.add_business_metric(m);
35 }
36 }
37 }
38 };
39}
40
41macro_rules! add_metrics_unique_reverse {
42 ($features:expr, $ua:expr, $added:expr) => {
43 let mut unique_metrics = Vec::new();
44 for feature in $features {
45 if let Some(m) = feature.provide_business_metric() {
46 if !$added.contains(&m) {
47 $added.insert(m.clone());
48 unique_metrics.push(m);
49 }
50 }
51 }
52 for m in unique_metrics.into_iter().rev() {
53 $ua.add_business_metric(m);
54 }
55 };
56}
57
58#[allow(clippy::declare_interior_mutable_const)] const X_AMZ_USER_AGENT: HeaderName = HeaderName::from_static("x-amz-user-agent");
60
61#[derive(Debug)]
62enum UserAgentInterceptorError {
63 MissingApiMetadata,
64 InvalidHeaderValue(InvalidHeaderValue),
65 InvalidMetadataValue(InvalidMetadataValue),
66}
67
68impl std::error::Error for UserAgentInterceptorError {
69 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70 match self {
71 Self::InvalidHeaderValue(source) => Some(source),
72 Self::InvalidMetadataValue(source) => Some(source),
73 Self::MissingApiMetadata => None,
74 }
75 }
76}
77
78impl fmt::Display for UserAgentInterceptorError {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 f.write_str(match self {
81 Self::InvalidHeaderValue(_) => "AwsUserAgent generated an invalid HTTP header value. This is a bug. Please file an issue.",
82 Self::InvalidMetadataValue(_) => "AwsUserAgent generated an invalid metadata value. This is a bug. Please file an issue.",
83 Self::MissingApiMetadata => "The UserAgentInterceptor requires ApiMetadata to be set before the request is made. This is a bug. Please file an issue.",
84 })
85 }
86}
87
88impl From<InvalidHeaderValue> for UserAgentInterceptorError {
89 fn from(err: InvalidHeaderValue) -> Self {
90 UserAgentInterceptorError::InvalidHeaderValue(err)
91 }
92}
93
94impl From<InvalidMetadataValue> for UserAgentInterceptorError {
95 fn from(err: InvalidMetadataValue) -> Self {
96 UserAgentInterceptorError::InvalidMetadataValue(err)
97 }
98}
99
100#[non_exhaustive]
102#[derive(Debug, Default)]
103pub struct UserAgentInterceptor;
104
105impl UserAgentInterceptor {
106 pub fn new() -> Self {
108 UserAgentInterceptor
109 }
110}
111
112fn header_values(
113 ua: &AwsUserAgent,
114) -> Result<(HeaderValue, HeaderValue), UserAgentInterceptorError> {
115 Ok((
117 HeaderValue::try_from(ua.ua_header())?,
118 HeaderValue::try_from(ua.aws_ua_header())?,
119 ))
120}
121
122#[dyn_dispatch_hint]
123impl Intercept for UserAgentInterceptor {
124 fn name(&self) -> &'static str {
125 "UserAgentInterceptor"
126 }
127
128 fn read_after_serialization(
129 &self,
130 _context: &BeforeTransmitInterceptorContextRef<'_>,
131 _runtime_components: &RuntimeComponents,
132 cfg: &mut ConfigBag,
133 ) -> Result<(), BoxError> {
134 if cfg.load::<AwsUserAgent>().is_some() {
138 return Ok(());
139 }
140
141 let api_metadata = cfg
142 .load::<ApiMetadata>()
143 .ok_or(UserAgentInterceptorError::MissingApiMetadata)?;
144 let mut ua = AwsUserAgent::new_from_environment(Env::real(), api_metadata.clone());
145
146 let maybe_app_name = cfg.load::<AppName>();
147 if let Some(app_name) = maybe_app_name {
148 ua.set_app_name(app_name.clone());
149 }
150
151 const MAX_FRAMEWORK_METADATA: usize = 10;
160 let appended: Vec<&aws_types::sdk_ua_metadata::FrameworkMetadata> = cfg
161 .load::<aws_types::sdk_ua_metadata::FrameworkMetadata>()
162 .collect();
163 let mut seen = std::collections::HashSet::new();
164 let mut kept = 0usize;
165 for md in appended.into_iter().rev() {
166 let key = (md.name().to_owned(), md.version().map(|v| v.to_owned()));
167 if !seen.insert(key) {
168 continue;
170 }
171 if kept >= MAX_FRAMEWORK_METADATA {
172 tracing::warn!(
173 "More than {MAX_FRAMEWORK_METADATA} unique framework metadata entries \
174 were configured; only the first {MAX_FRAMEWORK_METADATA} will be included \
175 in the user agent."
176 );
177 break;
178 }
179 ua.add_framework_metadata(md.clone());
181 kept += 1;
182 }
183
184 cfg.interceptor_state().store_put(ua);
185
186 Ok(())
187 }
188
189 fn modify_before_signing(
190 &self,
191 context: &mut BeforeTransmitInterceptorContextMut<'_>,
192 runtime_components: &RuntimeComponents,
193 cfg: &mut ConfigBag,
194 ) -> Result<(), BoxError> {
195 let mut ua = cfg
196 .load::<AwsUserAgent>()
197 .expect("`AwsUserAgent should have been created in `read_before_execution`")
198 .clone();
199
200 let mut added_metrics = std::collections::HashSet::new();
201
202 add_metrics_unique!(cfg.load::<SmithySdkFeature>(), &mut ua, &mut added_metrics);
203 add_metrics_unique!(cfg.load::<AwsSdkFeature>(), &mut ua, &mut added_metrics);
204 add_metrics_unique_reverse!(
207 cfg.load::<AwsCredentialFeature>(),
208 &mut ua,
209 &mut added_metrics
210 );
211
212 let maybe_connector_metadata = runtime_components
213 .http_client()
214 .and_then(|c| c.connector_metadata());
215 if let Some(connector_metadata) = maybe_connector_metadata {
216 let am = AdditionalMetadata::new(Cow::Owned(connector_metadata.to_string()))?;
217 ua.add_additional_metadata(am);
218 }
219
220 let headers = context.request_mut().headers_mut();
221 let (user_agent, x_amz_user_agent) = header_values(&ua)?;
222 headers.append(USER_AGENT, user_agent);
223 headers.append(X_AMZ_USER_AGENT, x_amz_user_agent);
224 Ok(())
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use aws_smithy_runtime_api::client::interceptors::context::{Input, InterceptorContext};
232 use aws_smithy_runtime_api::client::interceptors::Intercept;
233 use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
234 use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
235 use aws_smithy_types::config_bag::{ConfigBag, Layer};
236 use aws_smithy_types::error::display::DisplayErrorContext;
237
238 fn expect_header<'a>(context: &'a InterceptorContext, header_name: &str) -> &'a str {
239 context
240 .request()
241 .expect("request is set")
242 .headers()
243 .get(header_name)
244 .unwrap()
245 }
246
247 fn context() -> InterceptorContext {
248 let mut context = InterceptorContext::new(Input::doesnt_matter());
249 context.enter_serialization_phase();
250 context.set_request(HttpRequest::empty());
251 let _ = context.take_input();
252 context.enter_before_transmit_phase();
253 context
254 }
255
256 #[test]
257 fn test_overridden_ua() {
258 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
259 let mut context = context();
260
261 let mut layer = Layer::new("test");
262 layer.store_put(AwsUserAgent::for_tests());
263 layer.store_put(ApiMetadata::new("unused", "unused"));
264 let mut cfg = ConfigBag::of_layers(vec![layer]);
265
266 let interceptor = UserAgentInterceptor::new();
267 let mut ctx = Into::into(&mut context);
268 interceptor
269 .modify_before_signing(&mut ctx, &rc, &mut cfg)
270 .unwrap();
271
272 let ua_header = expect_header(&context, "user-agent");
273 assert_eq!(AwsUserAgent::for_tests().ua_header(), ua_header);
274 assert!(!ua_header.contains("unused"));
275
276 let aws_ua_header = expect_header(&context, "x-amz-user-agent");
277 assert_eq!(AwsUserAgent::for_tests().ua_header(), aws_ua_header);
278 assert!(!aws_ua_header.contains("unused"));
279 }
280
281 #[test]
282 fn test_default_ua() {
283 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
284 let mut context = context();
285
286 let api_metadata = ApiMetadata::new("some-service", "some-version");
287 let mut layer = Layer::new("test");
288 layer.store_put(api_metadata.clone());
289 let mut config = ConfigBag::of_layers(vec![layer]);
290
291 let interceptor = UserAgentInterceptor::new();
292 let ctx = Into::into(&context);
293 interceptor
294 .read_after_serialization(&ctx, &rc, &mut config)
295 .unwrap();
296 let mut ctx = Into::into(&mut context);
297 interceptor
298 .modify_before_signing(&mut ctx, &rc, &mut config)
299 .unwrap();
300
301 let expected_ua = AwsUserAgent::new_from_environment(Env::real(), api_metadata);
302 assert!(
303 expected_ua.aws_ua_header().contains("some-service"),
304 "precondition"
305 );
306 assert_eq!(
307 expected_ua.ua_header(),
308 expect_header(&context, "user-agent")
309 );
310 assert_eq!(
311 expected_ua.aws_ua_header(),
312 expect_header(&context, "x-amz-user-agent")
313 );
314 }
315
316 #[test]
317 fn test_modify_before_signing_no_duplicate_metrics() {
318 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
319 let mut context = context();
320
321 let api_metadata = ApiMetadata::new("test-service", "1.0");
322 let mut layer = Layer::new("test");
323 layer.store_put(api_metadata);
324 layer.store_append(SmithySdkFeature::Waiter);
326 layer.store_append(SmithySdkFeature::Waiter);
327 layer.store_append(AwsSdkFeature::S3Transfer);
328 layer.store_append(AwsSdkFeature::S3Transfer);
329 layer.store_append(AwsCredentialFeature::CredentialsCode);
330 layer.store_append(AwsCredentialFeature::CredentialsCode);
331 let mut config = ConfigBag::of_layers(vec![layer]);
332
333 let interceptor = UserAgentInterceptor::new();
334 let ctx = Into::into(&context);
335 interceptor
336 .read_after_serialization(&ctx, &rc, &mut config)
337 .unwrap();
338 let mut ctx = Into::into(&mut context);
339 interceptor
340 .modify_before_signing(&mut ctx, &rc, &mut config)
341 .unwrap();
342
343 let ua_header = expect_header(&context, "x-amz-user-agent");
344 let aws_ua_header = expect_header(&context, "x-amz-user-agent");
345 let ua_metrics_section = ua_header.split(" m/").nth(1).unwrap();
346 let aws_ua_metrics_section = aws_ua_header.split(" m/").nth(1).unwrap();
347 assert_eq!(ua_metrics_section, aws_ua_metrics_section);
348
349 let waiter_count = ua_metrics_section.matches("B").count();
350 let s3_transfer_count = ua_metrics_section.matches("G").count();
351 let credentials_code_count = ua_metrics_section.matches("e").count();
352 assert_eq!(
353 1, waiter_count,
354 "Waiter metric should appear only once, but found {waiter_count} occurrences in: {aws_ua_header}",
355 );
356 assert_eq!(1, s3_transfer_count, "S3Transfer metric should appear only once, but found {s3_transfer_count} occurrences in metrics section: {aws_ua_header}");
357 assert_eq!(1, credentials_code_count, "CredentialsCode metric should appear only once, but found {credentials_code_count} occurrences in metrics section: {aws_ua_header}");
358 }
359
360 #[test]
361 fn test_s3_transfer_manager_features_reach_the_header() {
362 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
363 let mut context = context();
364
365 let mut layer = Layer::new("test");
366 layer.store_put(ApiMetadata::new("test-service", "1.0"));
367 for feature in [
368 AwsSdkFeature::S3TransferUploadDirectory,
369 AwsSdkFeature::S3TransferDownloadDirectory,
370 AwsSdkFeature::S3CustomPartSize,
371 AwsSdkFeature::S3CustomThroughput,
372 AwsSdkFeature::S3CustomMemoryLimit,
373 AwsSdkFeature::S3OnEc2,
374 AwsSdkFeature::S3FilePath,
375 ] {
376 layer.store_append(feature);
377 }
378 let mut config = ConfigBag::of_layers(vec![layer]);
379
380 let interceptor = UserAgentInterceptor::new();
381 let ctx = Into::into(&context);
382 interceptor
383 .read_after_serialization(&ctx, &rc, &mut config)
384 .unwrap();
385 let mut ctx = Into::into(&mut context);
386 interceptor
387 .modify_before_signing(&mut ctx, &rc, &mut config)
388 .unwrap();
389
390 let header = expect_header(&context, "x-amz-user-agent");
391 let metrics = header.split(" m/").nth(1).unwrap();
392 let mut ids: Vec<_> = metrics.split(',').collect();
393 ids.sort_unstable();
394 assert_eq!(vec!["+", "9", "AX", "AY", "AZ", "Aa", "Ab"], ids);
395 }
396
397 #[test]
398 fn test_metrics_order_preserved() {
399 use aws_credential_types::credential_feature::AwsCredentialFeature;
400
401 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
402 let mut context = context();
403
404 let api_metadata = ApiMetadata::new("test-service", "1.0");
405 let mut layer = Layer::new("test");
406 layer.store_put(api_metadata);
407 layer.store_append(AwsCredentialFeature::CredentialsCode);
408 layer.store_append(AwsCredentialFeature::CredentialsEnvVars);
409 layer.store_append(AwsCredentialFeature::CredentialsProfile);
410 let mut config = ConfigBag::of_layers(vec![layer]);
411
412 let interceptor = UserAgentInterceptor::new();
413 let ctx = Into::into(&context);
414 interceptor
415 .read_after_serialization(&ctx, &rc, &mut config)
416 .unwrap();
417 let mut ctx = Into::into(&mut context);
418 interceptor
419 .modify_before_signing(&mut ctx, &rc, &mut config)
420 .unwrap();
421
422 let ua_header = expect_header(&context, "user-agent");
423 let ua_metrics_section = ua_header.split(" m/").nth(1).unwrap();
424 assert_eq!(
425 ua_metrics_section, "e,g,n",
426 "AwsCredentialFeature metrics should preserve order"
427 );
428
429 let aws_ua_header = expect_header(&context, "x-amz-user-agent");
430 let aws_ua_metrics_section = aws_ua_header.split(" m/").nth(1).unwrap();
431 assert_eq!(
432 aws_ua_metrics_section, "e,g,n",
433 "AwsCredentialFeature metrics should preserve order"
434 );
435 }
436
437 #[test]
438 fn test_app_name() {
439 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
440 let mut context = context();
441
442 let api_metadata = ApiMetadata::new("some-service", "some-version");
443 let mut layer = Layer::new("test");
444 layer.store_put(api_metadata);
445 layer.store_put(AppName::new("my_awesome_app").unwrap());
446 let mut config = ConfigBag::of_layers(vec![layer]);
447
448 let interceptor = UserAgentInterceptor::new();
449 let ctx = Into::into(&context);
450 interceptor
451 .read_after_serialization(&ctx, &rc, &mut config)
452 .unwrap();
453 let mut ctx = Into::into(&mut context);
454 interceptor
455 .modify_before_signing(&mut ctx, &rc, &mut config)
456 .unwrap();
457
458 let app_value = "app/my_awesome_app";
459 let ua_header = expect_header(&context, "user-agent");
460 assert!(
461 ua_header.contains(app_value),
462 "expected `{ua_header}` to contain `{app_value}`"
463 );
464
465 let aws_ua_header = expect_header(&context, "x-amz-user-agent");
466 assert!(
467 aws_ua_header.contains(app_value),
468 "expected `{aws_ua_header}` to contain `{app_value}`"
469 );
470 }
471
472 fn run_interceptor_with_framework_metadata(
473 entries: Vec<aws_types::sdk_ua_metadata::FrameworkMetadata>,
474 ) -> String {
475 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
476 let mut context = context();
477
478 let api_metadata = ApiMetadata::new("some-service", "some-version");
479 let mut layer = Layer::new("test");
480 layer.store_put(api_metadata);
481 for entry in entries {
482 layer.store_append(entry);
483 }
484 let mut config = ConfigBag::of_layers(vec![layer]);
485
486 let interceptor = UserAgentInterceptor::new();
487 let ctx = Into::into(&context);
488 interceptor
489 .read_after_serialization(&ctx, &rc, &mut config)
490 .unwrap();
491 let mut ctx = Into::into(&mut context);
492 interceptor
493 .modify_before_signing(&mut ctx, &rc, &mut config)
494 .unwrap();
495
496 expect_header(&context, "x-amz-user-agent").to_string()
497 }
498
499 #[test]
500 fn test_framework_metadata() {
501 use aws_types::sdk_ua_metadata::FrameworkMetadata;
502
503 let header = run_interceptor_with_framework_metadata(vec![
504 FrameworkMetadata::new("framework-one", Some("1.0")).unwrap(),
505 FrameworkMetadata::new("framework-two", Some("2.0")).unwrap(),
506 ]);
507
508 assert!(
509 header.contains("lib/framework-one/1.0"),
510 "expected `{header}` to contain `lib/framework-one/1.0`"
511 );
512 assert!(
513 header.contains("lib/framework-two/2.0"),
514 "expected `{header}` to contain `lib/framework-two/2.0`"
515 );
516 let pos_one = header.find("lib/framework-one/1.0").unwrap();
518 let pos_two = header.find("lib/framework-two/2.0").unwrap();
519 assert!(
520 pos_one < pos_two,
521 "expected framework-one before framework-two in `{header}`"
522 );
523 }
524
525 #[test]
526 fn test_framework_metadata_dedup() {
527 use aws_types::sdk_ua_metadata::FrameworkMetadata;
528
529 let header = run_interceptor_with_framework_metadata(vec![
530 FrameworkMetadata::new("dup-framework", Some("1.0")).unwrap(),
531 FrameworkMetadata::new("dup-framework", Some("1.0")).unwrap(),
532 ]);
533
534 assert_eq!(
535 1,
536 header.matches("lib/dup-framework/1.0").count(),
537 "expected `lib/dup-framework/1.0` to appear exactly once in `{header}`"
538 );
539 }
540
541 #[test]
542 fn test_framework_metadata_cap() {
543 use aws_types::sdk_ua_metadata::FrameworkMetadata;
544
545 let entries = (0..15)
546 .map(|i| FrameworkMetadata::new(format!("framework-{i}"), Some("1.0")).unwrap())
547 .collect();
548 let header = run_interceptor_with_framework_metadata(entries);
549
550 let count = header.matches("lib/framework-").count();
551 assert_eq!(
552 10, count,
553 "expected at most 10 framework metadata entries, found {count} in `{header}`"
554 );
555 }
556
557 #[test]
558 fn test_framework_metadata_cap_boundary() {
559 use aws_types::sdk_ua_metadata::FrameworkMetadata;
560
561 let ten = (0..10)
563 .map(|i| FrameworkMetadata::new(format!("fw-{i}"), Some("1.0")).unwrap())
564 .collect();
565 let header = run_interceptor_with_framework_metadata(ten);
566 for i in 0..10 {
567 assert!(
568 header.contains(&format!("lib/fw-{i}/1.0")),
569 "expected `lib/fw-{i}/1.0` in `{header}`"
570 );
571 }
572
573 let eleven = (0..11)
575 .map(|i| FrameworkMetadata::new(format!("gw-{i}"), Some("1.0")).unwrap())
576 .collect();
577 let header = run_interceptor_with_framework_metadata(eleven);
578 assert_eq!(
579 10,
580 header.matches("lib/gw-").count(),
581 "expected exactly 10 entries in `{header}`"
582 );
583 assert!(
584 !header.contains("lib/gw-10/1.0"),
585 "the 11th entry should be dropped in `{header}`"
586 );
587 }
588
589 #[test]
590 fn test_framework_metadata_dedup_preserves_first_occurrence_order() {
591 use aws_types::sdk_ua_metadata::FrameworkMetadata;
592
593 let header = run_interceptor_with_framework_metadata(vec![
595 FrameworkMetadata::new("a", Some("1.0")).unwrap(),
596 FrameworkMetadata::new("b", Some("1.0")).unwrap(),
597 FrameworkMetadata::new("a", Some("1.0")).unwrap(), FrameworkMetadata::new("c", Some("1.0")).unwrap(),
599 ]);
600
601 let pa = header.find("lib/a/1.0").expect("a present");
602 let pb = header.find("lib/b/1.0").expect("b present");
603 let pc = header.find("lib/c/1.0").expect("c present");
604 assert!(
605 pa < pb && pb < pc,
606 "expected first-seen order a, b, c in `{header}`"
607 );
608 assert_eq!(
609 1,
610 header.matches("lib/a/1.0").count(),
611 "duplicate entry should appear once in `{header}`"
612 );
613 }
614
615 #[test]
616 fn test_framework_metadata_no_version() {
617 use aws_types::sdk_ua_metadata::FrameworkMetadata;
618
619 let header = run_interceptor_with_framework_metadata(vec![FrameworkMetadata::new(
620 "noversion-framework",
621 None::<&str>,
622 )
623 .unwrap()]);
624
625 assert!(
626 header.contains("lib/noversion-framework"),
627 "expected `lib/noversion-framework` in `{header}`"
628 );
629 assert!(
630 !header.contains("lib/noversion-framework/"),
631 "expected no trailing slash for a versionless entry in `{header}`"
632 );
633 }
634
635 #[test]
636 fn test_framework_metadata_across_layers_preserves_first_seen_order() {
637 use aws_types::sdk_ua_metadata::FrameworkMetadata;
638
639 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
640 let mut context = context();
641
642 let mut base_layer = Layer::new("from_sdk_config");
646 base_layer.store_put(ApiMetadata::new("some-service", "some-version"));
647 base_layer.store_append(FrameworkMetadata::new("from-sdk-config", Some("1.0")).unwrap());
648
649 let mut client_layer = Layer::new("from_client");
650 client_layer.store_append(FrameworkMetadata::new("from-client", Some("2.0")).unwrap());
651
652 let mut config = ConfigBag::of_layers(vec![base_layer, client_layer]);
653
654 let interceptor = UserAgentInterceptor::new();
655 let ctx = Into::into(&context);
656 interceptor
657 .read_after_serialization(&ctx, &rc, &mut config)
658 .unwrap();
659 let mut ctx = Into::into(&mut context);
660 interceptor
661 .modify_before_signing(&mut ctx, &rc, &mut config)
662 .unwrap();
663 let header = expect_header(&context, "x-amz-user-agent");
664
665 let pos_sdk = header
666 .find("lib/from-sdk-config/1.0")
667 .expect("sdk-config entry present");
668 let pos_client = header
669 .find("lib/from-client/2.0")
670 .expect("client entry present");
671 assert!(
672 pos_sdk < pos_client,
673 "expected the SdkConfig entry before the client entry in `{header}`"
674 );
675 }
676
677 #[test]
678 fn test_api_metadata_missing() {
679 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
680 let context = context();
681 let mut config = ConfigBag::base();
682
683 let interceptor = UserAgentInterceptor::new();
684 let ctx = Into::into(&context);
685
686 let error = format!(
687 "{}",
688 DisplayErrorContext(
689 &*interceptor
690 .read_after_serialization(&ctx, &rc, &mut config)
691 .expect_err("it should error")
692 )
693 );
694 assert!(
695 error.contains("This is a bug"),
696 "`{error}` should contain message `This is a bug`"
697 );
698 }
699
700 #[test]
701 fn test_api_metadata_missing_with_ua_override() {
702 let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
703 let mut context = context();
704
705 let mut layer = Layer::new("test");
706 layer.store_put(AwsUserAgent::for_tests());
707 let mut config = ConfigBag::of_layers(vec![layer]);
708
709 let interceptor = UserAgentInterceptor::new();
710 let mut ctx = Into::into(&mut context);
711
712 interceptor
713 .modify_before_signing(&mut ctx, &rc, &mut config)
714 .expect("it should succeed");
715
716 let ua_header = expect_header(&context, "user-agent");
717 assert_eq!(AwsUserAgent::for_tests().ua_header(), ua_header);
718 assert!(!ua_header.contains("unused"));
719
720 let aws_ua_header = expect_header(&context, "x-amz-user-agent");
721 assert_eq!(AwsUserAgent::for_tests().ua_header(), aws_ua_header);
722 assert!(!aws_ua_header.contains("unused"));
723 }
724}