diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4AuthDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4AuthDecorator.kt index a0cba8142b..751788df29 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4AuthDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SigV4AuthDecorator.kt @@ -8,6 +8,7 @@ package software.amazon.smithy.rustsdk import software.amazon.smithy.aws.traits.auth.SigV4Trait import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait import software.amazon.smithy.model.knowledge.ServiceIndex +import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.traits.OptionalAuthTrait import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator @@ -38,6 +39,7 @@ class SigV4AuthDecorator : ClientCodegenDecorator { override fun operationRuntimePluginCustomizations( codegenContext: ClientCodegenContext, + operation: OperationShape, baseCustomizations: List, ): List = baseCustomizations.letIf(codegenContext.settings.codegenConfig.enableNewSmithyRuntime) { diff --git a/aws/sra-test/integration-tests/aws-sdk-s3/Cargo.toml b/aws/sra-test/integration-tests/aws-sdk-s3/Cargo.toml index 65cfa7528d..51611304b2 100644 --- a/aws/sra-test/integration-tests/aws-sdk-s3/Cargo.toml +++ b/aws/sra-test/integration-tests/aws-sdk-s3/Cargo.toml @@ -6,18 +6,18 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -aws-credential-types = { path = "../../../rust-runtime/aws-credential-types", features = ["test-util"] } -aws-http = { path = "../../../rust-runtime/aws-http" } -aws-runtime = { path = "../../../rust-runtime/aws-runtime" } -aws-sdk-s3 = { path = "../../build/sdk/aws-sdk-s3", features = ["test-util"] } -aws-sigv4 = { path = "../../../rust-runtime/aws-sigv4" } -aws-types = { path = "../../../rust-runtime/aws-types" } -aws-smithy-async = { path = "../../../../rust-runtime/aws-smithy-async", features = ["rt-tokio"] } -aws-smithy-client = { path = "../../../../rust-runtime/aws-smithy-client", features = ["test-util"] } -aws-smithy-types = { path = "../../../../rust-runtime/aws-smithy-types" } -aws-smithy-http = { path = "../../../../rust-runtime/aws-smithy-http" } -aws-smithy-runtime = { path = "../../../../rust-runtime/aws-smithy-runtime", features = ["test-util"] } -aws-smithy-runtime-api = { path = "../../../../rust-runtime/aws-smithy-runtime-api" } +aws-credential-types = { path = "../../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } +aws-http = { path = "../../../sdk/build/aws-sdk/sdk/aws-http" } +aws-runtime = { path = "../../../sdk/build/aws-sdk/sdk/aws-runtime" } +aws-sdk-s3 = { path = "../../../sdk/build/aws-sdk/sdk/s3/", features = ["test-util"] } +aws-sigv4 = { path = "../../../sdk/build/aws-sdk/sdk/aws-sigv4" } +aws-types = { path = "../../../sdk/build/aws-sdk/sdk/aws-types" } +aws-smithy-async = { path = "../../../sdk/build/aws-sdk/sdk/aws-smithy-async", features = ["rt-tokio"] } +aws-smithy-client = { path = "../../../sdk/build/aws-sdk/sdk/aws-smithy-client", features = ["test-util"] } +aws-smithy-types = { path = "../../../sdk/build/aws-sdk/sdk/aws-smithy-types" } +aws-smithy-http = { path = "../../../sdk/build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } +aws-smithy-runtime-api = { path = "../../../sdk/build/aws-sdk/sdk/aws-smithy-runtime-api" } tokio = { version = "1.23.1", features = ["macros", "test-util", "rt-multi-thread"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.15", features = ["env-filter", "json"] } diff --git a/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs b/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs index 258177c3a4..da778c3737 100644 --- a/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs +++ b/aws/sra-test/integration-tests/aws-sdk-s3/tests/sra_test.rs @@ -17,8 +17,10 @@ use aws_sdk_s3::primitives::SdkBody; use aws_smithy_client::erase::DynConnector; use aws_smithy_client::test_connection::TestConnection; use aws_smithy_runtime::client::connections::adapter::DynConnectorAdapter; -use aws_smithy_runtime_api::client::endpoints::StaticUriEndpointResolver; -use aws_smithy_runtime_api::client::interceptors::{Interceptor, InterceptorContext, Interceptors}; +use aws_smithy_runtime_api::client::endpoints::DefaultEndpointResolver; +use aws_smithy_runtime_api::client::interceptors::{ + Interceptor, InterceptorContext, InterceptorError, Interceptors, +}; use aws_smithy_runtime_api::client::orchestrator::{ BoxError, ConfigBagAccessors, Connection, HttpRequest, HttpResponse, TraceProbe, }; @@ -27,7 +29,6 @@ use aws_smithy_runtime_api::config_bag::ConfigBag; use aws_smithy_runtime_api::type_erasure::TypedBox; use aws_types::region::SigningRegion; use aws_types::SigningService; -use http::Uri; use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; @@ -108,9 +109,16 @@ async fn sra_manual_test() { ), ); - cfg.set_endpoint_resolver(StaticUriEndpointResolver::uri(Uri::from_static( - "https://test-bucket.s3.us-east-1.amazonaws.com/", - ))); + cfg.set_endpoint_resolver(DefaultEndpointResolver::new( + aws_smithy_http::endpoint::SharedEndpointResolver::new( + aws_sdk_s3::endpoint::DefaultResolver::new(), + ), + )); + + let params_builder = aws_sdk_s3::endpoint::Params::builder() + .set_region(Some("us-east-1".to_owned())) + .set_endpoint(Some("https://s3.us-east-1.amazonaws.com/".to_owned())); + cfg.put(params_builder); cfg.set_retry_strategy( aws_smithy_runtime_api::client::retries::NeverRetryStrategy::new(), @@ -162,6 +170,77 @@ async fn sra_manual_test() { } } + // This is a temporary operation runtime plugin until EndpointParamsInterceptor and + // EndpointParamsFinalizerInterceptor have been fully implemented, in which case + // `.with_operation_plugin(ManualOperationRuntimePlugin)` can be removed. + struct ManualOperationRuntimePlugin; + + impl RuntimePlugin for ManualOperationRuntimePlugin { + fn configure(&self, cfg: &mut ConfigBag) -> Result<(), BoxError> { + #[derive(Debug)] + struct ListObjectsV2EndpointParamsInterceptor; + impl Interceptor for ListObjectsV2EndpointParamsInterceptor { + fn read_before_execution( + &self, + context: &InterceptorContext, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + let input = context.input()?; + let input = input + .downcast_ref::() + .ok_or_else(|| InterceptorError::invalid_input_access())?; + let mut params_builder = cfg + .get::() + .ok_or(InterceptorError::read_before_execution( + "missing endpoint params builder", + ))? + .clone(); + params_builder = params_builder.set_bucket(input.bucket.clone()); + cfg.put(params_builder); + + Ok(()) + } + } + + #[derive(Debug)] + struct ListObjectsV2EndpointParamsFinalizerInterceptor; + impl Interceptor for ListObjectsV2EndpointParamsFinalizerInterceptor { + fn read_before_execution( + &self, + _context: &InterceptorContext, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + let params_builder = cfg + .get::() + .ok_or(InterceptorError::read_before_execution( + "missing endpoint params builder", + ))? + .clone(); + let params = params_builder + .build() + .map_err(InterceptorError::read_before_execution)?; + cfg.put( + aws_smithy_runtime_api::client::orchestrator::EndpointResolverParams::new( + params, + ), + ); + + Ok(()) + } + } + + cfg.get::>() + .expect("interceptors set") + .register_operation_interceptor( + Arc::new(ListObjectsV2EndpointParamsInterceptor) as _ + ) + .register_operation_interceptor(Arc::new( + ListObjectsV2EndpointParamsFinalizerInterceptor, + ) as _); + Ok(()) + } + } + let conn = TestConnection::new(vec![( http::Request::builder() .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20210618/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=ae78f74d26b6b0c3a403d9e8cc7ec3829d6264a2b33db672bf2b151bbb901786") @@ -187,7 +266,8 @@ async fn sra_manual_test() { let runtime_plugins = aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins::new() .with_client_plugin(ManualServiceRuntimePlugin(conn.clone())) - .with_operation_plugin(aws_sdk_s3::operation::list_objects_v2::ListObjectsV2::new()); + .with_operation_plugin(aws_sdk_s3::operation::list_objects_v2::ListObjectsV2::new()) + .with_operation_plugin(ManualOperationRuntimePlugin); let input = ListObjectsV2Input::builder() .bucket("test-bucket") diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt index f8852a700d..2e72185f9d 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt @@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegen import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.customize.NoOpEventStreamSigningDecorator import software.amazon.smithy.rust.codegen.client.smithy.customize.RequiredCustomizations +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointParamsDecorator import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointsDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientDecorator import software.amazon.smithy.rust.codegen.client.testutil.ClientDecoratableBuildPlugin @@ -58,6 +59,7 @@ class RustClientCodegenPlugin : ClientDecoratableBuildPlugin() { RequiredCustomizations(), FluentClientDecorator(), EndpointsDecorator(), + EndpointParamsDecorator(), NoOpEventStreamSigningDecorator(), ApiKeyAuthDecorator(), *decorator, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/EndpointPrefixGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/EndpointPrefixGenerator.kt index 110ca580c6..2fe90239de 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/EndpointPrefixGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/EndpointPrefixGenerator.kt @@ -7,16 +7,16 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.traits.EndpointTrait +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.generators.EndpointTraitBindings 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.withBlock import software.amazon.smithy.rust.codegen.core.rustlang.writable -import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection -class EndpointPrefixGenerator(private val codegenContext: CodegenContext, private val shape: OperationShape) : +class EndpointPrefixGenerator(private val codegenContext: ClientCodegenContext, private val shape: OperationShape) : OperationCustomization() { override fun section(section: OperationSection): Writable = when (section) { is OperationSection.MutateRequest -> writable { @@ -29,11 +29,16 @@ class EndpointPrefixGenerator(private val codegenContext: CodegenContext, privat epTrait, ) withBlock("let endpoint_prefix = ", "?;") { - endpointTraitBindings.render(this, "self") + endpointTraitBindings.render( + this, + "self", + codegenContext.settings.codegenConfig.enableNewSmithyRuntime, + ) } rust("request.properties_mut().insert(endpoint_prefix);") } } + else -> emptySection } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt index d7066256a7..262baad0c4 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt @@ -74,6 +74,7 @@ interface ClientCodegenDecorator : CoreCodegenDecorator { */ fun operationRuntimePluginCustomizations( codegenContext: ClientCodegenContext, + operation: OperationShape, baseCustomizations: List, ): List = baseCustomizations } @@ -135,10 +136,11 @@ open class CombinedClientCodegenDecorator(decorators: List, ): List = combineCustomizations(baseCustomizations) { decorator, customizations -> - decorator.operationRuntimePluginCustomizations(codegenContext, customizations) + decorator.operationRuntimePluginCustomizations(codegenContext, operation, customizations) } companion object { diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt index d6bb2cccc7..2d742bbbec 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt @@ -69,6 +69,7 @@ internal class EndpointConfigCustomization( /// use aws_smithy_http::endpoint; /// use $moduleUseName::endpoint::{Params as EndpointParams, DefaultResolver}; /// /// Endpoint resolver which adds a prefix to the generated endpoint + /// ##[derive(Debug)] /// struct PrefixResolver { /// base_resolver: DefaultResolver, /// prefix: String @@ -132,6 +133,7 @@ internal class EndpointConfigCustomization( RuntimeType.forInlineFun("MissingResolver", ClientRustModule.Endpoint) { rustTemplate( """ + ##[derive(Debug)] pub(crate) struct MissingResolver; impl #{ResolveEndpoint} for MissingResolver { fn resolve_endpoint(&self, _params: &T) -> #{Result} { diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointParamsDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointParamsDecorator.kt new file mode 100644 index 0000000000..ba728bcdf8 --- /dev/null +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointParamsDecorator.kt @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.endpoint + +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationRuntimePluginCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationRuntimePluginSection +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.writable +import software.amazon.smithy.rust.codegen.core.util.letIf + +/** + * Decorator that injects operation-level interceptors that configure an endpoint parameters builder + * with operation specific information, e.g. a bucket name. + * + * Whenever a setter needs to be called on the endpoint parameters builder with operation specific information, + * this decorator must be used. + */ +class EndpointParamsDecorator : ClientCodegenDecorator { + override val name: String get() = "EndpointParamsDecorator" + override val order: Byte get() = 0 + + override fun operationRuntimePluginCustomizations( + codegenContext: ClientCodegenContext, + operation: OperationShape, + baseCustomizations: List, + ): List = + baseCustomizations.letIf(codegenContext.settings.codegenConfig.enableNewSmithyRuntime) { + it + listOf(EndpointParametersRuntimePluginCustomization(codegenContext, operation)) + } +} + +private class EndpointParametersRuntimePluginCustomization( + private val codegenContext: ClientCodegenContext, + private val operation: OperationShape, +) : OperationRuntimePluginCustomization() { + override fun section(section: OperationRuntimePluginSection): Writable = writable { + val symbolProvider = codegenContext.symbolProvider + val operationName = symbolProvider.toSymbol(operation).name + if (section is OperationRuntimePluginSection.AdditionalConfig) { + section.registerInterceptor(codegenContext.runtimeConfig, this) { + rust("${operationName}EndpointParamsInterceptor") + } + // The finalizer interceptor should be registered last + section.registerInterceptor(codegenContext.runtimeConfig, this) { + rust("${operationName}EndpointParamsFinalizerInterceptor") + } + } + } +} diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt index 2f6e496e69..0906a4f6db 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt @@ -41,6 +41,7 @@ class EndpointTypesGenerator( } fun paramsStruct(): RuntimeType = EndpointParamsGenerator(params).paramsStruct() + fun paramsBuilder(): RuntimeType = EndpointParamsGenerator(params).paramsBuilder() fun defaultResolver(): RuntimeType? = rules?.let { EndpointResolverGenerator(stdlib, runtimeConfig).defaultEndpointResolver(it) } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt index 286f34443d..ff19163afc 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt @@ -118,7 +118,7 @@ internal class EndpointParamsGenerator(private val parameters: Parameters) { generateEndpointsStruct(this) } - private fun endpointsBuilder(): RuntimeType = RuntimeType.forInlineFun("ParamsBuilder", ClientRustModule.Endpoint) { + internal fun paramsBuilder(): RuntimeType = RuntimeType.forInlineFun("ParamsBuilder", ClientRustModule.Endpoint) { generateEndpointParamsBuilder(this) } @@ -182,7 +182,7 @@ internal class EndpointParamsGenerator(private val parameters: Parameters) { #{Builder}::default() } """, - "Builder" to endpointsBuilder(), + "Builder" to paramsBuilder(), ) parameters.toList().forEach { parameter -> val name = parameter.memberName() diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt new file mode 100644 index 0000000000..8343bbc9bd --- /dev/null +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt @@ -0,0 +1,147 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators + +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.traits.EndpointTrait +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointTypesGenerator +import software.amazon.smithy.rust.codegen.client.smithy.generators.EndpointTraitBindings +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +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.withBlockTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.util.inputShape + +class EndpointParamsInterceptorGenerator( + private val codegenContext: ClientCodegenContext, +) { + private val model = codegenContext.model + private val symbolProvider = codegenContext.symbolProvider + private val codegenScope = codegenContext.runtimeConfig.let { rc -> + val endpointTypesGenerator = EndpointTypesGenerator.fromContext(codegenContext) + val runtimeApi = CargoDependency.smithyRuntimeApi(rc).toType() + val interceptors = runtimeApi.resolve("client::interceptors") + val orchestrator = runtimeApi.resolve("client::orchestrator") + arrayOf( + "BoxError" to runtimeApi.resolve("client::runtime_plugin::BoxError"), + "ConfigBag" to runtimeApi.resolve("config_bag::ConfigBag"), + "EndpointResolverParams" to orchestrator.resolve("EndpointResolverParams"), + "HttpResponse" to orchestrator.resolve("HttpResponse"), + "HttpRequest" to orchestrator.resolve("HttpRequest"), + "Interceptor" to interceptors.resolve("Interceptor"), + "InterceptorContext" to interceptors.resolve("InterceptorContext"), + "InterceptorError" to interceptors.resolve("error::InterceptorError"), + "ParamsBuilder" to endpointTypesGenerator.paramsBuilder(), + ) + } + + fun render(writer: RustWriter, operationShape: OperationShape) { + val operationName = symbolProvider.toSymbol(operationShape).name + renderInterceptor( + writer, + "${operationName}EndpointParamsInterceptor", + implInterceptorBodyForEndpointParams(operationShape), + ) + renderInterceptor( + writer, "${operationName}EndpointParamsFinalizerInterceptor", + implInterceptorBodyForEndpointParamsFinalizer, + ) + } + + private fun renderInterceptor(writer: RustWriter, interceptorName: String, implBody: Writable) { + writer.rustTemplate( + """ + ##[derive(Debug)] + struct $interceptorName; + + impl #{Interceptor}<#{HttpRequest}, #{HttpResponse}> for $interceptorName { + fn read_before_execution( + &self, + context: &#{InterceptorContext}<#{HttpRequest}, #{HttpResponse}>, + cfg: &mut #{ConfigBag}, + ) -> Result<(), #{BoxError}> { + #{body:W} + } + } + """, + *codegenScope, + "body" to implBody, + ) + } + + private fun implInterceptorBodyForEndpointParams(operationShape: OperationShape): Writable = writable { + val operationInput = symbolProvider.toSymbol(operationShape.inputShape(model)) + rustTemplate( + """ + let input = context.input()?; + let _input = input + .downcast_ref::<${operationInput.name}>() + .ok_or_else(|| #{InterceptorError}::invalid_input_access())?; + let params_builder = cfg + .get::<#{ParamsBuilder}>() + .ok_or(#{InterceptorError}::read_before_execution("missing endpoint params builder"))? + .clone(); + ${"" /* TODO(EndpointResolver): Call setters on `params_builder` to update its fields by using values from `_input` */} + cfg.put(params_builder); + + #{endpoint_prefix:W} + + Ok(()) + """, + *codegenScope, + "endpoint_prefix" to endpointPrefix(operationShape), + ) + } + + private fun endpointPrefix(operationShape: OperationShape): Writable = writable { + operationShape.getTrait(EndpointTrait::class.java).map { epTrait -> + val endpointTraitBindings = EndpointTraitBindings( + codegenContext.model, + symbolProvider, + codegenContext.runtimeConfig, + operationShape, + epTrait, + ) + withBlockTemplate( + "let endpoint_prefix = ", + ".map_err(#{InterceptorError}::read_before_execution)?;", + *codegenScope, + ) { + endpointTraitBindings.render( + this, + "_input", + codegenContext.settings.codegenConfig.enableNewSmithyRuntime, + ) + } + rust("cfg.put(endpoint_prefix);") + } + } + + private val implInterceptorBodyForEndpointParamsFinalizer: Writable = writable { + rustTemplate( + """ + let _ = context; + let params_builder = cfg + .get::<#{ParamsBuilder}>() + .ok_or(#{InterceptorError}::read_before_execution("missing endpoint params builder"))? + .clone(); + let params = params_builder + .build() + .map_err(#{InterceptorError}::read_before_execution)?; + cfg.put( + #{EndpointResolverParams}::new(params) + ); + + Ok(()) + """, + *codegenScope, + ) + } +} diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt index d85282f16e..7dbede9596 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt @@ -168,7 +168,7 @@ internal class EndpointResolverGenerator(stdlib: List, ru rustTemplate( """ /// The default endpoint resolver - ##[derive(Default)] + ##[derive(Debug, Default)] pub struct DefaultResolver { #{custom_fields:W} } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingGenerator.kt index c0f17d46ad..4cc4b93347 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingGenerator.kt @@ -44,7 +44,7 @@ class EndpointTraitBindings( * * The returned expression is a `Result` */ - fun render(writer: RustWriter, input: String) { + fun render(writer: RustWriter, input: String, enableNewSmithyRuntime: Boolean) { // the Rust format pattern to make the endpoint prefix e.g. "{}.foo" val formatLiteral = endpointTrait.prefixFormatString() if (endpointTrait.hostPrefix.labels.isEmpty()) { @@ -67,12 +67,23 @@ class EndpointTraitBindings( // NOTE: this is dead code until we start respecting @required rust("let $field = &$input.$field;") } - rustTemplate( + val contents = if (enableNewSmithyRuntime) { + // TODO(enableNewSmithyRuntime): Remove the allow attribute once all places need .into method + """ + if $field.is_empty() { + ##[allow(clippy::useless_conversion)] + return Err(#{invalidFieldError:W}.into()) + } + """ + } else { """ if $field.is_empty() { return Err(#{invalidFieldError:W}) } - """, + """ + } + rustTemplate( + contents, "invalidFieldError" to OperationBuildError(runtimeConfig).invalidField( field, "$field was unset or empty but must be set as part of the endpoint prefix", diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt index fb3e9f84b4..2ad5d454a2 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/OperationRuntimePluginGenerator.kt @@ -8,8 +8,10 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.Writable 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.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.Section @@ -22,7 +24,22 @@ sealed class OperationRuntimePluginSection(name: String) : Section(name) { data class AdditionalConfig( val configBagName: String, val operationShape: OperationShape, - ) : OperationRuntimePluginSection("AdditionalConfig") + ) : OperationRuntimePluginSection("AdditionalConfig") { + fun registerInterceptor(runtimeConfig: RuntimeConfig, writer: RustWriter, interceptor: Writable) { + val smithyRuntimeApi = RuntimeType.smithyRuntimeApi(runtimeConfig) + writer.rustTemplate( + """ + $configBagName.get::<#{Interceptors}<#{HttpRequest}, #{HttpResponse}>>() + .expect("interceptors set") + .register_operation_interceptor(std::sync::Arc::new(#{interceptor}) as _); + """, + "HttpRequest" to smithyRuntimeApi.resolve("client::orchestrator::HttpRequest"), + "HttpResponse" to smithyRuntimeApi.resolve("client::orchestrator::HttpResponse"), + "Interceptors" to smithyRuntimeApi.resolve("client::interceptors::Interceptors"), + "interceptor" to interceptor, + ) + } + } } typealias OperationRuntimePluginCustomization = NamedCustomization diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt index 359bb32d1f..acb93bbfa7 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceRuntimePluginGenerator.kt @@ -6,6 +6,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.endpoint.EndpointTypesGenerator import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -72,7 +73,9 @@ typealias ServiceRuntimePluginCustomization = NamedCustomization + val http = RuntimeType.smithyHttp(rc) val runtimeApi = RuntimeType.smithyRuntimeApi(rc) val runtime = RuntimeType.smithyRuntime(rc) arrayOf( @@ -83,12 +86,15 @@ class ServiceRuntimePluginGenerator( "ConfigBagAccessors" to runtimeApi.resolve("client::orchestrator::ConfigBagAccessors"), "Connection" to runtimeApi.resolve("client::orchestrator::Connection"), "ConnectorSettings" to RuntimeType.smithyClient(rc).resolve("http_connector::ConnectorSettings"), + "DefaultEndpointResolver" to runtimeApi.resolve("client::endpoints::DefaultEndpointResolver"), "DynConnectorAdapter" to runtime.resolve("client::connections::adapter::DynConnectorAdapter"), "HttpAuthSchemes" to runtimeApi.resolve("client::orchestrator::HttpAuthSchemes"), "IdentityResolvers" to runtimeApi.resolve("client::orchestrator::IdentityResolvers"), "NeverRetryStrategy" to runtimeApi.resolve("client::retries::NeverRetryStrategy"), + "Params" to endpointTypesGenerator.paramsStruct(), + "ResolveEndpoint" to http.resolve("endpoint::ResolveEndpoint"), "RuntimePlugin" to runtimeApi.resolve("client::runtime_plugin::RuntimePlugin"), - "StaticUriEndpointResolver" to runtimeApi.resolve("client::endpoints::StaticUriEndpointResolver"), + "SharedEndpointResolver" to http.resolve("endpoint::SharedEndpointResolver"), "TestConnection" to runtime.resolve("client::connections::test_connection::TestConnection"), "TraceProbe" to runtimeApi.resolve("client::orchestrator::TraceProbe"), ) @@ -125,8 +131,12 @@ class ServiceRuntimePluginGenerator( // Set an empty auth option resolver to be overridden by operations that need auth. cfg.set_auth_option_resolver(#{AuthOptionListResolver}::new(Vec::new())); - // TODO(RuntimePlugins): Resolve the correct endpoint - cfg.set_endpoint_resolver(#{StaticUriEndpointResolver}::http_localhost(1234)); + let endpoint_resolver = #{DefaultEndpointResolver}::<#{Params}>::new( + #{SharedEndpointResolver}::from(self.handle.conf.endpoint_resolver())); + cfg.set_endpoint_resolver(endpoint_resolver); + + ${"" /* TODO(EndpointResolver): Create endpoint params builder from service config */} + cfg.put(#{Params}::builder()); // TODO(RuntimePlugins): Wire up standard retry cfg.set_retry_strategy(#{NeverRetryStrategy}::new()); diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt index ebe387d4d6..3c8fc7c974 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt @@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.protocol import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointParamsInterceptorGenerator import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationRuntimePluginGenerator import software.amazon.smithy.rust.codegen.client.smithy.protocols.HttpBoundProtocolTraitImplGenerator import software.amazon.smithy.rust.codegen.core.rustlang.Attribute @@ -101,13 +102,16 @@ open class ClientProtocolGenerator( operationWriter, operationShape, operationName, - codegenDecorator.operationRuntimePluginCustomizations(codegenContext, emptyList()), + codegenDecorator.operationRuntimePluginCustomizations(codegenContext, operationShape, emptyList()), ) ResponseDeserializerGenerator(codegenContext, protocol) .render(operationWriter, operationShape, operationCustomizations) RequestSerializerGenerator(codegenContext, protocol, bodyGenerator) .render(operationWriter, operationShape, operationCustomizations) + + EndpointParamsInterceptorGenerator(codegenContext) + .render(operationWriter, operationShape) } } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt index 841986cb91..6fe1328bb8 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt @@ -7,6 +7,8 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.traits.EndpointTrait import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest @@ -34,8 +36,9 @@ internal class EndpointTraitBindingsTest { epTrait.prefixFormatString() shouldBe ("\"{foo}.data\"") } - @Test - fun `generate endpoint prefixes`() { + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `generate endpoint prefixes`(enableNewSmithyRuntime: Boolean) { val model = """ namespace test @readonly @@ -73,7 +76,7 @@ internal class EndpointTraitBindingsTest { RuntimeType.smithyHttp(TestRuntimeConfig), TestRuntimeConfig.operationBuildError(), ) { - endpointBindingGenerator.render(this, "self") + endpointBindingGenerator.render(this, "self", enableNewSmithyRuntime) } } unitTest( diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt index cf2ff4b5d5..575f9eb6f6 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt @@ -87,7 +87,9 @@ fun > T.withBlockTemplate( block: T.() -> Unit, ): T { return withTemplate(textBeforeNewLine, ctx) { header -> - conditionalBlock(header, textAfterNewLine, conditional = true, block = block) + withTemplate(textAfterNewLine, ctx) { tail -> + conditionalBlock(header, tail, conditional = true, block = block) + } } } diff --git a/rust-runtime/aws-smithy-http/src/endpoint.rs b/rust-runtime/aws-smithy-http/src/endpoint.rs index e73cd05131..d9d7844746 100644 --- a/rust-runtime/aws-smithy-http/src/endpoint.rs +++ b/rust-runtime/aws-smithy-http/src/endpoint.rs @@ -9,8 +9,10 @@ use crate::endpoint::error::InvalidEndpointError; use crate::operation::error::BuildError; use http::uri::{Authority, Uri}; use std::borrow::Cow; +use std::fmt::Debug; use std::result::Result as StdResult; use std::str::FromStr; +use std::sync::Arc; pub mod error; pub mod middleware; @@ -21,7 +23,7 @@ pub use error::ResolveEndpointError; pub type Result = std::result::Result; /// Implementors of this trait can resolve an endpoint that will be applied to a request. -pub trait ResolveEndpoint: Send + Sync { +pub trait ResolveEndpoint: Debug + Send + Sync { /// Given some endpoint parameters, resolve an endpoint or return an error when resolution is /// impossible. fn resolve_endpoint(&self, params: &Params) -> Result; @@ -35,6 +37,35 @@ impl ResolveEndpoint for &'static str { } } +/// Endpoint Resolver wrapper that may be shared +#[derive(Clone, Debug)] +pub struct SharedEndpointResolver(Arc>); + +impl SharedEndpointResolver { + /// Create a new `SharedEndpointResolver` from `ResolveEndpoint` + pub fn new(resolve_endpoint: impl ResolveEndpoint + 'static) -> Self { + Self(Arc::new(resolve_endpoint)) + } +} + +impl AsRef> for SharedEndpointResolver { + fn as_ref(&self) -> &(dyn ResolveEndpoint + 'static) { + self.0.as_ref() + } +} + +impl From>> for SharedEndpointResolver { + fn from(resolve_endpoint: Arc>) -> Self { + SharedEndpointResolver(resolve_endpoint) + } +} + +impl ResolveEndpoint for SharedEndpointResolver { + fn resolve_endpoint(&self, params: &T) -> Result { + self.0.resolve_endpoint(params) + } +} + /// API Endpoint /// /// This implements an API endpoint as specified in the diff --git a/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs b/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs index 1665df9bce..fd934058d2 100644 --- a/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs +++ b/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs @@ -13,6 +13,8 @@ use http::header::HeaderName; use http::{HeaderValue, Uri}; use std::str::FromStr; +// TODO(enableNewSmithyRuntime): Delete this module + /// Middleware to apply an HTTP endpoint to the request /// /// This middleware reads [`aws_smithy_types::endpoint::Endpoint`] out of the request properties and applies diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/endpoints.rs b/rust-runtime/aws-smithy-runtime-api/src/client/endpoints.rs index 1e823e713a..b9b4f4a512 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/endpoints.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/endpoints.rs @@ -3,9 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -use crate::client::orchestrator::{BoxError, EndpointResolver, HttpRequest}; -use aws_smithy_http::endpoint::apply_endpoint; -use http::Uri; +use crate::client::orchestrator::{ + BoxError, EndpointResolver, EndpointResolverParams, HttpRequest, +}; +use aws_smithy_http::endpoint::error::ResolveEndpointError; +use aws_smithy_http::endpoint::{ + apply_endpoint, EndpointPrefix, ResolveEndpoint, SharedEndpointResolver, +}; +use http::header::HeaderName; +use http::{HeaderValue, Uri}; use std::fmt::Debug; use std::str::FromStr; @@ -28,8 +34,77 @@ impl StaticUriEndpointResolver { } impl EndpointResolver for StaticUriEndpointResolver { - fn resolve_and_apply_endpoint(&self, request: &mut HttpRequest) -> Result<(), BoxError> { + fn resolve_and_apply_endpoint( + &self, + _params: &EndpointResolverParams, + _endpoint_prefix: Option<&EndpointPrefix>, + request: &mut HttpRequest, + ) -> Result<(), BoxError> { apply_endpoint(request.uri_mut(), &self.endpoint, None)?; Ok(()) } } + +#[derive(Debug, Clone)] +pub struct DefaultEndpointResolver { + inner: SharedEndpointResolver, +} + +impl DefaultEndpointResolver { + pub fn new(resolve_endpoint: SharedEndpointResolver) -> Self { + Self { + inner: resolve_endpoint, + } + } +} + +impl EndpointResolver for DefaultEndpointResolver +where + Params: Debug + Send + Sync + 'static, +{ + fn resolve_and_apply_endpoint( + &self, + params: &EndpointResolverParams, + endpoint_prefix: Option<&EndpointPrefix>, + request: &mut HttpRequest, + ) -> Result<(), BoxError> { + let endpoint = match params.get::() { + Some(params) => self.inner.resolve_endpoint(params)?, + None => { + return Err(Box::new(ResolveEndpointError::message( + "params of expected type was not present", + ))); + } + }; + + let uri: Uri = endpoint.url().parse().map_err(|err| { + ResolveEndpointError::from_source("endpoint did not have a valid uri", err) + })?; + + apply_endpoint(request.uri_mut(), &uri, endpoint_prefix).map_err(|err| { + ResolveEndpointError::message(format!( + "failed to apply endpoint `{:?}` to request `{:?}`", + uri, request, + )) + .with_source(Some(err.into())) + })?; + + for (header_name, header_values) in endpoint.headers() { + request.headers_mut().remove(header_name); + for value in header_values { + request.headers_mut().insert( + HeaderName::from_str(header_name).map_err(|err| { + ResolveEndpointError::message("invalid header name") + .with_source(Some(err.into())) + })?, + HeaderValue::from_str(value).map_err(|err| { + ResolveEndpointError::message("invalid header value") + .with_source(Some(err.into())) + })?, + ); + } + } + + Ok(()) + } +} diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs index 29baf6bf00..35b85e3bcb 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs @@ -9,6 +9,7 @@ use crate::client::interceptors::InterceptorContext; use crate::config_bag::ConfigBag; use crate::type_erasure::{TypeErasedBox, TypedBox}; use aws_smithy_http::body::SdkBody; +use aws_smithy_http::endpoint::EndpointPrefix; use aws_smithy_http::property_bag::PropertyBag; use std::any::Any; use std::borrow::Cow; @@ -179,8 +180,26 @@ pub trait HttpRequestSigner: Send + Sync + Debug { ) -> Result<(), BoxError>; } +#[derive(Debug)] +pub struct EndpointResolverParams(TypeErasedBox); + +impl EndpointResolverParams { + pub fn new(params: T) -> Self { + Self(TypedBox::new(params).erase()) + } + + pub fn get(&self) -> Option<&T> { + self.0.downcast_ref() + } +} + pub trait EndpointResolver: Send + Sync + Debug { - fn resolve_and_apply_endpoint(&self, request: &mut HttpRequest) -> Result<(), BoxError>; + fn resolve_and_apply_endpoint( + &self, + params: &EndpointResolverParams, + endpoint_prefix: Option<&EndpointPrefix>, + request: &mut HttpRequest, + ) -> Result<(), BoxError>; } pub trait ConfigBagAccessors { @@ -193,6 +212,9 @@ pub trait ConfigBagAccessors { fn auth_option_resolver(&self) -> &dyn AuthOptionResolver; fn set_auth_option_resolver(&mut self, auth_option_resolver: impl AuthOptionResolver + 'static); + fn endpoint_resolver_params(&self) -> &EndpointResolverParams; + fn set_endpoint_resolver_params(&mut self, endpoint_resolver_params: EndpointResolverParams); + fn endpoint_resolver(&self) -> &dyn EndpointResolver; fn set_endpoint_resolver(&mut self, endpoint_resolver: impl EndpointResolver + 'static); @@ -266,6 +288,15 @@ impl ConfigBagAccessors for ConfigBag { self.put::>(Box::new(retry_strategy)); } + fn endpoint_resolver_params(&self) -> &EndpointResolverParams { + self.get::() + .expect("endpoint resolver params must be set") + } + + fn set_endpoint_resolver_params(&mut self, endpoint_resolver_params: EndpointResolverParams) { + self.put::(endpoint_resolver_params); + } + fn endpoint_resolver(&self) -> &dyn EndpointResolver { &**self .get::>() diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index fbc83cadfb..217a0eaae7 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs @@ -4,6 +4,7 @@ */ use self::auth::orchestrate_auth; +use crate::client::orchestrator::endpoints::orchestrate_endpoint; use crate::client::orchestrator::http::read_body; use crate::client::orchestrator::phase::Phase; use aws_smithy_http::result::SdkError; @@ -17,6 +18,7 @@ use aws_smithy_runtime_api::config_bag::ConfigBag; use tracing::{debug_span, Instrument}; mod auth; +mod endpoints; mod http; pub(self) mod phase; @@ -105,12 +107,7 @@ async fn make_an_attempt( ) -> Result> { let dispatch_phase = dispatch_phase .include(|ctx| interceptors.read_before_attempt(ctx, cfg))? - .include_mut(|ctx| { - let request = ctx.request_mut().expect("request has been set"); - - let endpoint_resolver = cfg.endpoint_resolver(); - endpoint_resolver.resolve_and_apply_endpoint(request) - })? + .include_mut(|ctx| orchestrate_endpoint(ctx, cfg))? .include_mut(|ctx| interceptors.modify_before_signing(ctx, cfg))? .include(|ctx| interceptors.read_before_signing(ctx, cfg))?; diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs new file mode 100644 index 0000000000..5da7bfe517 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs @@ -0,0 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_smithy_http::endpoint::EndpointPrefix; +use aws_smithy_runtime_api::client::interceptors::InterceptorContext; +use aws_smithy_runtime_api::client::orchestrator::{ + BoxError, ConfigBagAccessors, HttpRequest, HttpResponse, +}; +use aws_smithy_runtime_api::config_bag::ConfigBag; + +pub(super) fn orchestrate_endpoint( + ctx: &mut InterceptorContext, + cfg: &ConfigBag, +) -> Result<(), BoxError> { + let params = cfg.endpoint_resolver_params(); + let endpoint_prefix = cfg.get::(); + let request = ctx.request_mut()?; + + let endpoint_resolver = cfg.endpoint_resolver(); + endpoint_resolver.resolve_and_apply_endpoint(params, endpoint_prefix, request)?; + + Ok(()) +} diff --git a/rust-runtime/inlineable/src/endpoint_lib/partition.rs b/rust-runtime/inlineable/src/endpoint_lib/partition.rs index 55b97a21d2..b9b4002ed1 100644 --- a/rust-runtime/inlineable/src/endpoint_lib/partition.rs +++ b/rust-runtime/inlineable/src/endpoint_lib/partition.rs @@ -17,7 +17,7 @@ use std::borrow::Cow; use std::collections::HashMap; /// Determine the AWS partition metadata for a given region -#[derive(Default)] +#[derive(Debug, Default)] pub(crate) struct PartitionResolver { partitions: Vec, } @@ -151,6 +151,7 @@ impl PartitionResolver { type Str = Cow<'static, str>; +#[derive(Debug)] pub(crate) struct PartitionMetadata { id: Str, region_regex: Regex, @@ -203,6 +204,7 @@ impl PartitionMetadata { } } +#[derive(Debug)] pub(crate) struct PartitionOutput { name: Str, dns_suffix: Str, @@ -211,7 +213,7 @@ pub(crate) struct PartitionOutput { supports_dual_stack: bool, } -#[derive(Default)] +#[derive(Debug, Default)] pub(crate) struct PartitionOutputOverride { name: Option, dns_suffix: Option,