Unverified Commit 06c23f65 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Improve `Endpoint` panic-safety and ergonomics (#1984)

- `Endpoint::set_endpoint` no longer panics when called on an endpoint
  without a scheme
- `Endpoint::mutable` and `Endpoint::immutable` now both return a result
  so that constructing an endpoint without a scheme is an error
- `Endpoint::mutable` and `Endpoint::immutable` both now take a string
  instead of a `Uri` as a convenience
- `Endpoint::mutable_uri` and `Endpoint::immutable_uri` were added
  to construct an endpoint directly from a `Uri`
parent 927fe303
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -395,3 +395,39 @@ This is forward-compatible because the execution will hit the second last match
references = ["smithy-rs#1945"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "ysaito1001"

[[aws-sdk-rust]]
message = "Functions on `aws_smithy_http::endpoint::Endpoint` now return a `Result` instead of panicking."
references = ["smithy-rs#1984", "smithy-rs#1496"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "jdisanti"

[[smithy-rs]]
message = "Functions on `aws_smithy_http::endpoint::Endpoint` now return a `Result` instead of panicking."
references = ["smithy-rs#1984", "smithy-rs#1496"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"

[[aws-sdk-rust]]
message = "`Endpoint::mutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::mutable_uri`."
references = ["smithy-rs#1984", "smithy-rs#1496"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "jdisanti"

[[smithy-rs]]
message = "`Endpoint::mutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::mutable_uri`."
references = ["smithy-rs#1984", "smithy-rs#1496"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"

[[aws-sdk-rust]]
message = "`Endpoint::immutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::immutable_uri`."
references = ["smithy-rs#1984", "smithy-rs#1496"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "jdisanti"

[[smithy-rs]]
message = "`Endpoint::immutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::immutable_uri`."
references = ["smithy-rs#1984", "smithy-rs#1496"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"
+3 −2
Original line number Diff line number Diff line
@@ -8,11 +8,12 @@ allowed_external_types = [
   "aws_smithy_client::bounds::SmithyConnector",
   "aws_smithy_client::erase::DynConnector",
   "aws_smithy_client::erase::boxclone::BoxCloneService",
   "aws_smithy_client::http_connector::HttpConnector",
   "aws_smithy_client::http_connector::ConnectorSettings",
   "aws_smithy_client::http_connector::HttpConnector",
   "aws_smithy_http::body::SdkBody",
   "aws_smithy_http::result::SdkError",
   "aws_smithy_http::endpoint",
   "aws_smithy_http::endpoint::error::InvalidEndpointError",
   "aws_smithy_http::result::SdkError",
   "aws_smithy_types::retry",
   "aws_smithy_types::retry::*",
   "aws_smithy_types::timeout",
+5 −2
Original line number Diff line number Diff line
@@ -190,8 +190,11 @@ impl Provider {
                });
            }
        };
        let endpoint = Endpoint::immutable(Uri::from_static(BASE_HOST));
        endpoint.set_endpoint(&mut relative_uri, None);
        let endpoint =
            Endpoint::immutable_uri(Uri::from_static(BASE_HOST)).expect("BASE_HOST is valid");
        endpoint
            .set_endpoint(&mut relative_uri, None)
            .expect("appending relative URLs to the ECS endpoint should always succeed");
        Ok(relative_uri)
    }
}
+10 −11
Original line number Diff line number Diff line
@@ -8,9 +8,7 @@
//! Client for direct access to IMDSv2.

use crate::connector::expect_connector;
use crate::imds::client::error::{
    BuildError, BuildErrorKind, ImdsError, InnerImdsError, InvalidEndpointMode,
};
use crate::imds::client::error::{BuildError, ImdsError, InnerImdsError, InvalidEndpointMode};
use crate::imds::client::token::TokenMiddleware;
use crate::provider_config::ProviderConfig;
use crate::{profile, PKG_VERSION};
@@ -237,7 +235,10 @@ impl Client {
        let mut base_uri: Uri = path.parse().map_err(|_| {
            ImdsError::unexpected("IMDS path was not a valid URI. Hint: does it begin with `/`?")
        })?;
        self.inner.endpoint.set_endpoint(&mut base_uri, None);
        self.inner
            .endpoint
            .set_endpoint(&mut base_uri, None)
            .map_err(ImdsError::unexpected)?;
        let request = http::Request::builder()
            .uri(base_uri)
            .body(SdkBody::empty())
@@ -433,7 +434,7 @@ impl Builder {
            .endpoint
            .unwrap_or_else(|| EndpointSource::Env(config.env(), config.fs()));
        let endpoint = endpoint_source.endpoint(self.mode_override).await?;
        let endpoint = Endpoint::immutable(endpoint);
        let endpoint = Endpoint::immutable_uri(endpoint)?;
        let retry_config = retry::Config::default()
            .with_max_attempts(self.max_attempts.unwrap_or(DEFAULT_ATTEMPTS));
        let token_loader = token::TokenMiddleware::new(
@@ -496,16 +497,14 @@ impl EndpointSource {
                // load an endpoint override from the environment
                let profile = profile::load(fs, env, &Default::default())
                    .await
                    .map_err(BuildErrorKind::InvalidProfile)?;
                    .map_err(BuildError::invalid_profile)?;
                let uri_override = if let Ok(uri) = env.get(env::ENDPOINT) {
                    Some(Cow::Owned(uri))
                } else {
                    profile.get(profile_keys::ENDPOINT).map(Cow::Borrowed)
                };
                if let Some(uri) = uri_override {
                    return Ok(
                        Uri::try_from(uri.as_ref()).map_err(BuildErrorKind::InvalidEndpointUri)?
                    );
                    return Uri::try_from(uri.as_ref()).map_err(BuildError::invalid_endpoint_uri);
                }

                // if not, load a endpoint mode from the environment
@@ -513,10 +512,10 @@ impl EndpointSource {
                    mode
                } else if let Ok(mode) = env.get(env::ENDPOINT_MODE) {
                    mode.parse::<EndpointMode>()
                        .map_err(BuildErrorKind::InvalidEndpointMode)?
                        .map_err(BuildError::invalid_endpoint_mode)?
                } else if let Some(mode) = profile.get(profile_keys::ENDPOINT_MODE) {
                    mode.parse::<EndpointMode>()
                        .map_err(BuildErrorKind::InvalidEndpointMode)?
                        .map_err(BuildError::invalid_endpoint_mode)?
                } else {
                    EndpointMode::IpV4
                };
+31 −7
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@
use crate::profile::credentials::ProfileFileError;
use aws_smithy_client::SdkError;
use aws_smithy_http::body::SdkBody;
use http::uri::InvalidUri;
use aws_smithy_http::endpoint::error::InvalidEndpointError;
use std::error::Error;
use std::fmt;

@@ -174,7 +174,7 @@ impl Error for InvalidEndpointMode {}

#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
pub(super) enum BuildErrorKind {
enum BuildErrorKind {
    /// The endpoint mode was invalid
    InvalidEndpointMode(InvalidEndpointMode),

@@ -182,7 +182,7 @@ pub(super) enum BuildErrorKind {
    InvalidProfile(ProfileFileError),

    /// The specified endpoint was not a valid URI
    InvalidEndpointUri(InvalidUri),
    InvalidEndpointUri(Box<dyn Error + Send + Sync + 'static>),
}

/// Error constructing IMDSv2 Client
@@ -191,6 +191,28 @@ pub struct BuildError {
    kind: BuildErrorKind,
}

impl BuildError {
    pub(super) fn invalid_endpoint_mode(source: InvalidEndpointMode) -> Self {
        Self {
            kind: BuildErrorKind::InvalidEndpointMode(source),
        }
    }

    pub(super) fn invalid_profile(source: ProfileFileError) -> Self {
        Self {
            kind: BuildErrorKind::InvalidProfile(source),
        }
    }

    pub(super) fn invalid_endpoint_uri(
        source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
    ) -> Self {
        Self {
            kind: BuildErrorKind::InvalidEndpointUri(source.into()),
        }
    }
}

impl fmt::Display for BuildError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
        use BuildErrorKind::*;
@@ -209,14 +231,16 @@ impl Error for BuildError {
        match &self.kind {
            InvalidEndpointMode(e) => Some(e),
            InvalidProfile(e) => Some(e),
            InvalidEndpointUri(e) => Some(e),
            InvalidEndpointUri(e) => Some(e.as_ref()),
        }
    }
}

impl From<BuildErrorKind> for BuildError {
    fn from(kind: BuildErrorKind) -> Self {
        Self { kind }
impl From<InvalidEndpointError> for BuildError {
    fn from(err: InvalidEndpointError) -> Self {
        Self {
            kind: BuildErrorKind::InvalidEndpointUri(err.into()),
        }
    }
}

Loading