diff --git a/.changelog/1722623288.md b/.changelog/1722623288.md new file mode 100644 index 0000000000000000000000000000000000000000..267cc211fe27c0db094ab0f9cce1ee62786fa3a4 --- /dev/null +++ b/.changelog/1722623288.md @@ -0,0 +1,12 @@ +--- +applies_to: +- aws-sdk-rust +authors: +- ysaito1001 +references: +- smithy-rs#3781 +breaking: false +new_feature: false +bug_fix: false +--- +Allow [AwsUserAgent](https://docs.rs/aws-runtime/1.3.1/aws_runtime/user_agent/struct.AwsUserAgent.html) to incorporate business metrics, which now deprecates the existing feature and config metadata. diff --git a/aws/rust-runtime/Cargo.lock b/aws/rust-runtime/Cargo.lock index 0891972110f8c7783ed62b97d39e6d8955b53fa8..10855213638745e11224ac3cae51ee65a585d289 100644 --- a/aws/rust-runtime/Cargo.lock +++ b/aws/rust-runtime/Cargo.lock @@ -108,7 +108,7 @@ version = "0.60.3" [[package]] name = "aws-http" -version = "0.60.5" +version = "0.60.6" dependencies = [ "aws-runtime", ] @@ -149,7 +149,7 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.3.1" +version = "1.4.0" dependencies = [ "arbitrary", "aws-credential-types", @@ -163,12 +163,14 @@ dependencies = [ "aws-types", "bytes", "bytes-utils", + "convert_case", "fastrand", "futures-util", "http 0.2.12", "http 1.1.0", "http-body 0.4.6", "http-body 1.0.0", + "once_cell", "percent-encoding", "pin-project-lite", "proptest", @@ -282,21 +284,24 @@ dependencies = [ [[package]] name = "aws-smithy-protocol-test" -version = "0.60.7" +version = "0.62.0" dependencies = [ "assert-json-diff", "aws-smithy-runtime-api", + "base64-simd", + "cbor-diag", "http 0.2.12", "pretty_assertions", "regex-lite", "roxmltree", + "serde_cbor", "serde_json", "thiserror", ] [[package]] name = "aws-smithy-runtime" -version = "1.6.1" +version = "1.6.2" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -449,6 +454,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -477,6 +491,25 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbor-diag" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc245b6ecd09b23901a4fbad1ad975701fd5061ceaef6afa93a2d70605a64429" +dependencies = [ + "bs58", + "chrono", + "data-encoding", + "half 2.4.1", + "nom", + "num-bigint", + "num-rational", + "num-traits", + "separator", + "url", + "uuid", +] + [[package]] name = "cc" version = "1.0.99" @@ -494,6 +527,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -518,7 +560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half", + "half 2.4.1", ] [[package]] @@ -552,6 +594,15 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -694,6 +745,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "der" version = "0.6.1" @@ -923,6 +980,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "half" version = "2.4.1" @@ -1079,6 +1142,16 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -1218,6 +1291,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1238,6 +1317,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1248,6 +1337,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1263,6 +1362,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1821,6 +1931,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +[[package]] +name = "separator" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" + [[package]] name = "serde" version = "1.0.203" @@ -1830,6 +1946,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.203" @@ -2053,6 +2179,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.38.0" @@ -2212,18 +2353,50 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.8.0" diff --git a/aws/rust-runtime/aws-http/Cargo.toml b/aws/rust-runtime/aws-http/Cargo.toml index e01593ab5c30f7a592f43942743055a3939938c7..8395a66d40d1071bfe4a19fff1e73c75f747e9c7 100644 --- a/aws/rust-runtime/aws-http/Cargo.toml +++ b/aws/rust-runtime/aws-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-http" -version = "0.60.5" +version = "0.60.6" authors = ["AWS Rust SDK Team ", "Russell Cohen "] description = "This crate is no longer used by the AWS SDK and is deprecated." edition = "2021" diff --git a/aws/rust-runtime/aws-http/src/user_agent.rs b/aws/rust-runtime/aws-http/src/user_agent.rs index f68ecf91bee6bf1c2db4aade587f4e10ad233a50..e53531c9d4fc87a235d78ff3dd911592464f40ff 100644 --- a/aws/rust-runtime/aws-http/src/user_agent.rs +++ b/aws/rust-runtime/aws-http/src/user_agent.rs @@ -31,18 +31,20 @@ pub type InvalidMetadataValue = aws_runtime::user_agent::InvalidMetadataValue; )] pub type AdditionalMetadata = aws_runtime::user_agent::AdditionalMetadata; -/// Use aws_runtime::user_agent::FeatureMetadata instead. +/// Use aws_runtime::user_agent::BusinessMetric instead. #[deprecated( since = "0.60.2", - note = "Use aws_runtime::user_agent::FeatureMetadata instead." + note = "Use aws_runtime::user_agent::BusinessMetric instead." )] +#[allow(deprecated)] pub type FeatureMetadata = aws_runtime::user_agent::FeatureMetadata; -/// Use aws_runtime::user_agent::ConfigMetadata instead. +/// Use aws_runtime::user_agent::BusinessMetric instead. #[deprecated( since = "0.60.2", - note = "Use aws_runtime::user_agent::ConfigMetadata instead." + note = "Use aws_runtime::user_agent::BusinessMetric instead." )] +#[allow(deprecated)] pub type ConfigMetadata = aws_runtime::user_agent::ConfigMetadata; /// Use aws_runtime::user_agent::FrameworkMetadata instead. diff --git a/aws/rust-runtime/aws-runtime/Cargo.toml b/aws/rust-runtime/aws-runtime/Cargo.toml index 79ebb292ae8a8ef0d2a1b76637390eda72df69e1..4886198937335f1a677b1971a468fd9d63ecb49f 100644 --- a/aws/rust-runtime/aws-runtime/Cargo.toml +++ b/aws/rust-runtime/aws-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-runtime" -version = "1.3.1" +version = "1.4.0" authors = ["AWS Rust SDK Team "] description = "Runtime support code for the AWS SDK. This crate isn't intended to be used directly." edition = "2021" @@ -30,6 +30,7 @@ http-02x = { package = "http", version = "0.2.3" } http-body-04x = { package = "http-body", version = "0.4.5" } http-1x = { package = "http", version = "1.1.0", optional = true } http-body-1x = { package = "http-body", version = "1.0.0", optional = true } +once_cell = "1.18.0" percent-encoding = "2.1.0" pin-project-lite = "0.2.9" tracing = "0.1" @@ -43,6 +44,7 @@ aws-smithy-protocol-test = { path = "../../../rust-runtime/aws-smithy-protocol-t aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["test-util"] } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types", features = ["test-util"] } bytes-utils = "0.1.2" +convert_case = "0.6.0" futures-util = { version = "0.3.29", default-features = false } proptest = "1.2" serde = { version = "1", features = ["derive"]} diff --git a/aws/rust-runtime/aws-runtime/src/user_agent.rs b/aws/rust-runtime/aws-runtime/src/user_agent.rs index b00332ddb5b48f5d4a185f6bb832247697726aa8..b7aeeb8f8f7a5e356301d795b5bd7c7837ef0bcf 100644 --- a/aws/rust-runtime/aws-runtime/src/user_agent.rs +++ b/aws/rust-runtime/aws-runtime/src/user_agent.rs @@ -12,8 +12,11 @@ use std::error::Error; use std::fmt; mod interceptor; +mod metrics; +use crate::user_agent::metrics::BusinessMetrics; pub use interceptor::UserAgentInterceptor; +pub use metrics::BusinessMetric; /// AWS User Agent /// @@ -27,8 +30,7 @@ pub struct AwsUserAgent { os_metadata: OsMetadata, language_metadata: LanguageMetadata, exec_env_metadata: Option, - feature_metadata: Vec, - config_metadata: Vec, + business_metrics: BusinessMetrics, framework_metadata: Vec, app_name: Option, build_env_additional_metadata: Option, @@ -70,9 +72,8 @@ impl AwsUserAgent { extras: Default::default(), }, exec_env_metadata, - feature_metadata: Default::default(), - config_metadata: Default::default(), framework_metadata: Default::default(), + business_metrics: Default::default(), app_name: Default::default(), build_env_additional_metadata, additional_metadata: Default::default(), @@ -102,8 +103,7 @@ impl AwsUserAgent { extras: Default::default(), }, exec_env_metadata: None, - feature_metadata: Vec::new(), - config_metadata: Vec::new(), + business_metrics: Default::default(), framework_metadata: Vec::new(), app_name: None, build_env_additional_metadata: None, @@ -111,31 +111,65 @@ impl AwsUserAgent { } } + #[deprecated( + since = "1.4.0", + note = "This is a no-op; use `with_business_metric` instead." + )] + #[allow(unused_mut)] + #[allow(deprecated)] #[doc(hidden)] /// Adds feature metadata to the user agent. - pub fn with_feature_metadata(mut self, metadata: FeatureMetadata) -> Self { - self.feature_metadata.push(metadata); + pub fn with_feature_metadata(mut self, _metadata: FeatureMetadata) -> Self { self } + #[deprecated( + since = "1.4.0", + note = "This is a no-op; use `add_business_metric` instead." + )] + #[allow(deprecated)] + #[allow(unused_mut)] #[doc(hidden)] /// Adds feature metadata to the user agent. - pub fn add_feature_metadata(&mut self, metadata: FeatureMetadata) -> &mut Self { - self.feature_metadata.push(metadata); + pub fn add_feature_metadata(&mut self, _metadata: FeatureMetadata) -> &mut Self { self } + #[deprecated( + since = "1.4.0", + note = "This is a no-op; use `with_business_metric` instead." + )] + #[allow(deprecated)] + #[allow(unused_mut)] #[doc(hidden)] /// Adds config metadata to the user agent. - pub fn with_config_metadata(mut self, metadata: ConfigMetadata) -> Self { - self.config_metadata.push(metadata); + pub fn with_config_metadata(mut self, _metadata: ConfigMetadata) -> Self { self } + #[deprecated( + since = "1.4.0", + note = "This is a no-op; use `add_business_metric` instead." + )] + #[allow(deprecated)] + #[allow(unused_mut)] #[doc(hidden)] /// Adds config metadata to the user agent. - pub fn add_config_metadata(&mut self, metadata: ConfigMetadata) -> &mut Self { - self.config_metadata.push(metadata); + pub fn add_config_metadata(&mut self, _metadata: ConfigMetadata) -> &mut Self { + self + } + + #[doc(hidden)] + /// Adds business metric to the user agent. + pub fn with_business_metric(mut self, metric: BusinessMetric) -> Self { + self.business_metrics.push(metric); + self + } + + #[doc(hidden)] + /// Adds business metric to the user agent. + pub fn add_business_metric(&mut self, metric: BusinessMetric) -> &mut Self { + self.business_metrics.push(metric); self } @@ -188,10 +222,10 @@ impl AwsUserAgent { os-metadata RWS language-metadata RWS [env-metadata RWS] - *(feat-metadata RWS) - *(config-metadata RWS) - *(framework-metadata RWS) + ; ordering is not strictly required in the following section + [business-metrics] [appId] + *(framework-metadata RWS) */ let mut ua_value = String::new(); use std::fmt::Write; @@ -203,11 +237,8 @@ impl AwsUserAgent { if let Some(ref env_meta) = self.exec_env_metadata { write!(ua_value, "{} ", env_meta).unwrap(); } - for feature in &self.feature_metadata { - write!(ua_value, "{} ", feature).unwrap(); - } - for config in &self.config_metadata { - write!(ua_value, "{} ", config).unwrap(); + if !self.business_metrics.is_empty() { + write!(ua_value, "{} ", &self.business_metrics).unwrap() } for framework in &self.framework_metadata { write!(ua_value, "{} ", framework).unwrap(); @@ -370,6 +401,7 @@ impl fmt::Display for AdditionalMetadataList { } } +#[deprecated(since = "1.4.0", note = "Replaced by `BusinessMetric`.")] #[doc(hidden)] /// Metadata about a feature that is being used in the SDK. #[derive(Clone, Debug)] @@ -380,6 +412,7 @@ pub struct FeatureMetadata { additional: AdditionalMetadataList, } +#[allow(deprecated)] impl FeatureMetadata { /// Creates `FeatureMetadata`. /// @@ -406,6 +439,7 @@ impl FeatureMetadata { } } +#[allow(deprecated)] impl fmt::Display for FeatureMetadata { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // feat-metadata = "ft/" name ["/" version] *(RWS additional-metadata) @@ -417,6 +451,7 @@ impl fmt::Display for FeatureMetadata { } } +#[deprecated(since = "1.4.0", note = "Replaced by `BusinessMetric`.")] #[doc(hidden)] /// Metadata about a config value that is being used in the SDK. #[derive(Clone, Debug)] @@ -426,6 +461,7 @@ pub struct ConfigMetadata { value: Option>, } +#[allow(deprecated)] impl ConfigMetadata { /// Creates `ConfigMetadata`. /// @@ -445,6 +481,7 @@ impl ConfigMetadata { } } +#[allow(deprecated)] impl fmt::Display for ConfigMetadata { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // config-metadata = "cfg/" config ["/" value] @@ -605,54 +642,6 @@ mod test { ); } - #[test] - fn generate_a_valid_ua_with_features() { - let api_metadata = ApiMetadata { - service_id: "dynamodb".into(), - version: "123", - }; - let mut ua = AwsUserAgent::new_from_environment(Env::from_slice(&[]), api_metadata) - .with_feature_metadata( - FeatureMetadata::new("test-feature", Some(Cow::Borrowed("1.0"))).unwrap(), - ) - .with_feature_metadata( - FeatureMetadata::new("other-feature", None) - .unwrap() - .with_additional(AdditionalMetadata::new("asdf").unwrap()), - ); - make_deterministic(&mut ua); - assert_eq!( - ua.aws_ua_header(), - "aws-sdk-rust/0.1 api/dynamodb/123 os/macos/1.15 lang/rust/1.50.0 ft/test-feature/1.0 ft/other-feature md/asdf" - ); - assert_eq!( - ua.ua_header(), - "aws-sdk-rust/0.1 os/macos/1.15 lang/rust/1.50.0" - ); - } - - #[test] - fn generate_a_valid_ua_with_config() { - let api_metadata = ApiMetadata { - service_id: "dynamodb".into(), - version: "123", - }; - let mut ua = AwsUserAgent::new_from_environment(Env::from_slice(&[]), api_metadata) - .with_config_metadata( - ConfigMetadata::new("some-config", Some(Cow::Borrowed("5"))).unwrap(), - ) - .with_config_metadata(ConfigMetadata::new("other-config", None).unwrap()); - make_deterministic(&mut ua); - assert_eq!( - ua.aws_ua_header(), - "aws-sdk-rust/0.1 api/dynamodb/123 os/macos/1.15 lang/rust/1.50.0 cfg/some-config/5 cfg/other-config" - ); - assert_eq!( - ua.ua_header(), - "aws-sdk-rust/0.1 os/macos/1.15 lang/rust/1.50.0" - ); - } - #[test] fn generate_a_valid_ua_with_frameworks() { let api_metadata = ApiMetadata { @@ -709,6 +698,37 @@ mod test { "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" ); } + + #[test] + fn generate_a_valid_ua_with_business_metrics() { + // single metric ID + { + let ua = AwsUserAgent::for_tests().with_business_metric(BusinessMetric::ResourceModel); + assert_eq!( + ua.aws_ua_header(), + "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0 m/A" + ); + assert_eq!( + ua.ua_header(), + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ); + } + // multiple metric IDs + { + let ua = AwsUserAgent::for_tests() + .with_business_metric(BusinessMetric::RetryModeAdaptive) + .with_business_metric(BusinessMetric::S3Transfer) + .with_business_metric(BusinessMetric::S3ExpressBucket); + assert_eq!( + ua.aws_ua_header(), + "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0 m/F,G,J" + ); + assert_eq!( + ua.ua_header(), + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ); + } + } } /* @@ -729,21 +749,25 @@ api-metadata = "api/" service-id "/" version os-metadata = "os/" os-family ["/" version] language-metadata = "lang/" language "/" version *(RWS additional-metadata) env-metadata = "exec-env/" name -feat-metadata = "ft/" name ["/" version] *(RWS additional-metadata) -config-metadata = "cfg/" config ["/" value] framework-metadata = "lib/" name ["/" version] *(RWS additional-metadata) app-id = "app/" name build-env-additional-metadata = "md/" value +ua-metadata = "ua/2.1" +business-metrics = "m/" metric_id *(comma metric_id) +metric_id = 1*m_char +m_char = DIGIT / ALPHA / "+" / "-" +comma = "," ua-string = sdk-metadata RWS + ua-metadata RWS [api-metadata RWS] os-metadata RWS language-metadata RWS [env-metadata RWS] - *(feat-metadata RWS) - *(config-metadata RWS) - *(framework-metadata RWS) + ; ordering is not strictly required in the following section + [business-metrics] [app-id] [build-env-additional-metadata] + *(framework-metadata RWS) # New metadata field might be added in the future and they must follow this format prefix = token diff --git a/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs new file mode 100644 index 0000000000000000000000000000000000000000..2ecce131f9cc683bfcdac04d18981210a0baa034 --- /dev/null +++ b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs @@ -0,0 +1,283 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use once_cell::sync::Lazy; +use std::borrow::Cow; +use std::collections::HashMap; +use std::fmt; + +const MAX_COMMA_SEPARATED_METRICS_VALUES_LENGTH: usize = 1024; +#[allow(dead_code)] +const MAX_METRICS_ID_NUMBER: usize = 350; + +macro_rules! iterable_enum { + ($docs:tt, $enum_name:ident, $( $variant:ident ),*) => { + #[derive(Clone, Debug, Eq, Hash, PartialEq)] + #[non_exhaustive] + #[doc = $docs] + #[allow(missing_docs)] // for variants, not for the Enum itself + pub enum $enum_name { + $( $variant ),* + } + + #[allow(dead_code)] + impl $enum_name { + pub(crate) fn iter() -> impl Iterator { + const VARIANTS: &[$enum_name] = &[ + $( $enum_name::$variant ),* + ]; + VARIANTS.iter() + } + } + }; +} + +struct Base64Iterator { + current: Vec, + base64_chars: Vec, +} + +impl Base64Iterator { + #[allow(dead_code)] + fn new() -> Self { + Base64Iterator { + current: vec![0], // Start with the first character + base64_chars: (b'A'..=b'Z') // 'A'-'Z' + .chain(b'a'..=b'z') // 'a'-'z' + .chain(b'0'..=b'9') // '0'-'9' + .chain([b'+', b'-']) // '+' and '-' + .map(|c| c as char) + .collect(), + } + } + + fn increment(&mut self) { + let mut i = 0; + while i < self.current.len() { + self.current[i] += 1; + if self.current[i] < self.base64_chars.len() { + // The value at current position hasn't reached 64 + return; + } + self.current[i] = 0; + i += 1; + } + self.current.push(0); // Add new digit if all positions overflowed + } +} + +impl Iterator for Base64Iterator { + type Item = String; + + fn next(&mut self) -> Option { + if self.current.is_empty() { + return None; // No more items + } + + // Convert the current indices to characters + let result: String = self + .current + .iter() + .rev() + .map(|&idx| self.base64_chars[idx]) + .collect(); + + // Increment to the next value + self.increment(); + Some(result) + } +} + +pub(super) static FEATURE_ID_TO_METRIC_VALUE: Lazy>> = + Lazy::new(|| { + let mut m = HashMap::new(); + for (metric, value) in BusinessMetric::iter() + .cloned() + .zip(Base64Iterator::new()) + .take(MAX_METRICS_ID_NUMBER) + { + m.insert(metric, Cow::Owned(value)); + } + m + }); + +iterable_enum!( + "Enumerates human readable identifiers for the features tracked by metrics", + BusinessMetric, + ResourceModel, + Waiter, + Paginator, + RetryModeLegacy, + RetryModeStandard, + RetryModeAdaptive, + S3Transfer, + S3CryptoV1n, + S3CryptoV2, + S3ExpressBucket, + S3AccessGrants, + GzipRequestCompression, + ProtocolRpcV2Cbor, + EndpointOverride, + AccountIdEndpoint, + AccountIdModePreferred, + AccountIdModeDisabled, + AccountIdModeRequired, + Sigv4aSigning, + ResolvedAccountId +); + +#[derive(Clone, Debug, Default)] +pub(super) struct BusinessMetrics(Vec); + +impl BusinessMetrics { + pub(super) fn push(&mut self, metric: BusinessMetric) { + self.0.push(metric); + } + + pub(super) fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +fn drop_unfinished_metrics_to_fit(csv: &str, max_len: usize) -> Cow<'_, str> { + if csv.len() <= max_len { + Cow::Borrowed(csv) + } else { + let truncated = &csv[..max_len]; + if let Some(pos) = truncated.rfind(',') { + Cow::Owned(truncated[..pos].to_owned()) + } else { + Cow::Owned(truncated.to_owned()) + } + } +} + +impl fmt::Display for BusinessMetrics { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // business-metrics = "m/" metric_id *(comma metric_id) + let metrics_values = self + .0 + .iter() + .map(|feature_id| { + FEATURE_ID_TO_METRIC_VALUE + .get(feature_id) + .expect("{feature_id:?} should be found in `FEATURE_ID_TO_METRIC_VALUE`") + .clone() + }) + .collect::>() + .join(","); + + let metrics_values = drop_unfinished_metrics_to_fit( + &metrics_values, + MAX_COMMA_SEPARATED_METRICS_VALUES_LENGTH, + ); + + write!(f, "m/{}", metrics_values) + } +} +#[cfg(test)] +mod tests { + use crate::user_agent::metrics::{ + drop_unfinished_metrics_to_fit, Base64Iterator, FEATURE_ID_TO_METRIC_VALUE, + MAX_METRICS_ID_NUMBER, + }; + use crate::user_agent::BusinessMetric; + use convert_case::{Boundary, Case, Casing}; + use std::collections::HashMap; + use std::fmt::{Display, Formatter}; + + impl Display for BusinessMetric { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str( + &format!("{:?}", self) + .as_str() + .from_case(Case::Pascal) + .with_boundaries(&[Boundary::DigitUpper, Boundary::LowerUpper]) + .to_case(Case::ScreamingSnake), + ) + } + } + + #[test] + fn feature_id_to_metric_value() { + const EXPECTED: &str = r#" +{ + "RESOURCE_MODEL": "A", + "WAITER": "B", + "PAGINATOR": "C", + "RETRY_MODE_LEGACY": "D", + "RETRY_MODE_STANDARD": "E", + "RETRY_MODE_ADAPTIVE": "F", + "S3_TRANSFER": "G", + "S3_CRYPTO_V1N": "H", + "S3_CRYPTO_V2": "I", + "S3_EXPRESS_BUCKET": "J", + "S3_ACCESS_GRANTS": "K", + "GZIP_REQUEST_COMPRESSION": "L", + "PROTOCOL_RPC_V2_CBOR": "M", + "ENDPOINT_OVERRIDE": "N", + "ACCOUNT_ID_ENDPOINT": "O", + "ACCOUNT_ID_MODE_PREFERRED": "P", + "ACCOUNT_ID_MODE_DISABLED": "Q", + "ACCOUNT_ID_MODE_REQUIRED": "R", + "SIGV4A_SIGNING": "S", + "RESOLVED_ACCOUNT_ID": "T" +} + "#; + + let expected: HashMap<&str, &str> = serde_json::from_str(EXPECTED).unwrap(); + assert_eq!(expected.len(), FEATURE_ID_TO_METRIC_VALUE.len()); + + for (feature_id, metric_value) in &*FEATURE_ID_TO_METRIC_VALUE { + assert_eq!( + expected.get(format!("{feature_id}").as_str()).unwrap(), + metric_value, + ); + } + } + + #[test] + fn test_base64_iter() { + // 350 is the max number of metric IDs we support for now + let ids: Vec = Base64Iterator::new() + .into_iter() + .take(MAX_METRICS_ID_NUMBER) + .collect(); + assert_eq!("A", ids[0]); + assert_eq!("Z", ids[25]); + assert_eq!("a", ids[26]); + assert_eq!("z", ids[51]); + assert_eq!("0", ids[52]); + assert_eq!("9", ids[61]); + assert_eq!("+", ids[62]); + assert_eq!("-", ids[63]); + assert_eq!("AA", ids[64]); + assert_eq!("AB", ids[65]); + assert_eq!("A-", ids[127]); + assert_eq!("BA", ids[128]); + assert_eq!("Ed", ids[349]); + } + + #[test] + fn test_drop_unfinished_metrics_to_fit() { + let csv = "A,10BC,E"; + assert_eq!("A", drop_unfinished_metrics_to_fit(csv, 5)); + + let csv = "A10B,CE"; + assert_eq!("A10B", drop_unfinished_metrics_to_fit(csv, 5)); + + let csv = "A10BC,E"; + assert_eq!("A10BC", drop_unfinished_metrics_to_fit(csv, 5)); + + let csv = "A10BCE"; + assert_eq!("A10BC", drop_unfinished_metrics_to_fit(csv, 5)); + + let csv = "A"; + assert_eq!("A", drop_unfinished_metrics_to_fit(csv, 5)); + + let csv = "A,B"; + assert_eq!("A,B", drop_unfinished_metrics_to_fit(csv, 5)); + } +} diff --git a/tools/ci-build/changelogger/src/render.rs b/tools/ci-build/changelogger/src/render.rs index 252d150a28e3a5fb07d5642ca0c5839391b0dec0..40b0e149429ae66e8b21344f8532031d7ff25bf1 100644 --- a/tools/ci-build/changelogger/src/render.rs +++ b/tools/ci-build/changelogger/src/render.rs @@ -529,17 +529,11 @@ mod test { **New this release:** - :tada: (all, [smithy-rs#446](https://github.com/smithy-lang/smithy-rs/issues/446), [aws-sdk#123](https://github.com/aws/aws-sdk/issues/123), @external-contrib, @other-external-dev) I made a change to update the code generator -- :tada: (all, [smithy-rs#446](https://github.com/smithy-lang/smithy-rs/issues/446), [smithy-rs#447](https://github.com/smithy-lang/smithy-rs/issues/447), @external-contrib, @other-external-dev) I made a change to update the code generator - - **Update guide:** - blah blah -- (all, [smithy-rs#200](https://github.com/smithy-lang/smithy-rs/issues/200), @another-contrib) I made a minor change **Contributors** Thank you for your contributions! ❤ -- @another-contrib ([smithy-rs#200](https://github.com/smithy-lang/smithy-rs/issues/200)) -- @external-contrib ([smithy-rs#446](https://github.com/smithy-lang/smithy-rs/issues/446), [smithy-rs#447](https://github.com/smithy-lang/smithy-rs/issues/447)) -- @other-external-dev ([smithy-rs#446](https://github.com/smithy-lang/smithy-rs/issues/446), [smithy-rs#447](https://github.com/smithy-lang/smithy-rs/issues/447)) +- @external-contrib ([smithy-rs#446](https://github.com/smithy-lang/smithy-rs/issues/446)) +- @other-external-dev ([smithy-rs#446](https://github.com/smithy-lang/smithy-rs/issues/446)) "#; @@ -586,16 +580,6 @@ new_feature: true bug_fix: false --- I made a change to update the code generator -"#; - let smithy_rs_entry3 = r#"--- -applies_to: ["client", "server"] -authors: ["another-contrib"] -references: ["smithy-rs#200"] -breaking: false -new_feature: false -bug_fix: false ---- -I made a minor change "#; let aws_sdk_entry1 = r#"--- applies_to: ["aws-sdk-rust"] @@ -616,19 +600,6 @@ new_feature: true bug_fix: false --- I made a change to update the code generator -"#; - let smithy_rs_entry4 = r#"--- -applies_to: ["client", "server"] -authors: ["external-contrib", "other-external-dev"] -references: ["smithy-rs#446", "smithy-rs#447"] -breaking: false -new_feature: true -bug_fix: false ---- -I made a change to update the code generator - -**Update guide:** -blah blah "#; // We won't handwrite changelog entries for model updates, and they are still provided in @@ -656,10 +627,8 @@ message = "Some API change" [ smithy_rs_entry1, smithy_rs_entry2, - smithy_rs_entry3, aws_sdk_entry1, aws_sdk_entry2, - smithy_rs_entry4, ] .iter() .enumerate()