Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp errors in AWS runtime crates #1922

Merged
merged 6 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions aws/rust-runtime/aws-config/src/imds/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,29 @@ use crate::imds::client::{ImdsError, LazyClient};
use crate::json_credentials::{parse_json_credentials, JsonCredentials, RefreshableCredentials};
use crate::provider_config::ProviderConfig;
use aws_smithy_client::SdkError;
use aws_smithy_types::error::display::DisplayErrorContext;
use aws_types::credentials::{future, CredentialsError, ProvideCredentials};
use aws_types::os_shim_internal::Env;
use aws_types::{credentials, Credentials};
use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt;

#[derive(Debug)]
struct ImdsCommunicationError {
source: Box<dyn StdError + Send + Sync + 'static>,
}

impl fmt::Display for ImdsCommunicationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "could not communicate with IMDS")
}
}

impl StdError for ImdsCommunicationError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.source.as_ref())
}
}

/// IMDSv2 Credentials Provider
///
Expand Down Expand Up @@ -138,11 +156,10 @@ impl ImdsCredentialsProvider {
);
Err(CredentialsError::not_loaded("received 404 from IMDS"))
}
Err(ImdsError::FailedToLoadToken(ref err @ SdkError::DispatchFailure(_))) => {
Err(CredentialsError::not_loaded(format!(
"could not communicate with IMDS: {}",
DisplayErrorContext(&err)
)))
Err(ImdsError::FailedToLoadToken(err @ SdkError::DispatchFailure(_))) => {
Err(CredentialsError::not_loaded(ImdsCommunicationError {
source: err.into(),
}))
}
Err(other) => Err(CredentialsError::provider_error(other)),
}
Expand Down
10 changes: 5 additions & 5 deletions aws/rust-runtime/aws-config/src/meta/credentials/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ impl CredentialsProviderChain {
tracing::debug!(provider = %name, "loaded credentials");
return Ok(credentials);
}
Err(CredentialsError::CredentialsNotLoaded { context, .. }) => {
tracing::debug!(provider = %name, context = %context, "provider in chain did not provide credentials");
Err(err @ CredentialsError::CredentialsNotLoaded(_)) => {
tracing::debug!(provider = %name, context = %DisplayErrorContext(&err), "provider in chain did not provide credentials");
}
Err(e) => {
tracing::warn!(provider = %name, error = %DisplayErrorContext(&e), "provider failed to provide credentials");
return Err(e);
Err(err) => {
tracing::warn!(provider = %name, error = %DisplayErrorContext(&err), "provider failed to provide credentials");
return Err(err);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion aws/rust-runtime/aws-config/src/web_identity_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ mod test {
};
use aws_sdk_sts::Region;
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_types::error::display::DisplayErrorContext;
use aws_types::credentials::CredentialsError;
use aws_types::os_shim_internal::{Env, Fs};
use std::collections::HashMap;
Expand Down Expand Up @@ -308,7 +309,7 @@ mod test {
.await
.expect_err("should fail, provider not loaded");
assert!(
format!("{}", err).contains("AWS_ROLE_ARN"),
format!("{}", DisplayErrorContext(&err)).contains("AWS_ROLE_ARN"),
"`{}` did not contain expected string",
err
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "imds-default-chain",
"docs": "IMDS isn't specifically configured but is loaded as part of the default chain. This has the exact same HTTP traffic as imds_no_iam_role, they are equivalent.",
"result": {
"ErrorContains": "The credential provider was not enabled"
"ErrorContains": "the credential provider was not enabled"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "imds-disabled",
"docs": "when IMDS is disabled by an environment variable, it shouldn't be used as part of the default chain",
"result": {
"ErrorContains": "The credential provider was not enabled"
"ErrorContains": "the credential provider was not enabled"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "imds-token-fail",
"docs": "attempts to acquire an IMDS token, but the instance doesn't have a role configured",
"result": {
"ErrorContains": "The credential provider was not enabled"
"ErrorContains": "the credential provider was not enabled"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "credential_process_failure",
"docs": "credential_process handles the external process exiting with a non-zero exit code",
"result": {
"ErrorContains": "An error occurred while loading credentials: Error retrieving credentials: external process exited with code exit status: 1"
"ErrorContains": "an error occurred while loading credentials: Error retrieving credentials: external process exited with code exit status: 1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "empty-config",
"docs": "no config was defined",
"result": {
"ErrorContains": "The credential provider was not enabled"
"ErrorContains": "the credential provider was not enabled"
}
}
49 changes: 35 additions & 14 deletions aws/rust-runtime/aws-endpoint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use http::header::HeaderName;
use http::{HeaderValue, Uri};
use std::error::Error;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use std::sync::Arc;

Expand Down Expand Up @@ -100,19 +99,41 @@ impl ResolveEndpoint<Params> for EndpointShim {
pub struct AwsEndpointStage;

#[derive(Debug)]
pub enum AwsEndpointStageError {
enum AwsEndpointStageErrorKind {
NoEndpointResolver,
NoRegion,
EndpointResolutionError(BoxError),
}

impl Display for AwsEndpointStageError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(self, f)
#[derive(Debug)]
pub struct AwsEndpointStageError {
kind: AwsEndpointStageErrorKind,
}

impl fmt::Display for AwsEndpointStageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use AwsEndpointStageErrorKind::*;
match &self.kind {
NoEndpointResolver => write!(f, "endpoint resolution failed: no endpoint resolver"),
EndpointResolutionError(_) => write!(f, "endpoint resolution failed"),
}
}
}

impl Error for AwsEndpointStageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use AwsEndpointStageErrorKind::*;
match &self.kind {
EndpointResolutionError(source) => Some(source.as_ref() as _),
NoEndpointResolver => None,
}
}
}

impl Error for AwsEndpointStageError {}
impl From<AwsEndpointStageErrorKind> for AwsEndpointStageError {
fn from(kind: AwsEndpointStageErrorKind) -> Self {
Self { kind }
}
}

impl MapRequest for AwsEndpointStage {
type Error = AwsEndpointStageError;
Expand All @@ -121,7 +142,7 @@ impl MapRequest for AwsEndpointStage {
request.augment(|mut http_req, props| {
let endpoint_result = props
.get_mut::<aws_smithy_http::endpoint::Result>()
.ok_or(AwsEndpointStageError::NoEndpointResolver)?;
.ok_or(AwsEndpointStageErrorKind::NoEndpointResolver)?;
let endpoint = match endpoint_result {
// downgrade the mut ref to a shared ref
Ok(_endpoint) => props.get::<aws_smithy_http::endpoint::Result>()
Expand All @@ -131,25 +152,25 @@ impl MapRequest for AwsEndpointStage {
Err(e) => {
// We need to own the error to return it, so take it and leave a stub error in
// its place
return Err(AwsEndpointStageError::EndpointResolutionError(std::mem::replace(
return Err(AwsEndpointStageErrorKind::EndpointResolutionError(std::mem::replace(
e,
ResolveEndpointError::message("the original error was directly returned")
).into()));
).into()).into());
}
};
let (uri, signing_scope_override, signing_service_override) = smithy_to_aws(endpoint)
.map_err(|err| AwsEndpointStageError::EndpointResolutionError(err))?;
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err))?;
tracing::debug!(endpoint = ?endpoint, base_region = ?signing_scope_override, "resolved endpoint");
apply_endpoint(http_req.uri_mut(), &uri, props.get::<EndpointPrefix>())
.map_err(|err|AwsEndpointStageError::EndpointResolutionError(err.into()))?;
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err.into()))?;
for (header_name, header_values) in endpoint.headers() {
http_req.headers_mut().remove(header_name);
for value in header_values {
http_req.headers_mut().insert(
HeaderName::from_str(header_name)
.map_err(|err|AwsEndpointStageError::EndpointResolutionError(err.into()))?,
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err.into()))?,
HeaderValue::from_str(value)
.map_err(|err|AwsEndpointStageError::EndpointResolutionError(err.into()))?,
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err.into()))?,
);
}
}
Expand Down
34 changes: 14 additions & 20 deletions aws/rust-runtime/aws-http/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl CredentialsStage {
}
// if we get another error class, there is probably something actually wrong that the user will
// want to know about
Err(other) => return Err(CredentialsStageError::CredentialsLoadingError(other)),
Err(other) => return Err(other.into()),
}
Ok(request)
}
Expand All @@ -67,34 +67,28 @@ mod error {

/// Failures that can occur in the credentials middleware.
#[derive(Debug)]
pub enum CredentialsStageError {
/// No credentials provider was found in the property bag for the operation.
MissingCredentialsProvider,
/// Failed to load credentials with the credential provider in the property bag.
CredentialsLoadingError(CredentialsError),
pub struct CredentialsStageError {
source: CredentialsError,
}

impl StdError for CredentialsStageError {}
impl StdError for CredentialsStageError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&self.source as _)
}
}

