Skip to content

Commit

Permalink
Revamp errors in AWS runtime crates (#1922)
Browse files Browse the repository at this point in the history
Revamp errors in:
  * `aws-types`
  * `aws-endpoint`
  * `aws-http`
  * `aws-sig-auth`
  * `aws-inlineable`
  • Loading branch information
jdisanti authored Nov 16, 2022
1 parent 8dfe5a1 commit 543cac3
Show file tree
Hide file tree
Showing 15 changed files with 325 additions and 215 deletions.
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

0 comments on commit 543cac3

Please sign in to comment.