Skip to content

Commit 06c23f6

Browse files
authored
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`
1 parent 927fe30 commit 06c23f6

File tree

19 files changed

+269
-95
lines changed

19 files changed

+269
-95
lines changed

CHANGELOG.next.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,39 @@ This is forward-compatible because the execution will hit the second last match
395395
references = ["smithy-rs#1945"]
396396
meta = { "breaking" = true, "tada" = false, "bug" = false }
397397
author = "ysaito1001"
398+
399+
[[aws-sdk-rust]]
400+
message = "Functions on `aws_smithy_http::endpoint::Endpoint` now return a `Result` instead of panicking."
401+
references = ["smithy-rs#1984", "smithy-rs#1496"]
402+
meta = { "breaking" = true, "tada" = false, "bug" = false }
403+
author = "jdisanti"
404+
405+
[[smithy-rs]]
406+
message = "Functions on `aws_smithy_http::endpoint::Endpoint` now return a `Result` instead of panicking."
407+
references = ["smithy-rs#1984", "smithy-rs#1496"]
408+
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
409+
author = "jdisanti"
410+
411+
[[aws-sdk-rust]]
412+
message = "`Endpoint::mutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::mutable_uri`."
413+
references = ["smithy-rs#1984", "smithy-rs#1496"]
414+
meta = { "breaking" = true, "tada" = false, "bug" = false }
415+
author = "jdisanti"
416+
417+
[[smithy-rs]]
418+
message = "`Endpoint::mutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::mutable_uri`."
419+
references = ["smithy-rs#1984", "smithy-rs#1496"]
420+
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
421+
author = "jdisanti"
422+
423+
[[aws-sdk-rust]]
424+
message = "`Endpoint::immutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::immutable_uri`."
425+
references = ["smithy-rs#1984", "smithy-rs#1496"]
426+
meta = { "breaking" = true, "tada" = false, "bug" = false }
427+
author = "jdisanti"
428+
429+
[[smithy-rs]]
430+
message = "`Endpoint::immutable` now takes `impl AsRef<str>` instead of `Uri`. For the old functionality, use `Endpoint::immutable_uri`."
431+
references = ["smithy-rs#1984", "smithy-rs#1496"]
432+
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
433+
author = "jdisanti"

aws/rust-runtime/aws-config/external-types.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ allowed_external_types = [
88
"aws_smithy_client::bounds::SmithyConnector",
99
"aws_smithy_client::erase::DynConnector",
1010
"aws_smithy_client::erase::boxclone::BoxCloneService",
11-
"aws_smithy_client::http_connector::HttpConnector",
1211
"aws_smithy_client::http_connector::ConnectorSettings",
12+
"aws_smithy_client::http_connector::HttpConnector",
1313
"aws_smithy_http::body::SdkBody",
14-
"aws_smithy_http::result::SdkError",
1514
"aws_smithy_http::endpoint",
15+
"aws_smithy_http::endpoint::error::InvalidEndpointError",
16+
"aws_smithy_http::result::SdkError",
1617
"aws_smithy_types::retry",
1718
"aws_smithy_types::retry::*",
1819
"aws_smithy_types::timeout",

aws/rust-runtime/aws-config/src/ecs.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,11 @@ impl Provider {
190190
});
191191
}
192192
};
193-
let endpoint = Endpoint::immutable(Uri::from_static(BASE_HOST));
194-
endpoint.set_endpoint(&mut relative_uri, None);
193+
let endpoint =
194+
Endpoint::immutable_uri(Uri::from_static(BASE_HOST)).expect("BASE_HOST is valid");
195+
endpoint
196+
.set_endpoint(&mut relative_uri, None)
197+
.expect("appending relative URLs to the ECS endpoint should always succeed");
195198
Ok(relative_uri)
196199
}
197200
}

aws/rust-runtime/aws-config/src/imds/client.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
//! Client for direct access to IMDSv2.
99
1010
use crate::connector::expect_connector;
11-
use crate::imds::client::error::{
12-
BuildError, BuildErrorKind, ImdsError, InnerImdsError, InvalidEndpointMode,
13-
};
11+
use crate::imds::client::error::{BuildError, ImdsError, InnerImdsError, InvalidEndpointMode};
1412
use crate::imds::client::token::TokenMiddleware;
1513
use crate::provider_config::ProviderConfig;
1614
use crate::{profile, PKG_VERSION};
@@ -237,7 +235,10 @@ impl Client {
237235
let mut base_uri: Uri = path.parse().map_err(|_| {
238236
ImdsError::unexpected("IMDS path was not a valid URI. Hint: does it begin with `/`?")
239237
})?;
240-
self.inner.endpoint.set_endpoint(&mut base_uri, None);
238+
self.inner
239+
.endpoint
240+
.set_endpoint(&mut base_uri, None)
241+
.map_err(ImdsError::unexpected)?;
241242
let request = http::Request::builder()
242243
.uri(base_uri)
243244
.body(SdkBody::empty())
@@ -433,7 +434,7 @@ impl Builder {
433434
.endpoint
434435
.unwrap_or_else(|| EndpointSource::Env(config.env(), config.fs()));
435436
let endpoint = endpoint_source.endpoint(self.mode_override).await?;
436-
let endpoint = Endpoint::immutable(endpoint);
437+
let endpoint = Endpoint::immutable_uri(endpoint)?;
437438
let retry_config = retry::Config::default()
438439
.with_max_attempts(self.max_attempts.unwrap_or(DEFAULT_ATTEMPTS));
439440
let token_loader = token::TokenMiddleware::new(
@@ -496,27 +497,25 @@ impl EndpointSource {
496497
// load an endpoint override from the environment
497498
let profile = profile::load(fs, env, &Default::default())
498499
.await
499-
.map_err(BuildErrorKind::InvalidProfile)?;
500+
.map_err(BuildError::invalid_profile)?;
500501
let uri_override = if let Ok(uri) = env.get(env::ENDPOINT) {
501502
Some(Cow::Owned(uri))
502503
} else {
503504
profile.get(profile_keys::ENDPOINT).map(Cow::Borrowed)
504505
};
505506
if let Some(uri) = uri_override {
506-
return Ok(
507-
Uri::try_from(uri.as_ref()).map_err(BuildErrorKind::InvalidEndpointUri)?
508-
);
507+
return Uri::try_from(uri.as_ref()).map_err(BuildError::invalid_endpoint_uri);
509508
}
510509

511510
// if not, load a endpoint mode from the environment
512511
let mode = if let Some(mode) = mode_override {
513512
mode
514513
} else if let Ok(mode) = env.get(env::ENDPOINT_MODE) {
515514
mode.parse::<EndpointMode>()
516-
.map_err(BuildErrorKind::InvalidEndpointMode)?
515+
.map_err(BuildError::invalid_endpoint_mode)?
517516
} else if let Some(mode) = profile.get(profile_keys::ENDPOINT_MODE) {
518517
mode.parse::<EndpointMode>()
519-
.map_err(BuildErrorKind::InvalidEndpointMode)?
518+
.map_err(BuildError::invalid_endpoint_mode)?
520519
} else {
521520
EndpointMode::IpV4
522521
};

aws/rust-runtime/aws-config/src/imds/client/error.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use crate::profile::credentials::ProfileFileError;
99
use aws_smithy_client::SdkError;
1010
use aws_smithy_http::body::SdkBody;
11-
use http::uri::InvalidUri;
11+
use aws_smithy_http::endpoint::error::InvalidEndpointError;
1212
use std::error::Error;
1313
use std::fmt;
1414

@@ -174,15 +174,15 @@ impl Error for InvalidEndpointMode {}
174174

175175
#[derive(Debug)]
176176
#[allow(clippy::enum_variant_names)]
177-
pub(super) enum BuildErrorKind {
177+
enum BuildErrorKind {
178178
/// The endpoint mode was invalid
179179
InvalidEndpointMode(InvalidEndpointMode),
180180

181181
/// The AWS Profile (e.g. `~/.aws/config`) was invalid
182182
InvalidProfile(ProfileFileError),
183183

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

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

194+
impl BuildError {
195+
pub(super) fn invalid_endpoint_mode(source: InvalidEndpointMode) -> Self {
196+
Self {
197+
kind: BuildErrorKind::InvalidEndpointMode(source),
198+
}
199+
}
200+
201+
pub(super) fn invalid_profile(source: ProfileFileError) -> Self {
202+
Self {
203+
kind: BuildErrorKind::InvalidProfile(source),
204+
}
205+
}
206+
207+
pub(super) fn invalid_endpoint_uri(
208+
source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
209+
) -> Self {
210+
Self {
211+
kind: BuildErrorKind::InvalidEndpointUri(source.into()),
212+
}
213+
}
214+
}
215+
194216
impl fmt::Display for BuildError {
195217
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
196218
use BuildErrorKind::*;
@@ -209,14 +231,16 @@ impl Error for BuildError {
209231
match &self.kind {
210232
InvalidEndpointMode(e) => Some(e),
211233
InvalidProfile(e) => Some(e),
212-
InvalidEndpointUri(e) => Some(e),
234+
InvalidEndpointUri(e) => Some(e.as_ref()),
213235
}
214236
}
215237
}
216238

217-
impl From<BuildErrorKind> for BuildError {
218-
fn from(kind: BuildErrorKind) -> Self {
219-
Self { kind }
239+
impl From<InvalidEndpointError> for BuildError {
240+
fn from(err: InvalidEndpointError) -> Self {
241+
Self {
242+
kind: BuildErrorKind::InvalidEndpointUri(err.into()),
243+
}
220244
}
221245
}
222246

aws/rust-runtime/aws-config/src/imds/client/token.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ impl TokenMiddleware {
128128

129129
async fn get_token(&self) -> Result<(Token, SystemTime), ImdsError> {
130130
let mut uri = Uri::from_static("/latest/api/token");
131-
self.endpoint.set_endpoint(&mut uri, None);
131+
self.endpoint
132+
.set_endpoint(&mut uri, None)
133+
.map_err(ImdsError::unexpected)?;
132134
let request = http::Request::builder()
133135
.header(
134136
X_AWS_EC2_METADATA_TOKEN_TTL_SECONDS,

aws/rust-runtime/aws-config/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,14 @@ mod loader {
323323
///
324324
/// Use a static endpoint for all services
325325
/// ```no_run
326-
/// # async fn create_config() {
326+
/// # async fn create_config() -> Result<(), aws_smithy_http::endpoint::error::InvalidEndpointError> {
327327
/// use aws_config::endpoint::Endpoint;
328328
///
329329
/// let sdk_config = aws_config::from_env()
330-
/// .endpoint_resolver(Endpoint::immutable("http://localhost:1234".parse().expect("valid URI")))
330+
/// .endpoint_resolver(Endpoint::immutable("http://localhost:1234")?)
331331
/// .load()
332332
/// .await;
333+
/// # Ok(())
333334
/// # }
334335
pub fn endpoint_resolver(
335336
mut self,

aws/rust-runtime/aws-endpoint/src/partition/endpoint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl ResolveAwsEndpoint for Metadata {
5454
fn resolve_endpoint(&self, region: &Region) -> Result<AwsEndpoint, BoxError> {
5555
let uri = self.uri_template.replace("{region}", region.as_ref());
5656
let uri = format!("{}://{}", self.protocol.as_str(), uri);
57-
let endpoint = Endpoint::mutable(uri.parse()?);
57+
let endpoint = Endpoint::mutable(uri)?;
5858
let mut credential_scope = CredentialScope::builder().region(
5959
self.credential_scope
6060
.region()

aws/rust-runtime/aws-endpoint/src/partition/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ mod test {
373373
.resolve_endpoint(&Region::new(test_case.region))
374374
.expect("valid region");
375375
let mut test_uri = Uri::from_static("/");
376-
endpoint.set_endpoint(&mut test_uri, None);
376+
endpoint.set_endpoint(&mut test_uri, None).unwrap();
377377
assert_eq!(test_uri, Uri::from_static(test_case.uri));
378378
assert_eq!(
379379
endpoint.credential_scope().region(),

aws/rust-runtime/aws-types/external-types.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ allowed_external_types = [
44
"aws_smithy_client::http_connector::HttpConnector",
55
"aws_smithy_http::endpoint::Endpoint",
66
"aws_smithy_http::endpoint::EndpointPrefix",
7+
"aws_smithy_http::endpoint::error::InvalidEndpointError",
78
"aws_smithy_types::retry::RetryConfig",
89
"aws_smithy_types::timeout::TimeoutConfig",
910
"http::uri::Uri",

0 commit comments

Comments
 (0)