mirror of https://github.com/smithy-lang/smithy-rs
Delete `aws_smithy_http::ResolveEndpoint` and point usages to service-specific trait (#3078)
## Motivation and Context - Fixes https://github.com/awslabs/smithy-rs/issues/3043 As a follow up to #3072 this removes the old endpoint resolver interfaces in favor of creating a per-service resolver trait. This trait defines a `into_shared_resolver()` method which converts the local trait into a global resolver that can be used with the orchestrator. ## Description <!--- Describe your changes in detail --> ## Testing <!--- Please describe in detail how you tested your changes --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [x] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
This commit is contained in:
parent
bcfc211277
commit
12fa4d3963
|
@ -414,3 +414,15 @@ message = "The `idempotency_provider` field has been removed from config as a pu
|
|||
references = ["smithy-rs#3072"]
|
||||
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
|
||||
author = "rcoh"
|
||||
|
||||
[[smithy-rs]]
|
||||
message = "The `config::Builder::endpoint_resolver` method no longer accepts `&'static str`. Use `config::Builder::endpoint_url` instead."
|
||||
references = ["smithy-rs#3078"]
|
||||
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
|
||||
author = "rcoh"
|
||||
|
||||
[[smithy-rs]]
|
||||
message = "**This change has [detailed upgrade guidance](https://github.com/awslabs/smithy-rs/discussions/3079).** <br><br>The endpoint interfaces from `aws-smithy-http` have been removed. Service-specific endpoint resolver traits have been added."
|
||||
references = ["smithy-rs#3043", "smithy-rs#3078"]
|
||||
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
|
||||
author = "rcoh"
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
use aws_smithy_async::future::BoxFuture;
|
||||
use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
|
||||
use aws_smithy_async::time::SharedTimeSource;
|
||||
use aws_smithy_http::endpoint::{ResolveEndpoint, ResolveEndpointError};
|
||||
use aws_smithy_runtime_api::box_error::BoxError;
|
||||
use aws_smithy_runtime_api::client::endpoint::{
|
||||
EndpointFuture, EndpointResolverParams, ResolveEndpoint,
|
||||
};
|
||||
use aws_smithy_types::endpoint::Endpoint;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::future::Future;
|
||||
|
@ -20,11 +23,9 @@ use tokio::sync::oneshot::{Receiver, Sender};
|
|||
/// Endpoint reloader
|
||||
#[must_use]
|
||||
pub struct ReloadEndpoint {
|
||||
loader: Box<
|
||||
dyn Fn() -> BoxFuture<'static, (Endpoint, SystemTime), ResolveEndpointError> + Send + Sync,
|
||||
>,
|
||||
loader: Box<dyn Fn() -> BoxFuture<'static, (Endpoint, SystemTime), BoxError> + Send + Sync>,
|
||||
endpoint: Arc<Mutex<Option<ExpiringEndpoint>>>,
|
||||
error: Arc<Mutex<Option<ResolveEndpointError>>>,
|
||||
error: Arc<Mutex<Option<BoxError>>>,
|
||||
rx: Receiver<()>,
|
||||
sleep: SharedAsyncSleep,
|
||||
time: SharedTimeSource,
|
||||
|
@ -79,14 +80,14 @@ impl ReloadEndpoint {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct EndpointCache {
|
||||
error: Arc<Mutex<Option<ResolveEndpointError>>>,
|
||||
error: Arc<Mutex<Option<BoxError>>>,
|
||||
endpoint: Arc<Mutex<Option<ExpiringEndpoint>>>,
|
||||
// When the sender is dropped, this allows the reload loop to stop
|
||||
_drop_guard: Arc<Sender<()>>,
|
||||
}
|
||||
|
||||
impl<T> ResolveEndpoint<T> for EndpointCache {
|
||||
fn resolve_endpoint(&self, _params: &T) -> aws_smithy_http::endpoint::Result {
|
||||
impl ResolveEndpoint for EndpointCache {
|
||||
fn resolve_endpoint<'a>(&'a self, _params: &'a EndpointResolverParams) -> EndpointFuture<'a> {
|
||||
self.resolve_endpoint()
|
||||
}
|
||||
}
|
||||
|
@ -111,9 +112,9 @@ pub(crate) async fn create_cache<F>(
|
|||
loader_fn: impl Fn() -> F + Send + Sync + 'static,
|
||||
sleep: SharedAsyncSleep,
|
||||
time: SharedTimeSource,
|
||||
) -> Result<(EndpointCache, ReloadEndpoint), ResolveEndpointError>
|
||||
) -> Result<(EndpointCache, ReloadEndpoint), BoxError>
|
||||
where
|
||||
F: Future<Output = Result<(Endpoint, SystemTime), ResolveEndpointError>> + Send + 'static,
|
||||
F: Future<Output = Result<(Endpoint, SystemTime), BoxError>> + Send + 'static,
|
||||
{
|
||||
let error_holder = Arc::new(Mutex::new(None));
|
||||
let endpoint_holder = Arc::new(Mutex::new(None));
|
||||
|
@ -135,25 +136,24 @@ where
|
|||
reloader.reload_once().await;
|
||||
// if we didn't successfully get an endpoint, bail out so the client knows
|
||||
// configuration failed to work
|
||||
cache.resolve_endpoint()?;
|
||||
cache.resolve_endpoint().await?;
|
||||
Ok((cache, reloader))
|
||||
}
|
||||
|
||||
impl EndpointCache {
|
||||
fn resolve_endpoint(&self) -> aws_smithy_http::endpoint::Result {
|
||||
fn resolve_endpoint(&self) -> EndpointFuture<'_> {
|
||||
tracing::trace!("resolving endpoint from endpoint discovery cache");
|
||||
self.endpoint
|
||||
let ep = self
|
||||
.endpoint
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|e| e.endpoint.clone())
|
||||
.ok_or_else(|| {
|
||||
self.error
|
||||
.lock()
|
||||
.unwrap()
|
||||
.take()
|
||||
.unwrap_or_else(|| ResolveEndpointError::message("no endpoint loaded"))
|
||||
})
|
||||
let error: Option<BoxError> = self.error.lock().unwrap().take();
|
||||
error.unwrap_or_else(|| "Failed to resolve endpoint".into())
|
||||
});
|
||||
EndpointFuture::ready(ep)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,7 @@ mod test {
|
|||
.await
|
||||
.expect("returns an endpoint");
|
||||
assert_eq!(
|
||||
cache.resolve_endpoint().expect("ok").url(),
|
||||
cache.resolve_endpoint().await.expect("ok").url(),
|
||||
"http://foo.com/1"
|
||||
);
|
||||
// 120 second buffer
|
||||
|
@ -223,13 +223,13 @@ mod test {
|
|||
.reload_increment(expiry - Duration::from_secs(240))
|
||||
.await;
|
||||
assert_eq!(
|
||||
cache.resolve_endpoint().expect("ok").url(),
|
||||
cache.resolve_endpoint().await.expect("ok").url(),
|
||||
"http://foo.com/1"
|
||||
);
|
||||
|
||||
reloader.reload_increment(expiry).await;
|
||||
assert_eq!(
|
||||
cache.resolve_endpoint().expect("ok").url(),
|
||||
cache.resolve_endpoint().await.expect("ok").url(),
|
||||
"http://foo.com/2"
|
||||
);
|
||||
}
|
||||
|
@ -266,18 +266,27 @@ mod test {
|
|||
gate.expect_sleep().await.duration(),
|
||||
Duration::from_secs(60)
|
||||
);
|
||||
assert_eq!(cache.resolve_endpoint().unwrap().url(), "http://foo.com/1");
|
||||
assert_eq!(
|
||||
cache.resolve_endpoint().await.unwrap().url(),
|
||||
"http://foo.com/1"
|
||||
);
|
||||
// t = 60
|
||||
|
||||
let sleep = gate.expect_sleep().await;
|
||||
// we're still holding the drop guard, so we haven't expired yet.
|
||||
assert_eq!(cache.resolve_endpoint().unwrap().url(), "http://foo.com/1");
|
||||
assert_eq!(
|
||||
cache.resolve_endpoint().await.unwrap().url(),
|
||||
"http://foo.com/1"
|
||||
);
|
||||
assert_eq!(sleep.duration(), Duration::from_secs(60));
|
||||
sleep.allow_progress();
|
||||
// t = 120
|
||||
|
||||
let sleep = gate.expect_sleep().await;
|
||||
assert_eq!(cache.resolve_endpoint().unwrap().url(), "http://foo.com/2");
|
||||
assert_eq!(
|
||||
cache.resolve_endpoint().await.unwrap().url(),
|
||||
"http://foo.com/2"
|
||||
);
|
||||
sleep.allow_progress();
|
||||
|
||||
let sleep = gate.expect_sleep().await;
|
||||
|
|
|
@ -55,11 +55,9 @@ class TimestreamDecorator : ClientCodegenDecorator {
|
|||
// helper function to resolve an endpoint given a base client
|
||||
rustTemplate(
|
||||
"""
|
||||
async fn resolve_endpoint(client: &crate::Client) -> Result<(#{Endpoint}, #{SystemTime}), #{ResolveEndpointError}> {
|
||||
async fn resolve_endpoint(client: &crate::Client) -> Result<(#{Endpoint}, #{SystemTime}), #{BoxError}> {
|
||||
let describe_endpoints =
|
||||
client.describe_endpoints().send().await.map_err(|e| {
|
||||
#{ResolveEndpointError}::from_source("failed to call describe_endpoints", e)
|
||||
})?;
|
||||
client.describe_endpoints().send().await?;
|
||||
let endpoint = describe_endpoints.endpoints().get(0).unwrap();
|
||||
let expiry = client.config().time_source().expect("checked when ep discovery was enabled").now()
|
||||
+ #{Duration}::from_secs(endpoint.cache_period_in_minutes() as u64 * 60);
|
||||
|
@ -75,7 +73,7 @@ class TimestreamDecorator : ClientCodegenDecorator {
|
|||
/// Enable endpoint discovery for this client
|
||||
///
|
||||
/// This method MUST be called to construct a working client.
|
||||
pub async fn with_endpoint_discovery_enabled(self) -> #{Result}<(Self, #{endpoint_discovery}::ReloadEndpoint), #{ResolveEndpointError}> {
|
||||
pub async fn with_endpoint_discovery_enabled(self) -> #{Result}<(Self, #{endpoint_discovery}::ReloadEndpoint), #{BoxError}> {
|
||||
let handle = self.handle.clone();
|
||||
|
||||
// The original client without endpoint discover gets moved into the endpoint discovery
|
||||
|
@ -92,11 +90,11 @@ class TimestreamDecorator : ClientCodegenDecorator {
|
|||
.expect("endpoint discovery requires the client config to have a time source"),
|
||||
).await?;
|
||||
|
||||
let client_with_discovery = crate::Client::from_conf(
|
||||
handle.conf.to_builder()
|
||||
.endpoint_resolver(#{SharedEndpointResolver}::new(resolver))
|
||||
.build()
|
||||
);
|
||||
use #{IntoShared};
|
||||
let mut conf = handle.conf.to_builder();
|
||||
conf.set_endpoint_resolver(Some(resolver.into_shared()));
|
||||
|
||||
let client_with_discovery = crate::Client::from_conf(conf.build());
|
||||
Ok((client_with_discovery, reloader))
|
||||
}
|
||||
}
|
||||
|
@ -104,10 +102,10 @@ class TimestreamDecorator : ClientCodegenDecorator {
|
|||
*RuntimeType.preludeScope,
|
||||
"Arc" to RuntimeType.Arc,
|
||||
"Duration" to RuntimeType.std.resolve("time::Duration"),
|
||||
"SharedEndpointResolver" to RuntimeType.smithyHttp(codegenContext.runtimeConfig)
|
||||
.resolve("endpoint::SharedEndpointResolver"),
|
||||
"SystemTime" to RuntimeType.std.resolve("time::SystemTime"),
|
||||
"endpoint_discovery" to endpointDiscovery.toType(),
|
||||
"BoxError" to RuntimeType.boxError(codegenContext.runtimeConfig),
|
||||
"IntoShared" to RuntimeType.smithyRuntimeApi(codegenContext.runtimeConfig).resolve("shared::IntoShared"),
|
||||
*Types(codegenContext.runtimeConfig).toArray(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ class ClientModuleDocProvider(
|
|||
ClientRustModule.Config.endpoint -> strDoc("Types needed to configure endpoint resolution.")
|
||||
ClientRustModule.Config.retry -> strDoc("Retry configuration.")
|
||||
ClientRustModule.Config.timeout -> strDoc("Timeout configuration.")
|
||||
ClientRustModule.Config.interceptors -> strDoc("Types needed to implement [`Interceptor`](crate::config::Interceptor).")
|
||||
ClientRustModule.Config.interceptors -> strDoc("Types needed to implement [`Intercept`](crate::config::Intercept).")
|
||||
ClientRustModule.Error -> strDoc("Common errors and error handling utilities.")
|
||||
ClientRustModule.Operation -> strDoc("All operations that this crate can perform.")
|
||||
ClientRustModule.Meta -> strDoc("Information about this crate.")
|
||||
|
|
|
@ -7,10 +7,10 @@ package software.amazon.smithy.rust.codegen.client.smithy.endpoint
|
|||
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.serviceSpecificEndpointResolver
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.rust
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.writable
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
|
||||
|
@ -26,23 +26,21 @@ internal class EndpointConfigCustomization(
|
|||
ConfigCustomization() {
|
||||
private val runtimeConfig = codegenContext.runtimeConfig
|
||||
private val moduleUseName = codegenContext.moduleUseName()
|
||||
private val types = Types(runtimeConfig)
|
||||
private val epModule = RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::endpoint")
|
||||
private val epRuntimeModule = RuntimeType.smithyRuntime(runtimeConfig).resolve("client::orchestrator::endpoints")
|
||||
|
||||
private val codegenScope = arrayOf(
|
||||
*preludeScope,
|
||||
"DefaultEndpointResolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::orchestrator::endpoints::DefaultEndpointResolver"),
|
||||
"Endpoint" to RuntimeType.smithyHttp(runtimeConfig).resolve("endpoint::Endpoint"),
|
||||
"OldSharedEndpointResolver" to types.sharedEndpointResolver,
|
||||
"Params" to typesGenerator.paramsStruct(),
|
||||
"IntoShared" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("shared::IntoShared"),
|
||||
"Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"),
|
||||
"SharedEndpointResolver" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::endpoint::SharedEndpointResolver"),
|
||||
"SmithyResolver" to types.resolveEndpoint,
|
||||
"SharedEndpointResolver" to epModule.resolve("SharedEndpointResolver"),
|
||||
"StaticUriEndpointResolver" to epRuntimeModule.resolve("StaticUriEndpointResolver"),
|
||||
"ServiceSpecificResolver" to codegenContext.serviceSpecificEndpointResolver(),
|
||||
)
|
||||
|
||||
override fun section(section: ServiceConfig): Writable {
|
||||
return writable {
|
||||
val sharedEndpointResolver = "#{OldSharedEndpointResolver}<#{Params}>"
|
||||
val resolverTrait = "#{SmithyResolver}<#{Params}>"
|
||||
when (section) {
|
||||
is ServiceConfig.ConfigImpl -> {
|
||||
rustTemplate(
|
||||
|
@ -57,44 +55,16 @@ internal class EndpointConfigCustomization(
|
|||
}
|
||||
|
||||
ServiceConfig.BuilderImpl -> {
|
||||
val endpointModule = ClientRustModule.Config.endpoint.fullyQualifiedPath()
|
||||
.replace("crate::", "$moduleUseName::")
|
||||
// if there are no rules, we don't generate a default resolver—we need to also suppress those docs.
|
||||
val defaultResolverDocs = if (typesGenerator.defaultResolver() != null) {
|
||||
val endpointModule = ClientRustModule.Config.endpoint.fullyQualifiedPath()
|
||||
.replace("crate::", "$moduleUseName::")
|
||||
"""
|
||||
///
|
||||
/// When unset, the client will used a generated endpoint resolver based on the endpoint resolution
|
||||
/// rules for `$moduleUseName`.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```no_run
|
||||
/// use aws_smithy_http::endpoint;
|
||||
/// use $endpointModule::{Params as EndpointParams, DefaultResolver};
|
||||
/// /// Endpoint resolver which adds a prefix to the generated endpoint
|
||||
/// ##[derive(Debug)]
|
||||
/// struct PrefixResolver {
|
||||
/// base_resolver: DefaultResolver,
|
||||
/// prefix: String
|
||||
/// }
|
||||
/// impl endpoint::ResolveEndpoint<EndpointParams> for PrefixResolver {
|
||||
/// fn resolve_endpoint(&self, params: &EndpointParams) -> endpoint::Result {
|
||||
/// self.base_resolver
|
||||
/// .resolve_endpoint(params)
|
||||
/// .map(|ep|{
|
||||
/// let url = ep.url().to_string();
|
||||
/// ep.into_builder().url(format!("{}.{}", &self.prefix, url)).build()
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// let prefix_resolver = PrefixResolver {
|
||||
/// base_resolver: DefaultResolver::new(),
|
||||
/// prefix: "subdomain".to_string()
|
||||
/// };
|
||||
/// let config = $moduleUseName::Config::builder().endpoint_resolver(prefix_resolver);
|
||||
/// ```
|
||||
"""
|
||||
} else {
|
||||
""
|
||||
"/// This service does not define a default endpoint resolver."
|
||||
}
|
||||
if (codegenContext.settings.codegenConfig.includeEndpointUrlConfig) {
|
||||
rustTemplate(
|
||||
|
@ -120,9 +90,8 @@ internal class EndpointConfigCustomization(
|
|||
##[allow(deprecated)]
|
||||
self.set_endpoint_resolver(
|
||||
endpoint_url.map(|url| {
|
||||
#{OldSharedEndpointResolver}::new(
|
||||
#{Endpoint}::immutable(url).expect("invalid endpoint URL")
|
||||
)
|
||||
use #{IntoShared};
|
||||
#{StaticUriEndpointResolver}::uri(url).into_shared()
|
||||
})
|
||||
);
|
||||
self
|
||||
|
@ -135,31 +104,48 @@ internal class EndpointConfigCustomization(
|
|||
"""
|
||||
/// Sets the endpoint resolver to use when making requests.
|
||||
///
|
||||
/// Note: setting an endpoint resolver will replace any endpoint URL that has been set.
|
||||
///
|
||||
$defaultResolverDocs
|
||||
pub fn endpoint_resolver(mut self, endpoint_resolver: impl $resolverTrait + 'static) -> Self {
|
||||
self.set_endpoint_resolver(#{Some}(#{OldSharedEndpointResolver}::new(endpoint_resolver)));
|
||||
///
|
||||
/// Note: setting an endpoint resolver will replace any endpoint URL that has been set.
|
||||
/// This method accepts an endpoint resolver [specific to this service](#{ServiceSpecificResolver}). If you want to
|
||||
/// provide a shared endpoint resolver, use [`Self::set_endpoint_resolver`].
|
||||
///
|
||||
/// ## Examples
|
||||
/// Create a custom endpoint resolver that resolves a different endpoing per-stage, e.g. staging vs. production.
|
||||
/// ```no_run
|
||||
/// use $endpointModule::{ResolveEndpoint, EndpointFuture, Params, Endpoint};
|
||||
/// ##[derive(Debug)]
|
||||
/// struct StageResolver { stage: String }
|
||||
/// impl ResolveEndpoint for StageResolver {
|
||||
/// fn resolve_endpoint(&self, params: &Params) -> EndpointFuture<'_> {
|
||||
/// let stage = &self.stage;
|
||||
/// EndpointFuture::ready(Ok(Endpoint::builder().url(format!("{stage}.myservice.com")).build()))
|
||||
/// }
|
||||
/// }
|
||||
/// let resolver = StageResolver { stage: std::env::var("STAGE").unwrap() };
|
||||
/// let config = $moduleUseName::Config::builder().endpoint_resolver(resolver).build();
|
||||
/// let client = $moduleUseName::Client::from_conf(config);
|
||||
/// ```
|
||||
pub fn endpoint_resolver(mut self, endpoint_resolver: impl #{ServiceSpecificResolver} + 'static) -> Self {
|
||||
self.set_endpoint_resolver(#{Some}(endpoint_resolver.into_shared_resolver()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the endpoint resolver to use when making requests.
|
||||
///
|
||||
/// When unset, the client will used a generated endpoint resolver based on the endpoint resolution
|
||||
/// rules for `$moduleUseName`.
|
||||
$defaultResolverDocs
|
||||
""",
|
||||
*codegenScope,
|
||||
)
|
||||
|
||||
rustTemplate(
|
||||
"""
|
||||
pub fn set_endpoint_resolver(&mut self, endpoint_resolver: #{Option}<$sharedEndpointResolver>) -> &mut Self {
|
||||
self.runtime_components.set_endpoint_resolver(endpoint_resolver.map(|r|#{wrap_resolver}));
|
||||
pub fn set_endpoint_resolver(&mut self, endpoint_resolver: #{Option}<#{SharedEndpointResolver}>) -> &mut Self {
|
||||
self.runtime_components.set_endpoint_resolver(endpoint_resolver);
|
||||
self
|
||||
}
|
||||
""",
|
||||
*codegenScope,
|
||||
"wrap_resolver" to codegenContext.wrapResolver { rust("r") },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ class EndpointTypesGenerator(
|
|||
it,
|
||||
params,
|
||||
codegenContext = codegenContext,
|
||||
endpointCustomizations = codegenContext.rootDecorator.endpointCustomizations(codegenContext),
|
||||
).generate()
|
||||
}
|
||||
?: {}
|
||||
|
|
|
@ -12,15 +12,14 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
|
|||
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.CustomRuntimeFunction
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.endpointTestsModule
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.serviceSpecificEndpointResolver
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.SmithyEndpointsStdLib
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.map
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.writable
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
|
||||
|
||||
/**
|
||||
|
@ -118,8 +117,9 @@ class EndpointsDecorator : ClientCodegenDecorator {
|
|||
override fun section(section: ServiceRuntimePluginSection): Writable {
|
||||
return when (section) {
|
||||
is ServiceRuntimePluginSection.RegisterRuntimeComponents -> writable {
|
||||
codegenContext.defaultEndpointResolver()
|
||||
?.let { resolver -> section.registerEndpointResolver(this, resolver) }
|
||||
codegenContext.defaultEndpointResolver()?.also { resolver ->
|
||||
section.registerEndpointResolver(this, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
else -> emptySection
|
||||
|
@ -138,28 +138,22 @@ class EndpointsDecorator : ClientCodegenDecorator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rules-generated endpoint resolver for this service
|
||||
*
|
||||
* If no endpoint rules are provided, `null` will be returned.
|
||||
*/
|
||||
private fun ClientCodegenContext.defaultEndpointResolver(): Writable? {
|
||||
val generator = EndpointTypesGenerator.fromContext(this)
|
||||
val defaultResolver = generator.defaultResolver() ?: return null
|
||||
val ctx = arrayOf("DefaultResolver" to defaultResolver)
|
||||
return wrapResolver { rustTemplate("#{DefaultResolver}::new()", *ctx) }
|
||||
}
|
||||
|
||||
fun ClientCodegenContext.wrapResolver(resolver: Writable): Writable {
|
||||
val generator = EndpointTypesGenerator.fromContext(this)
|
||||
return resolver.map { base ->
|
||||
val types = Types(runtimeConfig)
|
||||
val ctx = arrayOf(
|
||||
"DefaultEndpointResolver" to RuntimeType.smithyRuntime(runtimeConfig)
|
||||
.resolve("client::orchestrator::endpoints::DefaultEndpointResolver"),
|
||||
"Params" to generator.paramsStruct(),
|
||||
"OldSharedEndpointResolver" to types.sharedEndpointResolver,
|
||||
)
|
||||
|
||||
val ctx = arrayOf("DefaultResolver" to defaultResolver, "ServiceSpecificResolver" to serviceSpecificEndpointResolver())
|
||||
return writable {
|
||||
rustTemplate(
|
||||
"#{DefaultEndpointResolver}::<#{Params}>::new(#{OldSharedEndpointResolver}::new(#{base}))",
|
||||
"""{
|
||||
use #{ServiceSpecificResolver};
|
||||
#{DefaultResolver}::new().into_shared_resolver()
|
||||
}""",
|
||||
*ctx,
|
||||
"base" to base,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,12 +57,18 @@ internal fun endpointsLib(name: String, vararg additionalDependency: RustDepende
|
|||
class Types(runtimeConfig: RuntimeConfig) {
|
||||
private val smithyTypesEndpointModule = RuntimeType.smithyTypes(runtimeConfig).resolve("endpoint")
|
||||
val smithyHttpEndpointModule = RuntimeType.smithyHttp(runtimeConfig).resolve("endpoint")
|
||||
val resolveEndpoint = smithyHttpEndpointModule.resolve("ResolveEndpoint")
|
||||
val sharedEndpointResolver = smithyHttpEndpointModule.resolve("SharedEndpointResolver")
|
||||
val smithyEndpoint = smithyTypesEndpointModule.resolve("Endpoint")
|
||||
val endpointFuture = RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::endpoint::EndpointFuture")
|
||||
private val endpointRtApi = RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::endpoint")
|
||||
val resolveEndpointError = smithyHttpEndpointModule.resolve("ResolveEndpointError")
|
||||
|
||||
fun toArray() = arrayOf("ResolveEndpointError" to resolveEndpointError, "Endpoint" to smithyEndpoint)
|
||||
fun toArray() = arrayOf(
|
||||
"Endpoint" to smithyEndpoint,
|
||||
"EndpointFuture" to endpointFuture,
|
||||
"SharedEndpointResolver" to endpointRtApi.resolve("SharedEndpointResolver"),
|
||||
"EndpointResolverParams" to endpointRtApi.resolve("EndpointResolverParams"),
|
||||
"ResolveEndpoint" to endpointRtApi.resolve("ResolveEndpoint"),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,7 +104,8 @@ class AuthSchemeLister : RuleValueVisitor<Set<String>> {
|
|||
}
|
||||
|
||||
override fun visitEndpointRule(endpoint: Endpoint): Set<String> {
|
||||
return endpoint.properties.getOrDefault(Identifier.of("authSchemes"), Literal.tupleLiteral(listOf())).asTupleLiteral()
|
||||
return endpoint.properties.getOrDefault(Identifier.of("authSchemes"), Literal.tupleLiteral(listOf()))
|
||||
.asTupleLiteral()
|
||||
.orNull()?.let {
|
||||
it.map { authScheme ->
|
||||
authScheme.asRecordLiteral().get()[Identifier.of("name")]!!.asStringLiteral().get().expectLiteral()
|
||||
|
|
|
@ -18,6 +18,7 @@ import software.amazon.smithy.rulesengine.language.syntax.rule.RuleValueVisitor
|
|||
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Context
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointTypesGenerator
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Types
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.endpointsLib
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.memberName
|
||||
|
@ -36,8 +37,10 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
|
|||
import software.amazon.smithy.rust.codegen.core.rustlang.toType
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.writable
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
|
||||
import software.amazon.smithy.rust.codegen.core.util.dq
|
||||
import software.amazon.smithy.rust.codegen.core.util.orNull
|
||||
import software.amazon.smithy.rust.codegen.core.util.serviceNameOrDefault
|
||||
|
||||
abstract class CustomRuntimeFunction {
|
||||
abstract val id: String
|
||||
|
@ -128,9 +131,13 @@ internal class EndpointResolverGenerator(
|
|||
private val registry: FunctionRegistry = FunctionRegistry(stdlib)
|
||||
private val types = Types(runtimeConfig)
|
||||
private val codegenScope = arrayOf(
|
||||
"BoxError" to RuntimeType.boxError(runtimeConfig),
|
||||
"endpoint" to types.smithyHttpEndpointModule,
|
||||
"SmithyEndpoint" to types.smithyEndpoint,
|
||||
"EndpointFuture" to types.endpointFuture,
|
||||
"ResolveEndpointError" to types.resolveEndpointError,
|
||||
"EndpointError" to types.resolveEndpointError,
|
||||
"ServiceSpecificEndpointResolver" to codegenContext.serviceSpecificEndpointResolver(),
|
||||
"DiagnosticCollector" to endpointsLib("diagnostic").toType().resolve("DiagnosticCollector"),
|
||||
)
|
||||
|
||||
|
@ -183,13 +190,17 @@ internal class EndpointResolverGenerator(
|
|||
pub fn new() -> Self {
|
||||
Self { #{custom_fields_init:W} }
|
||||
}
|
||||
|
||||
fn resolve_endpoint(&self, params: &#{Params}) -> Result<#{SmithyEndpoint}, #{BoxError}> {
|
||||
let mut diagnostic_collector = #{DiagnosticCollector}::new();
|
||||
Ok(#{resolver_fn}(params, &mut diagnostic_collector, #{additional_args})
|
||||
.map_err(|err|err.with_source(diagnostic_collector.take_last_error()))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl #{endpoint}::ResolveEndpoint<#{Params}> for DefaultResolver {
|
||||
fn resolve_endpoint(&self, params: &Params) -> #{endpoint}::Result {
|
||||
let mut diagnostic_collector = #{DiagnosticCollector}::new();
|
||||
#{resolver_fn}(params, &mut diagnostic_collector, #{additional_args})
|
||||
.map_err(|err|err.with_source(diagnostic_collector.take_last_error()))
|
||||
impl #{ServiceSpecificEndpointResolver} for DefaultResolver {
|
||||
fn resolve_endpoint(&self, params: &#{Params}) -> #{EndpointFuture} {
|
||||
#{EndpointFuture}::ready(self.resolve_endpoint(params))
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
@ -368,3 +379,46 @@ internal class EndpointResolverGenerator(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ClientCodegenContext.serviceSpecificEndpointResolver(): RuntimeType {
|
||||
val generator = EndpointTypesGenerator.fromContext(this)
|
||||
return RuntimeType.forInlineFun("ResolveEndpoint", ClientRustModule.Config.endpoint) {
|
||||
val ctx = arrayOf(*preludeScope, "Params" to generator.paramsStruct(), *Types(runtimeConfig).toArray(), "Debug" to RuntimeType.Debug)
|
||||
rustTemplate(
|
||||
"""
|
||||
/// Endpoint resolver trait specific to ${serviceShape.serviceNameOrDefault("this service")}
|
||||
pub trait ResolveEndpoint: #{Send} + #{Sync} + #{Debug} {
|
||||
/// Resolve an endpoint with the given parameters
|
||||
fn resolve_endpoint<'a>(&'a self, params: &'a #{Params}) -> #{EndpointFuture}<'a>;
|
||||
|
||||
/// Convert this service-specific resolver into a `SharedEndpointResolver`
|
||||
///
|
||||
/// The resulting resolver will downcast `EndpointResolverParams` into `#{Params}`.
|
||||
fn into_shared_resolver(self) -> #{SharedEndpointResolver}
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
#{SharedEndpointResolver}::new(DowncastParams(self))
|
||||
}
|
||||
}
|
||||
|
||||
##[derive(Debug)]
|
||||
struct DowncastParams<T>(T);
|
||||
impl<T> #{ResolveEndpoint} for DowncastParams<T>
|
||||
where
|
||||
T: ResolveEndpoint,
|
||||
{
|
||||
fn resolve_endpoint<'a>(&'a self, params: &'a #{EndpointResolverParams}) -> #{EndpointFuture}<'a> {
|
||||
let ep = match params.get::<#{Params}>() {
|
||||
Some(params) => self.0.resolve_endpoint(params),
|
||||
None => #{EndpointFuture}::ready(Err("params of expected type was not present".into())),
|
||||
};
|
||||
ep
|
||||
}
|
||||
}
|
||||
|
||||
""",
|
||||
*ctx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters
|
|||
import software.amazon.smithy.rulesengine.traits.EndpointTestCase
|
||||
import software.amazon.smithy.rulesengine.traits.ExpectedEndpoint
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Types
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.ClientInstantiator
|
||||
|
@ -38,16 +37,12 @@ internal class EndpointTestGenerator(
|
|||
private val paramsType: RuntimeType,
|
||||
private val resolverType: RuntimeType,
|
||||
private val params: Parameters,
|
||||
private val endpointCustomizations: List<EndpointCustomization>,
|
||||
codegenContext: ClientCodegenContext,
|
||||
) {
|
||||
private val runtimeConfig = codegenContext.runtimeConfig
|
||||
private val serviceShape = codegenContext.serviceShape
|
||||
private val model = codegenContext.model
|
||||
private val types = Types(runtimeConfig)
|
||||
private val codegenScope = arrayOf(
|
||||
"Endpoint" to types.smithyEndpoint,
|
||||
"ResolveEndpoint" to types.resolveEndpoint,
|
||||
"Error" to types.resolveEndpointError,
|
||||
"Document" to RuntimeType.document(runtimeConfig),
|
||||
"HashMap" to RuntimeType.HashMap,
|
||||
|
@ -67,7 +62,6 @@ internal class EndpointTestGenerator(
|
|||
#{docs:W}
|
||||
##[test]
|
||||
fn test_$id() {
|
||||
use #{ResolveEndpoint};
|
||||
let params = #{params:W};
|
||||
let resolver = #{resolver}::new();
|
||||
let endpoint = resolver.resolve_endpoint(¶ms);
|
||||
|
|
|
@ -7,6 +7,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators
|
|||
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Types
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
|
||||
|
@ -49,11 +50,11 @@ class ClientRuntimeTypesReExportGenerator(
|
|||
rustCrate.withModule(ClientRustModule.Config.endpoint) {
|
||||
rustTemplate(
|
||||
"""
|
||||
pub use #{ResolveEndpoint};
|
||||
pub use #{SharedEndpointResolver};
|
||||
pub use #{EndpointFuture};
|
||||
pub use #{Endpoint};
|
||||
""",
|
||||
"ResolveEndpoint" to RuntimeType.smithyHttp(rc).resolve("endpoint::ResolveEndpoint"),
|
||||
"SharedEndpointResolver" to RuntimeType.smithyHttp(rc).resolve("endpoint::SharedEndpointResolver"),
|
||||
*Types(rc).toArray(),
|
||||
)
|
||||
}
|
||||
rustCrate.withModule(ClientRustModule.Config.retry) {
|
||||
|
|
|
@ -208,7 +208,7 @@ class DefaultProtocolTestGenerator(
|
|||
rustTemplate(
|
||||
"""
|
||||
let (http_client, request_receiver) = #{capture_request}(None);
|
||||
let config_builder = #{config}::Config::builder().with_test_defaults().endpoint_resolver($host);
|
||||
let config_builder = #{config}::Config::builder().with_test_defaults().endpoint_url($host);
|
||||
#{customParams}
|
||||
|
||||
""",
|
||||
|
|
|
@ -49,7 +49,7 @@ class HttpAuthDecoratorTest {
|
|||
|
||||
let config = $moduleName::Config::builder()
|
||||
.api_key(Token::new("some-api-key", None))
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
@ -81,7 +81,7 @@ class HttpAuthDecoratorTest {
|
|||
|
||||
let config = $moduleName::Config::builder()
|
||||
.basic_auth_login(Login::new("some-user", "some-pass", None))
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
@ -121,7 +121,7 @@ class HttpAuthDecoratorTest {
|
|||
|
||||
let config = $moduleName::Config::builder()
|
||||
.api_key(Token::new("some-api-key", None))
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
@ -162,7 +162,7 @@ class HttpAuthDecoratorTest {
|
|||
|
||||
let config = $moduleName::Config::builder()
|
||||
.api_key(Token::new("some-api-key", None))
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
@ -203,7 +203,7 @@ class HttpAuthDecoratorTest {
|
|||
|
||||
let config = $moduleName::Config::builder()
|
||||
.basic_auth_login(Login::new("some-user", "some-pass", None))
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
@ -244,7 +244,7 @@ class HttpAuthDecoratorTest {
|
|||
|
||||
let config = $moduleName::Config::builder()
|
||||
.bearer_token(Token::new("some-token", None))
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
@ -281,7 +281,7 @@ class HttpAuthDecoratorTest {
|
|||
);
|
||||
|
||||
let config = $moduleName::Config::builder()
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
|
|
@ -83,7 +83,7 @@ class MetadataCustomizationTest {
|
|||
|
||||
let (http_client, _captured_request) = #{capture_request}(#{None});
|
||||
let client_config = crate::config::Config::builder()
|
||||
.endpoint_resolver("http://localhost:1234/")
|
||||
.endpoint_url("http://localhost:1234/")
|
||||
.http_client(http_client)
|
||||
.build();
|
||||
let client = crate::client::Client::from_conf(client_config);
|
||||
|
|
|
@ -63,7 +63,7 @@ class SensitiveOutputDecoratorTest {
|
|||
));
|
||||
|
||||
let config = $moduleName::Config::builder()
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(http_client.clone())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
|
|
@ -56,7 +56,7 @@ internal class ConfigOverrideRuntimePluginGeneratorTest {
|
|||
let (http_client, req) = #{capture_request}(None);
|
||||
let client_config = crate::config::Config::builder().http_client(http_client).build();
|
||||
let config_override =
|
||||
crate::config::Config::builder().endpoint_resolver(expected_url);
|
||||
crate::config::Config::builder().endpoint_url(expected_url);
|
||||
let client = crate::Client::from_conf(client_config);
|
||||
let _ = dbg!(client.say_hello().customize().config_override(config_override).send().await);
|
||||
assert_eq!("http://localhost:1234/", req.expect_request().uri());
|
||||
|
@ -86,7 +86,7 @@ internal class ConfigOverrideRuntimePluginGeneratorTest {
|
|||
let (http_client, captured_request) = #{capture_request}(#{None});
|
||||
let expected_url = "http://localhost:1234/";
|
||||
let client_config = crate::config::Config::builder()
|
||||
.endpoint_resolver(expected_url)
|
||||
.endpoint_url(expected_url)
|
||||
.http_client(#{NeverClient}::new())
|
||||
.build();
|
||||
let client = crate::client::Client::from_conf(client_config.clone());
|
||||
|
|
|
@ -45,7 +45,7 @@ class CustomizableOperationGeneratorTest {
|
|||
fn test() {
|
||||
let config = $moduleName::Config::builder()
|
||||
.http_client(#{NeverClient}::new())
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
check_send_and_sync(client.say_hello().customize());
|
||||
|
|
|
@ -77,7 +77,7 @@ class FluentClientGeneratorTest {
|
|||
##[test]
|
||||
fn test() {
|
||||
let config = $moduleName::Config::builder()
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(#{NeverClient}::new())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
@ -101,7 +101,7 @@ class FluentClientGeneratorTest {
|
|||
##[test]
|
||||
fn test() {
|
||||
let config = $moduleName::Config::builder()
|
||||
.endpoint_resolver("http://localhost:1234")
|
||||
.endpoint_url("http://localhost:1234")
|
||||
.http_client(#{NeverClient}::new())
|
||||
.build();
|
||||
let client = $moduleName::Client::from_conf(config);
|
||||
|
|
|
@ -5,95 +5,23 @@
|
|||
|
||||
//! Code for resolving an endpoint (URI) that a request should be sent to
|
||||
|
||||
use crate::endpoint::error::InvalidEndpointError;
|
||||
use aws_smithy_types::config_bag::{Storable, StoreReplace};
|
||||
use http::uri::{Authority, Uri};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fmt::Debug;
|
||||
use std::result::Result as StdResult;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use http::uri::{Authority, Uri};
|
||||
|
||||
use aws_smithy_types::config_bag::{Storable, StoreReplace};
|
||||
pub use error::ResolveEndpointError;
|
||||
|
||||
use crate::endpoint::error::InvalidEndpointError;
|
||||
|
||||
pub mod error;
|
||||
|
||||
pub use error::ResolveEndpointError;
|
||||
|
||||
/// An endpoint-resolution-specific Result. Contains either an [`Endpoint`](aws_smithy_types::endpoint::Endpoint) or a [`ResolveEndpointError`].
|
||||
pub type Result = std::result::Result<aws_smithy_types::endpoint::Endpoint, ResolveEndpointError>;
|
||||
|
||||
/// Implementors of this trait can resolve an endpoint that will be applied to a request.
|
||||
pub trait ResolveEndpoint<Params>: Send + Sync {
|
||||
/// Given some endpoint parameters, resolve an endpoint or return an error when resolution is
|
||||
/// impossible.
|
||||
fn resolve_endpoint(&self, params: &Params) -> Result;
|
||||
}
|
||||
|
||||
impl<T> ResolveEndpoint<T> for &'static str {
|
||||
fn resolve_endpoint(&self, _params: &T) -> Result {
|
||||
Ok(aws_smithy_types::endpoint::Endpoint::builder()
|
||||
.url(*self)
|
||||
.build())
|
||||
}
|
||||
}
|
||||
|
||||
/// Endpoint Resolver wrapper that may be shared
|
||||
#[derive(Clone)]
|
||||
pub struct SharedEndpointResolver<T>(Arc<dyn ResolveEndpoint<T>>);
|
||||
|
||||
impl<T> Debug for SharedEndpointResolver<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SharedEndpointResolver").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SharedEndpointResolver<T> {
|
||||
/// Create a new `SharedEndpointResolver` from `ResolveEndpoint`
|
||||
pub fn new(resolve_endpoint: impl ResolveEndpoint<T> + 'static) -> Self {
|
||||
Self(Arc::new(resolve_endpoint))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<dyn ResolveEndpoint<T>> for SharedEndpointResolver<T> {
|
||||
fn as_ref(&self) -> &(dyn ResolveEndpoint<T> + 'static) {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Arc<dyn ResolveEndpoint<T>>> for SharedEndpointResolver<T> {
|
||||
fn from(resolve_endpoint: Arc<dyn ResolveEndpoint<T>>) -> Self {
|
||||
SharedEndpointResolver(resolve_endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ResolveEndpoint<T> for SharedEndpointResolver<T> {
|
||||
fn resolve_endpoint(&self, params: &T) -> Result {
|
||||
self.0.resolve_endpoint(params)
|
||||
}
|
||||
}
|
||||
|
||||
/// API Endpoint
|
||||
///
|
||||
/// This implements an API endpoint as specified in the
|
||||
/// [Smithy Endpoint Specification](https://awslabs.github.io/smithy/1.0/spec/core/endpoint-traits.html)
|
||||
#[derive(Clone, Debug)]
|
||||
#[deprecated(note = "Use `.endpoint_url(...)` directly instead")]
|
||||
pub struct Endpoint {
|
||||
uri: http::Uri,
|
||||
|
||||
/// If true, endpointPrefix does ignored when setting the endpoint on a request
|
||||
immutable: bool,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
/// This allows customers that use `Endpoint` to override the endpoint to continue to do so
|
||||
impl<T> ResolveEndpoint<T> for Endpoint {
|
||||
fn resolve_endpoint(&self, _params: &T) -> Result {
|
||||
Ok(aws_smithy_types::endpoint::Endpoint::builder()
|
||||
.url(self.uri.to_string())
|
||||
.build())
|
||||
}
|
||||
}
|
||||
|
||||
/// A special type that adds support for services that have special URL-prefixing rules.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct EndpointPrefix(String);
|
||||
|
@ -156,95 +84,6 @@ pub fn apply_endpoint(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl Endpoint {
|
||||
/// Create a new endpoint from a URI
|
||||
///
|
||||
/// Certain services will augment the endpoint with additional metadata. For example,
|
||||
/// S3 can prefix the host with the bucket name. If your endpoint does not support this,
|
||||
/// (for example, when communicating with localhost), use [`Endpoint::immutable`].
|
||||
pub fn mutable_uri(uri: Uri) -> StdResult<Self, InvalidEndpointError> {
|
||||
Ok(Endpoint {
|
||||
uri: Self::validate_endpoint(uri)?,
|
||||
immutable: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new endpoint from a URI string
|
||||
///
|
||||
/// Certain services will augment the endpoint with additional metadata. For example,
|
||||
/// S3 can prefix the host with the bucket name. If your endpoint does not support this,
|
||||
/// (for example, when communicating with localhost), use [`Endpoint::immutable`].
|
||||
pub fn mutable(uri: impl AsRef<str>) -> StdResult<Self, InvalidEndpointError> {
|
||||
Self::mutable_uri(
|
||||
Uri::try_from(uri.as_ref()).map_err(InvalidEndpointError::failed_to_construct_uri)?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the URI of this endpoint
|
||||
pub fn uri(&self) -> &Uri {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
/// Create a new immutable endpoint from a URI
|
||||
///
|
||||
/// ```rust
|
||||
/// # use aws_smithy_http::endpoint::Endpoint;
|
||||
/// use http::Uri;
|
||||
/// let uri = Uri::from_static("http://localhost:8000");
|
||||
/// let endpoint = Endpoint::immutable_uri(uri);
|
||||
/// ```
|
||||
///
|
||||
/// Certain services will augment the endpoint with additional metadata. For example,
|
||||
/// S3 can prefix the host with the bucket name. This constructor creates an endpoint which will
|
||||
/// ignore those mutations. If you want an endpoint which will obey mutation requests, use
|
||||
/// [`Endpoint::mutable`] instead.
|
||||
pub fn immutable_uri(uri: Uri) -> StdResult<Self, InvalidEndpointError> {
|
||||
Ok(Endpoint {
|
||||
uri: Self::validate_endpoint(uri)?,
|
||||
immutable: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new immutable endpoint from a URI string
|
||||
///
|
||||
/// ```rust
|
||||
/// # use aws_smithy_http::endpoint::Endpoint;
|
||||
/// let endpoint = Endpoint::immutable("http://localhost:8000");
|
||||
/// ```
|
||||
///
|
||||
/// Certain services will augment the endpoint with additional metadata. For example,
|
||||
/// S3 can prefix the host with the bucket name. This constructor creates an endpoint which will
|
||||
/// ignore those mutations. If you want an endpoint which will obey mutation requests, use
|
||||
/// [`Endpoint::mutable`] instead.
|
||||
pub fn immutable(uri: impl AsRef<str>) -> StdResult<Self, InvalidEndpointError> {
|
||||
Self::immutable_uri(
|
||||
Uri::try_from(uri.as_ref()).map_err(InvalidEndpointError::failed_to_construct_uri)?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets the endpoint on `uri`, potentially applying the specified `prefix` in the process.
|
||||
pub fn set_endpoint(
|
||||
&self,
|
||||
uri: &mut http::Uri,
|
||||
prefix: Option<&EndpointPrefix>,
|
||||
) -> StdResult<(), InvalidEndpointError> {
|
||||
let prefix = match self.immutable {
|
||||
true => None,
|
||||
false => prefix,
|
||||
};
|
||||
apply_endpoint(uri, &self.uri, prefix)
|
||||
}
|
||||
|
||||
fn validate_endpoint(endpoint: Uri) -> StdResult<Uri, InvalidEndpointError> {
|
||||
if endpoint.scheme().is_none() {
|
||||
Err(InvalidEndpointError::endpoint_must_have_scheme())
|
||||
} else {
|
||||
Ok(endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_paths<'a>(endpoint: &'a Uri, uri: &'a Uri) -> Cow<'a, str> {
|
||||
if let Some(query) = endpoint.path_and_query().and_then(|pq| pq.query()) {
|
||||
tracing::warn!(query = %query, "query specified in endpoint will be ignored during endpoint resolution");
|
||||
|
@ -261,103 +100,3 @@ fn merge_paths<'a>(endpoint: &'a Uri, uri: &'a Uri) -> Cow<'a, str> {
|
|||
Cow::Owned(format!("{}/{}", ep_no_slash, uri_path_no_slash))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(deprecated)]
|
||||
mod test {
|
||||
use crate::endpoint::error::{InvalidEndpointError, InvalidEndpointErrorKind};
|
||||
use crate::endpoint::{Endpoint, EndpointPrefix};
|
||||
use http::Uri;
|
||||
|
||||
#[test]
|
||||
fn prefix_endpoint() {
|
||||
let ep = Endpoint::mutable("https://us-east-1.dynamo.amazonaws.com").unwrap();
|
||||
let mut uri = Uri::from_static("/list_tables?k=v");
|
||||
ep.set_endpoint(
|
||||
&mut uri,
|
||||
Some(&EndpointPrefix::new("subregion.").expect("valid prefix")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
uri,
|
||||
Uri::from_static("https://subregion.us-east-1.dynamo.amazonaws.com/list_tables?k=v")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_endpoint_custom_port() {
|
||||
let ep = Endpoint::mutable("https://us-east-1.dynamo.amazonaws.com:6443").unwrap();
|
||||
let mut uri = Uri::from_static("/list_tables?k=v");
|
||||
ep.set_endpoint(
|
||||
&mut uri,
|
||||
Some(&EndpointPrefix::new("subregion.").expect("valid prefix")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
uri,
|
||||
Uri::from_static(
|
||||
"https://subregion.us-east-1.dynamo.amazonaws.com:6443/list_tables?k=v"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_immutable_endpoint() {
|
||||
let ep = Endpoint::immutable("https://us-east-1.dynamo.amazonaws.com").unwrap();
|
||||
let mut uri = Uri::from_static("/list_tables?k=v");
|
||||
ep.set_endpoint(
|
||||
&mut uri,
|
||||
Some(&EndpointPrefix::new("subregion.").expect("valid prefix")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
uri,
|
||||
Uri::from_static("https://us-east-1.dynamo.amazonaws.com/list_tables?k=v")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn endpoint_with_path() {
|
||||
for uri in &[
|
||||
// check that trailing slashes are properly normalized
|
||||
"https://us-east-1.dynamo.amazonaws.com/private",
|
||||
"https://us-east-1.dynamo.amazonaws.com/private/",
|
||||
] {
|
||||
let ep = Endpoint::immutable(uri).unwrap();
|
||||
let mut uri = Uri::from_static("/list_tables?k=v");
|
||||
ep.set_endpoint(
|
||||
&mut uri,
|
||||
Some(&EndpointPrefix::new("subregion.").expect("valid prefix")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
uri,
|
||||
Uri::from_static("https://us-east-1.dynamo.amazonaws.com/private/list_tables?k=v")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_endpoint_empty_path() {
|
||||
let ep = Endpoint::immutable("http://localhost:8000").unwrap();
|
||||
let mut uri = Uri::from_static("/");
|
||||
ep.set_endpoint(&mut uri, None).unwrap();
|
||||
assert_eq!(uri, Uri::from_static("http://localhost:8000/"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn endpoint_construction_missing_scheme() {
|
||||
assert!(matches!(
|
||||
Endpoint::mutable("localhost:8000"),
|
||||
Err(InvalidEndpointError {
|
||||
kind: InvalidEndpointErrorKind::EndpointMustHaveScheme
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
Endpoint::immutable("localhost:8000"),
|
||||
Err(InvalidEndpointError {
|
||||
kind: InvalidEndpointErrorKind::EndpointMustHaveScheme
|
||||
})
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
use aws_smithy_http::endpoint::error::ResolveEndpointError;
|
||||
use aws_smithy_http::endpoint::EndpointPrefix;
|
||||
use aws_smithy_http::endpoint::SharedEndpointResolver;
|
||||
use aws_smithy_runtime_api::box_error::BoxError;
|
||||
use aws_smithy_runtime_api::client::endpoint::{
|
||||
EndpointFuture, EndpointResolverParams, ResolveEndpoint,
|
||||
|
@ -13,7 +12,7 @@ use aws_smithy_runtime_api::client::endpoint::{
|
|||
use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext;
|
||||
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
|
||||
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
|
||||
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
|
||||
use aws_smithy_types::config_bag::ConfigBag;
|
||||
use aws_smithy_types::endpoint::Endpoint;
|
||||
use http::header::HeaderName;
|
||||
use http::uri::PathAndQuery;
|
||||
|
@ -70,50 +69,6 @@ impl From<StaticUriEndpointResolverParams> for EndpointResolverParams {
|
|||
}
|
||||
}
|
||||
|
||||
/// Default implementation of [`ResolveEndpoint`].
|
||||
///
|
||||
/// This default endpoint resolver implements the `ResolveEndpoint` trait by
|
||||
/// converting the type-erased [`EndpointResolverParams`] into the concrete
|
||||
/// endpoint params for the service. It then delegates endpoint resolution
|
||||
/// to an underlying resolver that is aware of the concrete type.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DefaultEndpointResolver<Params> {
|
||||
inner: SharedEndpointResolver<Params>,
|
||||
}
|
||||
|
||||
impl<Params> Storable for DefaultEndpointResolver<Params>
|
||||
where
|
||||
Params: Debug + Send + Sync + 'static,
|
||||
{
|
||||
type Storer = StoreReplace<Self>;
|
||||
}
|
||||
|
||||
impl<Params> DefaultEndpointResolver<Params> {
|
||||
/// Creates a new `DefaultEndpointResolver`.
|
||||
pub fn new(resolve_endpoint: SharedEndpointResolver<Params>) -> Self {
|
||||
Self {
|
||||
inner: resolve_endpoint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Params> ResolveEndpoint for DefaultEndpointResolver<Params>
|
||||
where
|
||||
Params: Debug + Send + Sync + 'static,
|
||||
{
|
||||
fn resolve_endpoint<'a>(&'a self, params: &'a EndpointResolverParams) -> EndpointFuture<'a> {
|
||||
use aws_smithy_http::endpoint::ResolveEndpoint as _;
|
||||
let ep = match params.get::<Params>() {
|
||||
Some(params) => self.inner.resolve_endpoint(params).map_err(Box::new),
|
||||
None => Err(Box::new(ResolveEndpointError::message(
|
||||
"params of expected type was not present",
|
||||
))),
|
||||
}
|
||||
.map_err(|e| e as _);
|
||||
EndpointFuture::ready(ep)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn orchestrate_endpoint(
|
||||
ctx: &mut InterceptorContext,
|
||||
runtime_components: &RuntimeComponents,
|
||||
|
|
Loading…
Reference in New Issue