impl fmt::Display for CredentialsStageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CredentialsStageError::*;
match self {
MissingCredentialsProvider => {
write!(f, "No credentials provider in the property bag")
}
CredentialsLoadingError(err) => write!(
f,
"Failed to load credentials from the credentials provider: {}",
err
),
}
write!(
f,
"failed to load credentials from the credentials provider"
)
}
}

impl From<CredentialsError> for CredentialsStageError {
fn from(err: CredentialsError) -> Self {
CredentialsStageError::CredentialsLoadingError(err)
fn from(source: CredentialsError) -> Self {
CredentialsStageError { source }
}
}
}
Expand Down
40 changes: 31 additions & 9 deletions aws/rust-runtime/aws-http/src/user_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,31 +525,53 @@ impl UserAgentStage {
}
}

/// Failures that can arise from the user agent middleware
#[derive(Debug)]
pub enum UserAgentStageError {
enum UserAgentStageErrorKind {
/// There was no [`AwsUserAgent`] in the property bag.
UserAgentMissing,
/// The formatted user agent string is not a valid HTTP header value. This indicates a bug.
InvalidHeader(InvalidHeaderValue),
}

impl Error for UserAgentStageError {}
/// Failures that can arise from the user agent middleware
#[derive(Debug)]
pub struct UserAgentStageError {
kind: UserAgentStageErrorKind,
}

impl Error for UserAgentStageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use UserAgentStageErrorKind::*;
match &self.kind {
InvalidHeader(source) => Some(source as _),
UserAgentMissing => None,
}
}
}

