Loading CHANGELOG.next.toml +29 −0 Original line number Diff line number Diff line Loading @@ -32,3 +32,32 @@ result of the compilation.""" references = ["aws-sdk-rust#975", "smithy-rs#3269"] meta = { "breaking" = false, "tada" = true, "bug" = false } author = "jdisanti" [[aws-sdk-rust]] message = """Add `test_credentials` to `ConfigLoader` in `aws_config`. This allows the following pattern during tests: ```rust async fn main() { let conf = aws_config::defaults(BehaviorVersion::latest()) .test_credentials() .await; } ``` This is designed for unit tests and using local mocks like DynamoDB Local and LocalStack with the SDK. """ meta = { "breaking" = false, "tada" = true, "bug" = false } author = "rcoh" references = ["smithy-rs#3279", "aws-sdk-rust#971"] [[aws-sdk-rust]] message = "Improve the error messages for when auth fails to select an auth scheme for a request." references = ["aws-sdk-rust#979", "smithy-rs#3277"] meta = { "breaking" = false, "tada" = false, "bug" = false } author = "jdisanti" [[smithy-rs]] message = "Improve the error messages for when auth fails to select an auth scheme for a request." references = ["smithy-rs#3277"] meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" } author = "jdisanti" aws/rust-runtime/aws-config/Cargo.toml +4 −3 Original line number Diff line number Diff line Loading @@ -12,15 +12,17 @@ repository = "https://github.com/smithy-lang/smithy-rs" behavior-version-latest = [] client-hyper = ["aws-smithy-runtime/connector-hyper-0-14-x"] rustls = ["aws-smithy-runtime/tls-rustls", "client-hyper"] allow-compilation = [] # our tests use `cargo test --all-features` and native-tls breaks CI rt-tokio = ["aws-smithy-async/rt-tokio", "aws-smithy-runtime/rt-tokio", "tokio/rt"] sso = ["dep:aws-sdk-sso", "dep:aws-sdk-ssooidc", "dep:ring", "dep:hex", "dep:zeroize", "aws-smithy-runtime-api/http-auth"] credentials-process = ["tokio/process"] default = ["client-hyper", "rustls", "rt-tokio", "credentials-process", "sso"] # deprecated: this feature does nothing allow-compilation = [] [dependencies] aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types" } aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../sdk/build/aws-sdk/sdk/aws-http" } aws-sdk-sts = { path = "../../sdk/build/aws-sdk/sdk/sts", default-features = false } aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async" } Loading Loading @@ -52,7 +54,6 @@ zeroize = { version = "1", optional = true } aws-sdk-ssooidc = { path = "../../sdk/build/aws-sdk/sdk/ssooidc", default-features = false, optional = true } [dev-dependencies] aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x", "test-util"] } aws-smithy-runtime-api = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util"] } futures-util = { version = "0.3.16", default-features = false } Loading aws/rust-runtime/aws-config/src/lib.rs +10 −0 Original line number Diff line number Diff line Loading @@ -212,6 +212,7 @@ mod loader { use crate::profile::profile_file::ProfileFiles; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; use aws_credential_types::Credentials; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion; Loading Loading @@ -458,6 +459,10 @@ mod loader { /// anonymous auth for S3, calling operations in STS that don't require a signature, /// or using token-based auth. /// /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you /// want. If credentials are disabled, requests cannot be signed. For these use cases, use /// [`test_credentials`](Self::test_credentials). /// /// # Examples /// /// Turn off credentials in order to call a service without signing: Loading @@ -474,6 +479,11 @@ mod loader { self } /// Set test credentials for use when signing requests pub fn test_credentials(self) -> Self { self.credentials_provider(Credentials::for_tests()) } /// Override the name of the app used to build [`SdkConfig`](aws_types::SdkConfig). /// /// This _optional_ name is used to identify the application in the user agent that Loading aws/sdk/integration-tests/dynamodb/tests/auth_scheme_error.rs 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ use aws_sdk_dynamodb::config::Region; use aws_sdk_dynamodb::error::DisplayErrorContext; use aws_sdk_dynamodb::{Client, Config}; use aws_smithy_runtime::assert_str_contains; use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn auth_scheme_error() { let (http_client, _) = capture_request(None); let config = Config::builder() .behavior_version_latest() .http_client(http_client) .region(Region::new("us-west-2")) // intentionally omitting credentials_provider .build(); let client = Client::from_conf(config); let err = client .list_tables() .send() .await .expect_err("there is no credential provider, so this must fail"); assert_str_contains!( DisplayErrorContext(&err).to_string(), "\"sigv4\" wasn't a valid option because there was no identity resolver for it. Be sure to set an identity" ); } rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs +220 −7 Original line number Diff line number Diff line Loading @@ -20,18 +20,86 @@ use std::error::Error as StdError; use std::fmt; use tracing::trace; #[derive(Debug)] struct NoMatchingAuthSchemeError(ExploredList); impl fmt::Display for NoMatchingAuthSchemeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let explored = &self.0; // Use the information we have about the auth options that were explored to construct // as helpful of an error message as possible. if explored.items().count() == 0 { return f.write_str( "no auth options are available. This can happen if there's \ a problem with the service model, or if there is a codegen bug.", ); } if explored .items() .all(|explored| matches!(explored.result, ExploreResult::NoAuthScheme)) { return f.write_str( "no auth schemes are registered. This can happen if there's \ a problem with the service model, or if there is a codegen bug.", ); } let mut try_add_identity = false; let mut likely_bug = false; f.write_str("failed to select an auth scheme to sign the request with.")?; for item in explored.items() { write!( f, " \"{}\" wasn't a valid option because ", item.scheme_id.as_str() )?; f.write_str(match item.result { ExploreResult::NoAuthScheme => { likely_bug = true; "no auth scheme was registered for it." } ExploreResult::NoIdentityResolver => { try_add_identity = true; "there was no identity resolver for it." } ExploreResult::MissingEndpointConfig => { likely_bug = true; "there is auth config in the endpoint config, but this scheme wasn't listed in it \ (see https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details)." } ExploreResult::NotExplored => { debug_assert!(false, "this should be unreachable"); "<unknown>" } })?; } if try_add_identity { f.write_str(" Be sure to set an identity, such as credentials, auth token, or other identity type that is required for this service.")?; } else if likely_bug { f.write_str(" This is likely a bug.")?; } if explored.truncated { f.write_str(" Note: there were other auth schemes that were evaluated that weren't listed here.")?; } Ok(()) } } impl StdError for NoMatchingAuthSchemeError {} #[derive(Debug)] enum AuthOrchestrationError { NoMatchingAuthScheme, MissingEndpointConfig, BadAuthSchemeEndpointConfig(Cow<'static, str>), } impl fmt::Display for AuthOrchestrationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NoMatchingAuthScheme => f.write_str( "no auth scheme matched auth scheme options. This is a bug. Please file an issue.", ), // This error is never bubbled up Self::MissingEndpointConfig => f.write_str("missing endpoint config"), Self::BadAuthSchemeEndpointConfig(message) => f.write_str(message), } } Loading Loading @@ -59,6 +127,8 @@ pub(super) async fn orchestrate_auth( "orchestrating auth", ); let mut explored = ExploredList::default(); // Iterate over IDs of possibly-supported auth schemes for &scheme_id in options.as_ref() { // For each ID, try to resolve the corresponding auth scheme. Loading Loading @@ -95,16 +165,21 @@ pub(super) async fn orchestrate_auth( )?; return Ok(()); } Err(AuthOrchestrationError::NoMatchingAuthScheme) => { Err(AuthOrchestrationError::MissingEndpointConfig) => { explored.push(scheme_id, ExploreResult::MissingEndpointConfig); continue; } Err(other_err) => return Err(other_err.into()), } } else { explored.push(scheme_id, ExploreResult::NoIdentityResolver); } } else { explored.push(scheme_id, ExploreResult::NoAuthScheme); } } Err(AuthOrchestrationError::NoMatchingAuthScheme.into()) Err(NoMatchingAuthSchemeError(explored).into()) } fn extract_endpoint_auth_scheme_config( Loading Loading @@ -135,10 +210,66 @@ fn extract_endpoint_auth_scheme_config( .and_then(Document::as_string); config_scheme_id == Some(scheme_id.as_str()) }) .ok_or(AuthOrchestrationError::NoMatchingAuthScheme)?; .ok_or(AuthOrchestrationError::MissingEndpointConfig)?; Ok(AuthSchemeEndpointConfig::from(Some(auth_scheme_config))) } #[derive(Debug)] enum ExploreResult { NotExplored, NoAuthScheme, NoIdentityResolver, MissingEndpointConfig, } /// Information about an evaluated auth option. /// This should be kept small so it can fit in an array on the stack. #[derive(Debug)] struct ExploredAuthOption { scheme_id: AuthSchemeId, result: ExploreResult, } impl Default for ExploredAuthOption { fn default() -> Self { Self { scheme_id: AuthSchemeId::new(""), result: ExploreResult::NotExplored, } } } const MAX_EXPLORED_LIST_LEN: usize = 8; /// Stack allocated list of explored auth options for error messaging #[derive(Default)] struct ExploredList { items: [ExploredAuthOption; MAX_EXPLORED_LIST_LEN], len: usize, truncated: bool, } impl ExploredList { fn items(&self) -> impl Iterator<Item = &ExploredAuthOption> { self.items.iter().take(self.len) } fn push(&mut self, scheme_id: AuthSchemeId, result: ExploreResult) { if self.len + 1 >= self.items.len() { self.truncated = true; } else { self.items[self.len] = ExploredAuthOption { scheme_id, result }; self.len += 1; } } } impl fmt::Debug for ExploredList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ExploredList") .field("items", &&self.items[0..self.len]) .field("truncated", &self.truncated) .finish() } } #[cfg(all(test, feature = "test-util"))] mod tests { use super::*; Loading Loading @@ -481,4 +612,86 @@ mod tests { .unwrap() ); } #[test] fn friendly_error_messages() { let err = NoMatchingAuthSchemeError(ExploredList::default()); assert_eq!( "no auth options are available. This can happen if there's a problem with \ the service model, or if there is a codegen bug.", err.to_string() ); let mut list = ExploredList::default(); list.push( AuthSchemeId::new("SigV4"), ExploreResult::NoIdentityResolver, ); list.push( AuthSchemeId::new("SigV4a"), ExploreResult::NoIdentityResolver, ); let err = NoMatchingAuthSchemeError(list); assert_eq!( "failed to select an auth scheme to sign the request with. \ \"SigV4\" wasn't a valid option because there was no identity resolver for it. \ \"SigV4a\" wasn't a valid option because there was no identity resolver for it. \ Be sure to set an identity, such as credentials, auth token, or other identity \ type that is required for this service.", err.to_string() ); // It should prioritize the suggestion to try an identity before saying it's a bug let mut list = ExploredList::default(); list.push( AuthSchemeId::new("SigV4"), ExploreResult::NoIdentityResolver, ); list.push( AuthSchemeId::new("SigV4a"), ExploreResult::MissingEndpointConfig, ); let err = NoMatchingAuthSchemeError(list); assert_eq!( "failed to select an auth scheme to sign the request with. \ \"SigV4\" wasn't a valid option because there was no identity resolver for it. \ \"SigV4a\" wasn't a valid option because there is auth config in the endpoint \ config, but this scheme wasn't listed in it (see \ https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \ Be sure to set an identity, such as credentials, auth token, or other identity \ type that is required for this service.", err.to_string() ); // Otherwise, it should suggest it's a bug let mut list = ExploredList::default(); list.push( AuthSchemeId::new("SigV4a"), ExploreResult::MissingEndpointConfig, ); let err = NoMatchingAuthSchemeError(list); assert_eq!( "failed to select an auth scheme to sign the request with. \ \"SigV4a\" wasn't a valid option because there is auth config in the endpoint \ config, but this scheme wasn't listed in it (see \ https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \ This is likely a bug.", err.to_string() ); // Truncation should be indicated let mut list = ExploredList::default(); for _ in 0..=MAX_EXPLORED_LIST_LEN { list.push( AuthSchemeId::new("dontcare"), ExploreResult::MissingEndpointConfig, ); } let err = NoMatchingAuthSchemeError(list).to_string(); if !err.contains( "Note: there were other auth schemes that were evaluated that weren't listed here", ) { panic!("The error should indicate that the explored list was truncated."); } } } Loading
CHANGELOG.next.toml +29 −0 Original line number Diff line number Diff line Loading @@ -32,3 +32,32 @@ result of the compilation.""" references = ["aws-sdk-rust#975", "smithy-rs#3269"] meta = { "breaking" = false, "tada" = true, "bug" = false } author = "jdisanti" [[aws-sdk-rust]] message = """Add `test_credentials` to `ConfigLoader` in `aws_config`. This allows the following pattern during tests: ```rust async fn main() { let conf = aws_config::defaults(BehaviorVersion::latest()) .test_credentials() .await; } ``` This is designed for unit tests and using local mocks like DynamoDB Local and LocalStack with the SDK. """ meta = { "breaking" = false, "tada" = true, "bug" = false } author = "rcoh" references = ["smithy-rs#3279", "aws-sdk-rust#971"] [[aws-sdk-rust]] message = "Improve the error messages for when auth fails to select an auth scheme for a request." references = ["aws-sdk-rust#979", "smithy-rs#3277"] meta = { "breaking" = false, "tada" = false, "bug" = false } author = "jdisanti" [[smithy-rs]] message = "Improve the error messages for when auth fails to select an auth scheme for a request." references = ["smithy-rs#3277"] meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" } author = "jdisanti"
aws/rust-runtime/aws-config/Cargo.toml +4 −3 Original line number Diff line number Diff line Loading @@ -12,15 +12,17 @@ repository = "https://github.com/smithy-lang/smithy-rs" behavior-version-latest = [] client-hyper = ["aws-smithy-runtime/connector-hyper-0-14-x"] rustls = ["aws-smithy-runtime/tls-rustls", "client-hyper"] allow-compilation = [] # our tests use `cargo test --all-features` and native-tls breaks CI rt-tokio = ["aws-smithy-async/rt-tokio", "aws-smithy-runtime/rt-tokio", "tokio/rt"] sso = ["dep:aws-sdk-sso", "dep:aws-sdk-ssooidc", "dep:ring", "dep:hex", "dep:zeroize", "aws-smithy-runtime-api/http-auth"] credentials-process = ["tokio/process"] default = ["client-hyper", "rustls", "rt-tokio", "credentials-process", "sso"] # deprecated: this feature does nothing allow-compilation = [] [dependencies] aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types" } aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../sdk/build/aws-sdk/sdk/aws-http" } aws-sdk-sts = { path = "../../sdk/build/aws-sdk/sdk/sts", default-features = false } aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async" } Loading Loading @@ -52,7 +54,6 @@ zeroize = { version = "1", optional = true } aws-sdk-ssooidc = { path = "../../sdk/build/aws-sdk/sdk/ssooidc", default-features = false, optional = true } [dev-dependencies] aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x", "test-util"] } aws-smithy-runtime-api = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util"] } futures-util = { version = "0.3.16", default-features = false } Loading
aws/rust-runtime/aws-config/src/lib.rs +10 −0 Original line number Diff line number Diff line Loading @@ -212,6 +212,7 @@ mod loader { use crate::profile::profile_file::ProfileFiles; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; use aws_credential_types::Credentials; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion; Loading Loading @@ -458,6 +459,10 @@ mod loader { /// anonymous auth for S3, calling operations in STS that don't require a signature, /// or using token-based auth. /// /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you /// want. If credentials are disabled, requests cannot be signed. For these use cases, use /// [`test_credentials`](Self::test_credentials). /// /// # Examples /// /// Turn off credentials in order to call a service without signing: Loading @@ -474,6 +479,11 @@ mod loader { self } /// Set test credentials for use when signing requests pub fn test_credentials(self) -> Self { self.credentials_provider(Credentials::for_tests()) } /// Override the name of the app used to build [`SdkConfig`](aws_types::SdkConfig). /// /// This _optional_ name is used to identify the application in the user agent that Loading
aws/sdk/integration-tests/dynamodb/tests/auth_scheme_error.rs 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ use aws_sdk_dynamodb::config::Region; use aws_sdk_dynamodb::error::DisplayErrorContext; use aws_sdk_dynamodb::{Client, Config}; use aws_smithy_runtime::assert_str_contains; use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn auth_scheme_error() { let (http_client, _) = capture_request(None); let config = Config::builder() .behavior_version_latest() .http_client(http_client) .region(Region::new("us-west-2")) // intentionally omitting credentials_provider .build(); let client = Client::from_conf(config); let err = client .list_tables() .send() .await .expect_err("there is no credential provider, so this must fail"); assert_str_contains!( DisplayErrorContext(&err).to_string(), "\"sigv4\" wasn't a valid option because there was no identity resolver for it. Be sure to set an identity" ); }
rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs +220 −7 Original line number Diff line number Diff line Loading @@ -20,18 +20,86 @@ use std::error::Error as StdError; use std::fmt; use tracing::trace; #[derive(Debug)] struct NoMatchingAuthSchemeError(ExploredList); impl fmt::Display for NoMatchingAuthSchemeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let explored = &self.0; // Use the information we have about the auth options that were explored to construct // as helpful of an error message as possible. if explored.items().count() == 0 { return f.write_str( "no auth options are available. This can happen if there's \ a problem with the service model, or if there is a codegen bug.", ); } if explored .items() .all(|explored| matches!(explored.result, ExploreResult::NoAuthScheme)) { return f.write_str( "no auth schemes are registered. This can happen if there's \ a problem with the service model, or if there is a codegen bug.", ); } let mut try_add_identity = false; let mut likely_bug = false; f.write_str("failed to select an auth scheme to sign the request with.")?; for item in explored.items() { write!( f, " \"{}\" wasn't a valid option because ", item.scheme_id.as_str() )?; f.write_str(match item.result { ExploreResult::NoAuthScheme => { likely_bug = true; "no auth scheme was registered for it." } ExploreResult::NoIdentityResolver => { try_add_identity = true; "there was no identity resolver for it." } ExploreResult::MissingEndpointConfig => { likely_bug = true; "there is auth config in the endpoint config, but this scheme wasn't listed in it \ (see https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details)." } ExploreResult::NotExplored => { debug_assert!(false, "this should be unreachable"); "<unknown>" } })?; } if try_add_identity { f.write_str(" Be sure to set an identity, such as credentials, auth token, or other identity type that is required for this service.")?; } else if likely_bug { f.write_str(" This is likely a bug.")?; } if explored.truncated { f.write_str(" Note: there were other auth schemes that were evaluated that weren't listed here.")?; } Ok(()) } } impl StdError for NoMatchingAuthSchemeError {} #[derive(Debug)] enum AuthOrchestrationError { NoMatchingAuthScheme, MissingEndpointConfig, BadAuthSchemeEndpointConfig(Cow<'static, str>), } impl fmt::Display for AuthOrchestrationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NoMatchingAuthScheme => f.write_str( "no auth scheme matched auth scheme options. This is a bug. Please file an issue.", ), // This error is never bubbled up Self::MissingEndpointConfig => f.write_str("missing endpoint config"), Self::BadAuthSchemeEndpointConfig(message) => f.write_str(message), } } Loading Loading @@ -59,6 +127,8 @@ pub(super) async fn orchestrate_auth( "orchestrating auth", ); let mut explored = ExploredList::default(); // Iterate over IDs of possibly-supported auth schemes for &scheme_id in options.as_ref() { // For each ID, try to resolve the corresponding auth scheme. Loading Loading @@ -95,16 +165,21 @@ pub(super) async fn orchestrate_auth( )?; return Ok(()); } Err(AuthOrchestrationError::NoMatchingAuthScheme) => { Err(AuthOrchestrationError::MissingEndpointConfig) => { explored.push(scheme_id, ExploreResult::MissingEndpointConfig); continue; } Err(other_err) => return Err(other_err.into()), } } else { explored.push(scheme_id, ExploreResult::NoIdentityResolver); } } else { explored.push(scheme_id, ExploreResult::NoAuthScheme); } } Err(AuthOrchestrationError::NoMatchingAuthScheme.into()) Err(NoMatchingAuthSchemeError(explored).into()) } fn extract_endpoint_auth_scheme_config( Loading Loading @@ -135,10 +210,66 @@ fn extract_endpoint_auth_scheme_config( .and_then(Document::as_string); config_scheme_id == Some(scheme_id.as_str()) }) .ok_or(AuthOrchestrationError::NoMatchingAuthScheme)?; .ok_or(AuthOrchestrationError::MissingEndpointConfig)?; Ok(AuthSchemeEndpointConfig::from(Some(auth_scheme_config))) } #[derive(Debug)] enum ExploreResult { NotExplored, NoAuthScheme, NoIdentityResolver, MissingEndpointConfig, } /// Information about an evaluated auth option. /// This should be kept small so it can fit in an array on the stack. #[derive(Debug)] struct ExploredAuthOption { scheme_id: AuthSchemeId, result: ExploreResult, } impl Default for ExploredAuthOption { fn default() -> Self { Self { scheme_id: AuthSchemeId::new(""), result: ExploreResult::NotExplored, } } } const MAX_EXPLORED_LIST_LEN: usize = 8; /// Stack allocated list of explored auth options for error messaging #[derive(Default)] struct ExploredList { items: [ExploredAuthOption; MAX_EXPLORED_LIST_LEN], len: usize, truncated: bool, } impl ExploredList { fn items(&self) -> impl Iterator<Item = &ExploredAuthOption> { self.items.iter().take(self.len) } fn push(&mut self, scheme_id: AuthSchemeId, result: ExploreResult) { if self.len + 1 >= self.items.len() { self.truncated = true; } else { self.items[self.len] = ExploredAuthOption { scheme_id, result }; self.len += 1; } } } impl fmt::Debug for ExploredList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ExploredList") .field("items", &&self.items[0..self.len]) .field("truncated", &self.truncated) .finish() } } #[cfg(all(test, feature = "test-util"))] mod tests { use super::*; Loading Loading @@ -481,4 +612,86 @@ mod tests { .unwrap() ); } #[test] fn friendly_error_messages() { let err = NoMatchingAuthSchemeError(ExploredList::default()); assert_eq!( "no auth options are available. This can happen if there's a problem with \ the service model, or if there is a codegen bug.", err.to_string() ); let mut list = ExploredList::default(); list.push( AuthSchemeId::new("SigV4"), ExploreResult::NoIdentityResolver, ); list.push( AuthSchemeId::new("SigV4a"), ExploreResult::NoIdentityResolver, ); let err = NoMatchingAuthSchemeError(list); assert_eq!( "failed to select an auth scheme to sign the request with. \ \"SigV4\" wasn't a valid option because there was no identity resolver for it. \ \"SigV4a\" wasn't a valid option because there was no identity resolver for it. \ Be sure to set an identity, such as credentials, auth token, or other identity \ type that is required for this service.", err.to_string() ); // It should prioritize the suggestion to try an identity before saying it's a bug let mut list = ExploredList::default(); list.push( AuthSchemeId::new("SigV4"), ExploreResult::NoIdentityResolver, ); list.push( AuthSchemeId::new("SigV4a"), ExploreResult::MissingEndpointConfig, ); let err = NoMatchingAuthSchemeError(list); assert_eq!( "failed to select an auth scheme to sign the request with. \ \"SigV4\" wasn't a valid option because there was no identity resolver for it. \ \"SigV4a\" wasn't a valid option because there is auth config in the endpoint \ config, but this scheme wasn't listed in it (see \ https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \ Be sure to set an identity, such as credentials, auth token, or other identity \ type that is required for this service.", err.to_string() ); // Otherwise, it should suggest it's a bug let mut list = ExploredList::default(); list.push( AuthSchemeId::new("SigV4a"), ExploreResult::MissingEndpointConfig, ); let err = NoMatchingAuthSchemeError(list); assert_eq!( "failed to select an auth scheme to sign the request with. \ \"SigV4a\" wasn't a valid option because there is auth config in the endpoint \ config, but this scheme wasn't listed in it (see \ https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \ This is likely a bug.", err.to_string() ); // Truncation should be indicated let mut list = ExploredList::default(); for _ in 0..=MAX_EXPLORED_LIST_LEN { list.push( AuthSchemeId::new("dontcare"), ExploreResult::MissingEndpointConfig, ); } let err = NoMatchingAuthSchemeError(list).to_string(); if !err.contains( "Note: there were other auth schemes that were evaluated that weren't listed here", ) { panic!("The error should indicate that the explored list was truncated."); } } }