impl fmt::Display for UserAgentStageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UserAgentMissing => write!(f, "User agent missing from property bag"),
Self::InvalidHeader(_) => {
write!(f, "Provided user agent header was invalid. This is a bug.")
use UserAgentStageErrorKind::*;
match self.kind {
UserAgentMissing => write!(f, "user agent missing from property bag"),
InvalidHeader(_) => {
write!(f, "provided user agent header was invalid (this is a bug)")
}
}
}
}

impl From<UserAgentStageErrorKind> for UserAgentStageError {
fn from(kind: UserAgentStageErrorKind) -> Self {
Self { kind }
}
}

impl From<InvalidHeaderValue> for UserAgentStageError {
fn from(value: InvalidHeaderValue) -> Self {
UserAgentStageError::InvalidHeader(value)
Self {
kind: UserAgentStageErrorKind::InvalidHeader(value),
}
}
}

Expand All @@ -564,7 +586,7 @@ impl MapRequest for UserAgentStage {
request.augment(|mut req, conf| {
let ua = conf
.get::<AwsUserAgent>()
.ok_or(UserAgentStageError::UserAgentMissing)?;
.ok_or(UserAgentStageErrorKind::UserAgentMissing)?;
req.headers_mut()
.append(USER_AGENT, HeaderValue::try_from(ua.ua_header())?);
req.headers_mut().append(
Expand Down
Loading