Tidy up `aws-smithy-runtime-api` a bit (#2867)

This PR makes some progress towards documenting the
`aws-smithy-runtime-api` crate, as well as some additional work to make
it more stable:

- Places the `client` module behind a `client` feature so that it will
be possible to add a `server` module/feature in the future.
- Deletes `ConfigBagAccessors`.
- Renames auth types to reflect changes in the internal spec.
- Moves `RequestAttempts` into the `client::retries` module.
- Moves several types that were in floating around in
`client::orchestrator` to their own modules.
- Renames `Connector` to `HttpConnector`.
- Changes `DynResponseDeserializer` to `SharedResponseDeserializer` for
consistency with serialization, and also so that it could be moved into
runtime components or config in the future (since those need to
implement `Clone`).
- Updates the identity and auth design doc.
- Updates READMEs and crate-level documentation.
- Fixes most, but not all, the missing documentation in the crate.
- Hides the builder macros.

----

_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:
John DiSanti 2023-07-24 08:28:49 -07:00 committed by GitHub
parent e060135e01
commit 666c474f4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1055 additions and 942 deletions

View File

@ -11,7 +11,6 @@ use aws_sigv4::http_request::SignableBody;
use aws_smithy_http::body::SdkBody;
use aws_smithy_http::byte_stream;
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::config_bag_accessors::ConfigBagAccessors;
use aws_smithy_runtime_api::client::interceptors::context::{
BeforeSerializationInterceptorContextMut, BeforeTransmitInterceptorContextMut,
};
@ -126,7 +125,7 @@ impl Interceptor for GlacierTreeHashHeaderInterceptor {
// Request the request body to be loaded into memory immediately after serialization
// so that it can be checksummed before signing and transmit
cfg.interceptor_state()
.set_loaded_request_body(LoadedRequestBody::Requested);
.store_put(LoadedRequestBody::Requested);
Ok(())
}

View File

@ -18,8 +18,8 @@ aws-sigv4 = { path = "../aws-sigv4" }
aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" }
aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true }
aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" }
aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime" }
aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api" }
aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", features = ["client"] }
aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] }
aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" }
aws-types = { path = "../aws-types" }
fastrand = "2.0.0"

View File

@ -10,7 +10,7 @@ use aws_sigv4::http_request::{
};
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::auth::{
AuthSchemeEndpointConfig, AuthSchemeId, HttpAuthScheme, HttpRequestSigner,
AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, Signer,
};
use aws_smithy_runtime_api::client::identity::{Identity, SharedIdentityResolver};
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
@ -79,18 +79,18 @@ impl StdError for SigV4SigningError {
/// SigV4 auth scheme.
#[derive(Debug, Default)]
pub struct SigV4HttpAuthScheme {
signer: SigV4HttpRequestSigner,
pub struct SigV4AuthScheme {
signer: SigV4Signer,
}
impl SigV4HttpAuthScheme {
/// Creates a new `SigV4HttpAuthScheme`.
impl SigV4AuthScheme {
/// Creates a new `SigV4AuthScheme`.
pub fn new() -> Self {
Default::default()
}
}
impl HttpAuthScheme for SigV4HttpAuthScheme {
impl AuthScheme for SigV4AuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
SCHEME_ID
}
@ -102,7 +102,7 @@ impl HttpAuthScheme for SigV4HttpAuthScheme {
identity_resolvers.identity_resolver(self.scheme_id())
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
fn signer(&self) -> &dyn Signer {
&self.signer
}
}
@ -174,11 +174,11 @@ impl Storable for SigV4OperationSigningConfig {
type Storer = StoreReplace<Self>;
}
/// SigV4 HTTP request signer.
/// SigV4 signer.
#[derive(Debug, Default)]
pub struct SigV4HttpRequestSigner;
pub struct SigV4Signer;
impl SigV4HttpRequestSigner {
impl SigV4Signer {
/// Creates a new signer instance.
pub fn new() -> Self {
Self
@ -291,7 +291,7 @@ impl SigV4HttpRequestSigner {
endpoint_config: AuthSchemeEndpointConfig<'_>,
) -> Result<EndpointAuthSchemeConfig, SigV4SigningError> {
let (mut signing_region_override, mut signing_service_override) = (None, None);
if let Some(config) = endpoint_config.config().and_then(Document::as_object) {
if let Some(config) = endpoint_config.as_document().and_then(Document::as_object) {
use SigV4SigningError::BadTypeInEndpointAuthSchemeConfig as UnexpectedType;
signing_region_override = match config.get("signingRegion") {
Some(Document::String(s)) => Some(SigningRegion::from(Region::new(s.clone()))),
@ -311,8 +311,8 @@ impl SigV4HttpRequestSigner {
}
}
impl HttpRequestSigner for SigV4HttpRequestSigner {
fn sign_request(
impl Signer for SigV4Signer {
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
@ -550,15 +550,13 @@ mod tests {
payload_override: None,
},
};
SigV4HttpRequestSigner::signing_params(settings, &credentials, &operation_config, now)
.unwrap();
SigV4Signer::signing_params(settings, &credentials, &operation_config, now).unwrap();
assert!(!logs_contain(EXPIRATION_WARNING));
let mut settings = SigningSettings::default();
settings.expires_in = Some(creds_expire_in + Duration::from_secs(10));
SigV4HttpRequestSigner::signing_params(settings, &credentials, &operation_config, now)
.unwrap();
SigV4Signer::signing_params(settings, &credentials, &operation_config, now).unwrap();
assert!(logs_contain(EXPIRATION_WARNING));
}
@ -583,11 +581,10 @@ mod tests {
);
out
});
let config = AuthSchemeEndpointConfig::new(Some(&config));
let config = AuthSchemeEndpointConfig::from(Some(&config));
let cfg = ConfigBag::of_layers(vec![layer]);
let result =
SigV4HttpRequestSigner::extract_operation_config(config, &cfg).expect("success");
let result = SigV4Signer::extract_operation_config(config, &cfg).expect("success");
assert_eq!(
result.region,
@ -611,8 +608,7 @@ mod tests {
let cfg = ConfigBag::of_layers(vec![layer]);
let config = AuthSchemeEndpointConfig::empty();
let result =
SigV4HttpRequestSigner::extract_operation_config(config, &cfg).expect("success");
let result = SigV4Signer::extract_operation_config(config, &cfg).expect("success");
assert_eq!(
result.region,

View File

@ -7,7 +7,7 @@ use aws_smithy_runtime::client::orchestrator::interceptors::ServiceClockSkew;
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextMut;
use aws_smithy_runtime_api::client::interceptors::Interceptor;
use aws_smithy_runtime_api::client::request_attempts::RequestAttempts;
use aws_smithy_runtime_api::client::retries::RequestAttempts;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_types::config_bag::ConfigBag;
use aws_smithy_types::date_time::Format;

View File

@ -22,7 +22,6 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) :
"AwsUserAgent" to AwsRuntimeType.awsHttp(runtimeConfig).resolve("user_agent::AwsUserAgent"),
"BeforeTransmitInterceptorContextMut" to RuntimeType.beforeTransmitInterceptorContextMut(runtimeConfig),
"ConfigBag" to RuntimeType.configBag(runtimeConfig),
"ConfigBagAccessors" to RuntimeType.configBagAccessors(runtimeConfig),
"http" to CargoDependency.Http.toType(),
"InterceptorContext" to RuntimeType.interceptorContext(runtimeConfig),
"RuntimeComponentsBuilder" to RuntimeType.runtimeComponentsBuilder(runtimeConfig),

View File

@ -364,21 +364,19 @@ class AwsPresignedFluentBuilderMethod(
struct AlternatePresigningSerializerRuntimePlugin;
impl #{RuntimePlugin} for AlternatePresigningSerializerRuntimePlugin {
fn config(&self) -> #{Option}<#{FrozenLayer}> {
use #{ConfigBagAccessors};
let mut cfg = #{Layer}::new("presigning_serializer");
cfg.set_request_serializer(#{SharedRequestSerializer}::new(#{AlternateSerializer}));
cfg.store_put(#{SharedRequestSerializer}::new(#{AlternateSerializer}));
#{Some}(cfg.freeze())
}
}
""",
*preludeScope,
"AlternateSerializer" to alternateSerializer(operationShape),
"ConfigBagAccessors" to RuntimeType.configBagAccessors(runtimeConfig),
"FrozenLayer" to smithyTypes.resolve("config_bag::FrozenLayer"),
"Layer" to smithyTypes.resolve("config_bag::Layer"),
"RuntimePlugin" to RuntimeType.runtimePlugin(codegenContext.runtimeConfig),
"SharedRequestSerializer" to RuntimeType.smithyRuntimeApi(codegenContext.runtimeConfig)
.resolve("client::orchestrator::SharedRequestSerializer"),
.resolve("client::ser_de::SharedRequestSerializer"),
)
}
},

View File

@ -10,7 +10,7 @@ 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.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthSchemeOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationSection
@ -33,9 +33,9 @@ class SigV4AuthDecorator : ClientCodegenDecorator {
override fun authOptions(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
baseAuthOptions: List<AuthOption>,
): List<AuthOption> = baseAuthOptions.letIf(codegenContext.smithyRuntimeMode.generateOrchestrator) {
it + AuthOption.StaticAuthOption(SigV4Trait.ID) {
baseAuthSchemeOptions: List<AuthSchemeOption>,
): List<AuthSchemeOption> = baseAuthSchemeOptions.letIf(codegenContext.smithyRuntimeMode.generateOrchestrator) {
it + AuthSchemeOption.StaticAuthSchemeOption(SigV4Trait.ID) {
rustTemplate(
"#{scheme_id},",
"scheme_id" to AwsRuntimeType.awsRuntime(codegenContext.runtimeConfig)
@ -69,10 +69,10 @@ private class AuthServiceRuntimePluginCustomization(private val codegenContext:
val awsRuntime = AwsRuntimeType.awsRuntime(runtimeConfig)
arrayOf(
"SIGV4_SCHEME_ID" to awsRuntime.resolve("auth::sigv4::SCHEME_ID"),
"SigV4HttpAuthScheme" to awsRuntime.resolve("auth::sigv4::SigV4HttpAuthScheme"),
"SigV4AuthScheme" to awsRuntime.resolve("auth::sigv4::SigV4AuthScheme"),
"SigningRegion" to AwsRuntimeType.awsTypes(runtimeConfig).resolve("region::SigningRegion"),
"SigningService" to AwsRuntimeType.awsTypes(runtimeConfig).resolve("SigningService"),
"SharedHttpAuthScheme" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::auth::SharedHttpAuthScheme"),
"SharedAuthScheme" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::auth::SharedAuthScheme"),
)
}
@ -84,8 +84,8 @@ private class AuthServiceRuntimePluginCustomization(private val codegenContext:
// enable the aws-runtime `sign-eventstream` feature
addDependency(AwsCargoDependency.awsRuntime(runtimeConfig).withFeature("event-stream").toType().toSymbol())
}
section.registerHttpAuthScheme(this) {
rustTemplate("#{SharedHttpAuthScheme}::new(#{SigV4HttpAuthScheme}::new())", *codegenScope)
section.registerAuthScheme(this) {
rustTemplate("#{SharedAuthScheme}::new(#{SigV4AuthScheme}::new())", *codegenScope)
}
}

View File

@ -12,7 +12,7 @@ import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.shapes.ToShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustSettings
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthSchemeOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientProtocolMap
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization
@ -63,9 +63,9 @@ class ServiceSpecificDecorator(
override fun authOptions(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
baseAuthOptions: List<AuthOption>,
): List<AuthOption> = baseAuthOptions.maybeApply(codegenContext.serviceShape) {
delegateTo.authOptions(codegenContext, operationShape, baseAuthOptions)
baseAuthSchemeOptions: List<AuthSchemeOption>,
): List<AuthSchemeOption> = baseAuthSchemeOptions.maybeApply(codegenContext.serviceShape) {
delegateTo.authOptions(codegenContext, operationShape, baseAuthSchemeOptions)
}
override fun builderCustomizations(

View File

@ -14,8 +14,8 @@ import software.amazon.smithy.model.traits.HttpBearerAuthTrait
import software.amazon.smithy.model.traits.HttpDigestAuthTrait
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.customize.AuthOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthOption.StaticAuthOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthSchemeOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthSchemeOption.StaticAuthSchemeOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection
@ -52,7 +52,7 @@ private fun codegenScope(runtimeConfig: RuntimeConfig): Array<Pair<String, Any>>
"IdentityResolver" to smithyRuntimeApi.resolve("client::identity::IdentityResolver"),
"Login" to smithyRuntimeApi.resolve("client::identity::http::Login"),
"PropertyBag" to RuntimeType.smithyHttp(runtimeConfig).resolve("property_bag::PropertyBag"),
"SharedHttpAuthScheme" to smithyRuntimeApi.resolve("client::auth::SharedHttpAuthScheme"),
"SharedAuthScheme" to smithyRuntimeApi.resolve("client::auth::SharedAuthScheme"),
"SharedIdentityResolver" to smithyRuntimeApi.resolve("client::identity::SharedIdentityResolver"),
"Token" to smithyRuntimeApi.resolve("client::identity::http::Token"),
)
@ -89,16 +89,16 @@ class HttpAuthDecorator : ClientCodegenDecorator {
override fun authOptions(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
baseAuthOptions: List<AuthOption>,
): List<AuthOption> {
baseAuthSchemeOptions: List<AuthSchemeOption>,
): List<AuthSchemeOption> {
val serviceIndex = ServiceIndex.of(codegenContext.model)
val authSchemes = serviceIndex.getEffectiveAuthSchemes(codegenContext.serviceShape, operationShape)
val codegenScope = codegenScope(codegenContext.runtimeConfig)
val options = ArrayList<AuthOption>()
val options = ArrayList<AuthSchemeOption>()
for (authScheme in authSchemes.keys) {
fun addOption(schemeShapeId: ShapeId, name: String) {
options.add(
StaticAuthOption(
StaticAuthSchemeOption(
schemeShapeId,
writable {
rustTemplate("$name,", *codegenScope)
@ -114,7 +114,7 @@ class HttpAuthDecorator : ClientCodegenDecorator {
else -> {}
}
}
return baseAuthOptions + options
return baseAuthSchemeOptions + options
}
override fun configCustomizations(
@ -164,8 +164,8 @@ private class HttpAuthServiceRuntimePluginCustomization(
when (section) {
is ServiceRuntimePluginSection.RegisterRuntimeComponents -> {
fun registerAuthScheme(scheme: Writable) {
section.registerHttpAuthScheme(this) {
rustTemplate("#{SharedHttpAuthScheme}::new(#{Scheme})", *codegenScope, "Scheme" to scheme)
section.registerAuthScheme(this) {
rustTemplate("#{SharedAuthScheme}::new(#{Scheme})", *codegenScope, "Scheme" to scheme)
}
}
fun registerNamedAuthScheme(name: String) {

View File

@ -45,7 +45,7 @@ private class HttpConnectorConfigCustomization(
"HttpConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::HttpConnector"),
"Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"),
"SharedAsyncSleep" to RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep::SharedAsyncSleep"),
"SharedConnector" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::connectors::SharedConnector"),
"SharedHttpConnector" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::connectors::SharedHttpConnector"),
"TimeoutConfig" to RuntimeType.smithyTypes(runtimeConfig).resolve("timeout::TimeoutConfig"),
)
@ -101,9 +101,9 @@ private class HttpConnectorConfigCustomization(
http_connector
.and_then(|c| c.connector(&connector_settings, sleep_impl.clone()))
.or_else(|| #{default_connector}(&connector_settings, sleep_impl))
.map(|c| #{SharedConnector}::new(#{DynConnectorAdapter}::new(c)));
.map(|c| #{SharedHttpConnector}::new(#{DynConnectorAdapter}::new(c)));
resolver.runtime_components_mut().set_connector(connector);
resolver.runtime_components_mut().set_http_connector(connector);
}
}
""",
@ -124,15 +124,9 @@ private class HttpConnectorConfigCustomization(
if (runtimeMode.defaultToOrchestrator) {
rustTemplate(
"""
// TODO(enableNewSmithyRuntimeCleanup): Remove this function
/// Return an [`HttpConnector`](#{HttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<&#{HttpConnector}> {
self.config.load::<#{HttpConnector}>()
}
/// Return the [`SharedConnector`](#{SharedConnector}) to use when making requests, if any.
pub fn connector(&self) -> Option<#{SharedConnector}> {
self.runtime_components.connector()
/// Return the [`SharedHttpConnector`](#{SharedHttpConnector}) to use when making requests, if any.
pub fn http_connector(&self) -> Option<#{SharedHttpConnector}> {
self.runtime_components.http_connector()
}
""",
*codegenScope,

View File

@ -8,7 +8,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthSchemeOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
@ -28,9 +28,9 @@ class NoAuthDecorator : ClientCodegenDecorator {
override fun authOptions(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
baseAuthOptions: List<AuthOption>,
): List<AuthOption> = baseAuthOptions +
AuthOption.StaticAuthOption(noAuthSchemeShapeId) {
baseAuthSchemeOptions: List<AuthSchemeOption>,
): List<AuthSchemeOption> = baseAuthSchemeOptions +
AuthSchemeOption.StaticAuthSchemeOption(noAuthSchemeShapeId) {
rustTemplate(
"#{NO_AUTH_SCHEME_ID},",
"NO_AUTH_SCHEME_ID" to noAuthModule(codegenContext).resolve("NO_AUTH_SCHEME_ID"),

View File

@ -26,14 +26,14 @@ import java.util.logging.Logger
typealias ClientProtocolMap = ProtocolMap<OperationGenerator, ClientCodegenContext>
sealed interface AuthOption {
/** Auth scheme for the `StaticAuthOptionResolver` */
data class StaticAuthOption(
sealed interface AuthSchemeOption {
/** Auth scheme for the `StaticAuthSchemeOptionResolver` */
data class StaticAuthSchemeOption(
val schemeShapeId: ShapeId,
val constructor: Writable,
) : AuthOption
) : AuthSchemeOption
class CustomResolver(/* unimplemented */) : AuthOption
class CustomResolver(/* unimplemented */) : AuthSchemeOption
}
/**
@ -47,8 +47,8 @@ interface ClientCodegenDecorator : CoreCodegenDecorator<ClientCodegenContext, Cl
fun authOptions(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
baseAuthOptions: List<AuthOption>,
): List<AuthOption> = baseAuthOptions
baseAuthSchemeOptions: List<AuthSchemeOption>,
): List<AuthSchemeOption> = baseAuthSchemeOptions
fun configCustomizations(
codegenContext: ClientCodegenContext,
@ -110,8 +110,8 @@ open class CombinedClientCodegenDecorator(decorators: List<ClientCodegenDecorato
override fun authOptions(
codegenContext: ClientCodegenContext,
operationShape: OperationShape,
baseAuthOptions: List<AuthOption>,
): List<AuthOption> = combineCustomizations(baseAuthOptions) { decorator, authOptions ->
baseAuthSchemeOptions: List<AuthSchemeOption>,
): List<AuthSchemeOption> = combineCustomizations(baseAuthSchemeOptions) { decorator, authOptions ->
decorator.authOptions(codegenContext, operationShape, authOptions)
}

View File

@ -35,7 +35,7 @@ internal class EndpointConfigCustomization(
"OldSharedEndpointResolver" to types.sharedEndpointResolver,
"Params" to typesGenerator.paramsStruct(),
"Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"),
"SharedEndpointResolver" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::SharedEndpointResolver"),
"SharedEndpointResolver" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::endpoint::SharedEndpointResolver"),
"SmithyResolver" to types.resolveEndpoint,
)

View File

@ -45,15 +45,12 @@ class EndpointParamsInterceptorGenerator(
val runtimeApi = CargoDependency.smithyRuntimeApi(rc).toType()
val interceptors = runtimeApi.resolve("client::interceptors")
val orchestrator = runtimeApi.resolve("client::orchestrator")
val smithyTypes = CargoDependency.smithyTypes(rc).toType()
arrayOf(
*preludeScope,
"BoxError" to RuntimeType.boxError(rc),
"ConfigBag" to RuntimeType.configBag(rc),
"ConfigBagAccessors" to RuntimeType.smithyRuntimeApi(rc)
.resolve("client::config_bag_accessors::ConfigBagAccessors"),
"ContextAttachedError" to interceptors.resolve("error::ContextAttachedError"),
"EndpointResolverParams" to orchestrator.resolve("EndpointResolverParams"),
"EndpointResolverParams" to runtimeApi.resolve("client::endpoint::EndpointResolverParams"),
"HttpRequest" to orchestrator.resolve("HttpRequest"),
"HttpResponse" to orchestrator.resolve("HttpResponse"),
"Interceptor" to RuntimeType.interceptor(rc),
@ -82,7 +79,6 @@ class EndpointParamsInterceptorGenerator(
context: &#{BeforeSerializationInterceptorContextRef}<'_, #{Input}, #{Output}, #{Error}>,
cfg: &mut #{ConfigBag},
) -> #{Result}<(), #{BoxError}> {
use #{ConfigBagAccessors};
let _input = context.input()
.downcast_ref::<${operationInput.name}>()
.ok_or("failed to downcast to ${operationInput.name}")?;
@ -93,7 +89,7 @@ class EndpointParamsInterceptorGenerator(
#{param_setters}
.build()
.map_err(|err| #{ContextAttachedError}::new("endpoint params could not be built", err))?;
cfg.interceptor_state().set_endpoint_resolver_params(#{EndpointResolverParams}::new(params));
cfg.interceptor_state().store_put(#{EndpointResolverParams}::new(params));
#{Ok}(())
}
}

View File

@ -25,7 +25,6 @@ class ConfigOverrideRuntimePluginGenerator(
*RuntimeType.preludeScope,
"Cow" to RuntimeType.Cow,
"CloneableLayer" to smithyTypes.resolve("config_bag::CloneableLayer"),
"ConfigBagAccessors" to runtimeApi.resolve("client::config_bag_accessors::ConfigBagAccessors"),
"FrozenLayer" to smithyTypes.resolve("config_bag::FrozenLayer"),
"InterceptorRegistrar" to runtimeApi.resolve("client::interceptors::InterceptorRegistrar"),
"Layer" to smithyTypes.resolve("config_bag::Layer"),

View File

@ -7,7 +7,7 @@ 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.client.smithy.customize.AuthOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthSchemeOption
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.protocol.MakeOperationGenerator
@ -85,7 +85,7 @@ open class OperationGenerator(
private fun renderOperationStruct(
operationWriter: RustWriter,
operationShape: OperationShape,
authOptions: List<AuthOption>,
authSchemeOptions: List<AuthSchemeOption>,
operationCustomizations: List<OperationCustomization>,
) {
val operationName = symbolProvider.toSymbol(operationShape).name
@ -213,7 +213,7 @@ open class OperationGenerator(
operationWriter,
operationShape,
operationName,
authOptions,
authSchemeOptions,
operationCustomizations,
)

View File

@ -10,7 +10,7 @@ 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.customizations.noAuthSchemeShapeId
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthOption
import software.amazon.smithy.rust.codegen.client.smithy.customize.AuthSchemeOption
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
@ -35,21 +35,20 @@ class OperationRuntimePluginGenerator(
val smithyTypes = RuntimeType.smithyTypes(rc)
arrayOf(
*preludeScope,
"AuthOptionResolverParams" to runtimeApi.resolve("client::auth::AuthOptionResolverParams"),
"AuthSchemeOptionResolverParams" to runtimeApi.resolve("client::auth::AuthSchemeOptionResolverParams"),
"BoxError" to RuntimeType.boxError(codegenContext.runtimeConfig),
"ConfigBag" to RuntimeType.configBag(codegenContext.runtimeConfig),
"ConfigBagAccessors" to RuntimeType.configBagAccessors(codegenContext.runtimeConfig),
"Cow" to RuntimeType.Cow,
"SharedAuthOptionResolver" to runtimeApi.resolve("client::auth::SharedAuthOptionResolver"),
"DynResponseDeserializer" to runtimeApi.resolve("client::orchestrator::DynResponseDeserializer"),
"FrozenLayer" to smithyTypes.resolve("config_bag::FrozenLayer"),
"Layer" to smithyTypes.resolve("config_bag::Layer"),
"RetryClassifiers" to runtimeApi.resolve("client::retries::RetryClassifiers"),
"RuntimePlugin" to RuntimeType.runtimePlugin(codegenContext.runtimeConfig),
"RuntimeComponentsBuilder" to RuntimeType.runtimeComponentsBuilder(codegenContext.runtimeConfig),
"SharedRequestSerializer" to runtimeApi.resolve("client::orchestrator::SharedRequestSerializer"),
"StaticAuthOptionResolver" to runtimeApi.resolve("client::auth::option_resolver::StaticAuthOptionResolver"),
"StaticAuthOptionResolverParams" to runtimeApi.resolve("client::auth::option_resolver::StaticAuthOptionResolverParams"),
"RuntimePlugin" to RuntimeType.runtimePlugin(codegenContext.runtimeConfig),
"SharedAuthSchemeOptionResolver" to runtimeApi.resolve("client::auth::SharedAuthSchemeOptionResolver"),
"SharedRequestSerializer" to runtimeApi.resolve("client::ser_de::SharedRequestSerializer"),
"SharedResponseDeserializer" to runtimeApi.resolve("client::ser_de::SharedResponseDeserializer"),
"StaticAuthSchemeOptionResolver" to runtimeApi.resolve("client::auth::static_resolver::StaticAuthSchemeOptionResolver"),
"StaticAuthSchemeOptionResolverParams" to runtimeApi.resolve("client::auth::static_resolver::StaticAuthSchemeOptionResolverParams"),
)
}
@ -57,7 +56,7 @@ class OperationRuntimePluginGenerator(
writer: RustWriter,
operationShape: OperationShape,
operationStructName: String,
authOptions: List<AuthOption>,
authSchemeOptions: List<AuthSchemeOption>,
customizations: List<OperationCustomization>,
) {
writer.rustTemplate(
@ -65,13 +64,12 @@ class OperationRuntimePluginGenerator(
impl #{RuntimePlugin} for $operationStructName {
fn config(&self) -> #{Option}<#{FrozenLayer}> {
let mut cfg = #{Layer}::new(${operationShape.id.name.dq()});
use #{ConfigBagAccessors} as _;
cfg.store_put(#{SharedRequestSerializer}::new(${operationStructName}RequestSerializer));
cfg.store_put(#{DynResponseDeserializer}::new(${operationStructName}ResponseDeserializer));
cfg.store_put(#{SharedResponseDeserializer}::new(${operationStructName}ResponseDeserializer));
${"" /* TODO(IdentityAndAuth): Resolve auth parameters from input for services that need this */}
cfg.set_auth_option_resolver_params(#{AuthOptionResolverParams}::new(#{StaticAuthOptionResolverParams}::new()));
cfg.store_put(#{AuthSchemeOptionResolverParams}::new(#{StaticAuthSchemeOptionResolverParams}::new()));
#{additional_config}
@ -96,7 +94,7 @@ class OperationRuntimePluginGenerator(
""",
*codegenScope,
*preludeScope,
"auth_options" to generateAuthOptions(operationShape, authOptions),
"auth_options" to generateAuthOptions(operationShape, authSchemeOptions),
"additional_config" to writable {
writeCustomizations(
customizations,
@ -130,20 +128,20 @@ class OperationRuntimePluginGenerator(
private fun generateAuthOptions(
operationShape: OperationShape,
authOptions: List<AuthOption>,
authSchemeOptions: List<AuthSchemeOption>,
): Writable = writable {
if (authOptions.any { it is AuthOption.CustomResolver }) {
throw IllegalStateException("AuthOption.CustomResolver is unimplemented")
if (authSchemeOptions.any { it is AuthSchemeOption.CustomResolver }) {
throw IllegalStateException("AuthSchemeOption.CustomResolver is unimplemented")
} else {
val authOptionsMap = authOptions.associate {
val option = it as AuthOption.StaticAuthOption
val authOptionsMap = authSchemeOptions.associate {
val option = it as AuthSchemeOption.StaticAuthSchemeOption
option.schemeShapeId to option
}
withBlockTemplate(
"""
.with_auth_option_resolver(#{Some}(
#{SharedAuthOptionResolver}::new(
#{StaticAuthOptionResolver}::new(vec![
.with_auth_scheme_option_resolver(#{Some}(
#{SharedAuthSchemeOptionResolver}::new(
#{StaticAuthSchemeOptionResolver}::new(vec![
""",
"]))))",
*codegenScope,

View File

@ -50,10 +50,10 @@ sealed class ServiceRuntimePluginSection(name: String) : Section(name) {
)
}
fun registerHttpAuthScheme(writer: RustWriter, authScheme: Writable) {
fun registerAuthScheme(writer: RustWriter, authScheme: Writable) {
writer.rustTemplate(
"""
runtime_components.push_http_auth_scheme(#{auth_scheme});
runtime_components.push_auth_scheme(#{auth_scheme});
""",
"auth_scheme" to authScheme,
)

View File

@ -313,8 +313,7 @@ class ServiceConfigGenerator(
*preludeScope,
"BoxError" to RuntimeType.boxError(runtimeConfig),
"CloneableLayer" to smithyTypes.resolve("config_bag::CloneableLayer"),
"ConfigBag" to RuntimeType.configBag(runtimeConfig),
"ConfigBagAccessors" to RuntimeType.configBagAccessors(runtimeConfig),
"ConfigBag" to RuntimeType.configBag(codegenContext.runtimeConfig),
"Cow" to RuntimeType.Cow,
"FrozenLayer" to smithyTypes.resolve("config_bag::FrozenLayer"),
"Layer" to smithyTypes.resolve("config_bag::Layer"),

View File

@ -375,7 +375,7 @@ class DefaultProtocolTestGenerator(
let op = #{Operation}::new();
let config = op.config().expect("the operation has config");
let de = config.load::<#{DynResponseDeserializer}>().expect("the config must have a deserializer");
let de = config.load::<#{SharedResponseDeserializer}>().expect("the config must have a deserializer");
let parsed = de.deserialize_streaming(&mut http_response);
let parsed = parsed.unwrap_or_else(|| {
@ -386,9 +386,9 @@ class DefaultProtocolTestGenerator(
});
""",
"copy_from_slice" to RT.Bytes.resolve("copy_from_slice"),
"DynResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::DynResponseDeserializer"),
"SharedResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::ser_de::SharedResponseDeserializer"),
"Operation" to codegenContext.symbolProvider.toSymbol(operationShape),
"ResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::ResponseDeserializer"),
"ResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::ser_de::ResponseDeserializer"),
"RuntimePlugin" to RT.runtimePlugin(rc),
"SdkBody" to RT.sdkBody(rc),
)

View File

@ -37,7 +37,6 @@ class RequestSerializerGenerator(
private val codegenScope by lazy {
val runtimeApi = RuntimeType.smithyRuntimeApi(codegenContext.runtimeConfig)
val interceptorContext = runtimeApi.resolve("client::interceptors::context")
val orchestrator = runtimeApi.resolve("client::orchestrator")
val smithyTypes = RuntimeType.smithyTypes(codegenContext.runtimeConfig)
arrayOf(
*preludeScope,
@ -46,11 +45,11 @@ class RequestSerializerGenerator(
"ConfigBag" to RuntimeType.configBag(codegenContext.runtimeConfig),
"header_util" to RuntimeType.smithyHttp(codegenContext.runtimeConfig).resolve("header"),
"http" to RuntimeType.Http,
"HttpRequest" to orchestrator.resolve("HttpRequest"),
"HttpRequest" to runtimeApi.resolve("client::orchestrator::HttpRequest"),
"HttpRequestBuilder" to RuntimeType.HttpRequestBuilder,
"Input" to interceptorContext.resolve("Input"),
"operation" to RuntimeType.operationModule(codegenContext.runtimeConfig),
"RequestSerializer" to orchestrator.resolve("RequestSerializer"),
"RequestSerializer" to runtimeApi.resolve("client::ser_de::RequestSerializer"),
"SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig),
"HeaderSerializationSettings" to RuntimeType.forInlineDependency(
InlineDependency.serializationSettings(

View File

@ -44,7 +44,7 @@ class ResponseDeserializerGenerator(
"Output" to interceptorContext.resolve("Output"),
"OutputOrError" to interceptorContext.resolve("OutputOrError"),
"OrchestratorError" to orchestrator.resolve("OrchestratorError"),
"ResponseDeserializer" to orchestrator.resolve("ResponseDeserializer"),
"ResponseDeserializer" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::ser_de::ResponseDeserializer"),
"SdkBody" to RuntimeType.sdkBody(runtimeConfig),
"SdkError" to RuntimeType.sdkError(runtimeConfig),
"TypedBox" to RuntimeType.smithyTypes(runtimeConfig).resolve("type_erasure::TypedBox"),

View File

@ -187,7 +187,7 @@ class EndpointsDecoratorTest {
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_client::never::NeverConnector;
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::orchestrator::EndpointResolverParams;
use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_types::config_bag::ConfigBag;
use aws_smithy_types::endpoint::Endpoint;

View File

@ -46,9 +46,8 @@ internal class ConfigOverrideRuntimePluginGeneratorTest {
val runtimeConfig = clientCodegenContext.runtimeConfig
val codegenScope = arrayOf(
*preludeScope,
"ConfigBagAccessors" to RuntimeType.configBagAccessors(runtimeConfig),
"EndpointResolverParams" to RuntimeType.smithyRuntimeApi(runtimeConfig)
.resolve("client::orchestrator::EndpointResolverParams"),
.resolve("client::endpoint::EndpointResolverParams"),
"RuntimePlugin" to RuntimeType.runtimePlugin(runtimeConfig),
)
rustCrate.testModule {
@ -57,7 +56,7 @@ internal class ConfigOverrideRuntimePluginGeneratorTest {
rustTemplate(
"""
use #{RuntimePlugin};
use ::aws_smithy_runtime_api::client::orchestrator::EndpointResolver;
use ::aws_smithy_runtime_api::client::endpoint::EndpointResolver;
let expected_url = "http://localhost:1234/";
let client_config = crate::config::Config::builder().build();
@ -93,7 +92,6 @@ internal class ConfigOverrideRuntimePluginGeneratorTest {
val runtimeConfig = clientCodegenContext.runtimeConfig
val codegenScope = arrayOf(
*preludeScope,
"ConfigBagAccessors" to RuntimeType.configBagAccessors(runtimeConfig),
"RuntimePlugin" to RuntimeType.runtimePlugin(runtimeConfig),
)
rustCrate.testModule {
@ -175,7 +173,6 @@ internal class ConfigOverrideRuntimePluginGeneratorTest {
"AlwaysRetry" to RuntimeType.smithyRuntimeApi(runtimeConfig)
.resolve("client::retries::AlwaysRetry"),
"ConfigBag" to RuntimeType.smithyTypes(runtimeConfig).resolve("config_bag::ConfigBag"),
"ConfigBagAccessors" to RuntimeType.configBagAccessors(runtimeConfig),
"ErrorKind" to RuntimeType.smithyTypes(runtimeConfig).resolve("retry::ErrorKind"),
"InterceptorContext" to RuntimeType.interceptorContext(runtimeConfig),
"Layer" to RuntimeType.smithyTypes(runtimeConfig).resolve("config_bag::Layer"),
@ -184,7 +181,7 @@ internal class ConfigOverrideRuntimePluginGeneratorTest {
"RetryConfig" to RuntimeType.smithyTypes(clientCodegenContext.runtimeConfig)
.resolve("retry::RetryConfig"),
"RequestAttempts" to smithyRuntimeApiTestUtil(runtimeConfig).toType()
.resolve("client::request_attempts::RequestAttempts"),
.resolve("client::retries::RequestAttempts"),
"RetryClassifiers" to RuntimeType.smithyRuntimeApi(runtimeConfig)
.resolve("client::retries::RetryClassifiers"),
"RuntimeComponentsBuilder" to RuntimeType.runtimeComponentsBuilder(runtimeConfig),

View File

@ -102,15 +102,15 @@ private class TestOperationCustomization(
.map_err(|e| #{OrchestratorError}::operation(#{TypedBox}::new(e).erase_error()))
}
}
cfg.store_put(#{DynResponseDeserializer}::new(TestDeser));
cfg.store_put(#{SharedResponseDeserializer}::new(TestDeser));
""",
*preludeScope,
"DynResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::DynResponseDeserializer"),
"SharedResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::ser_de::SharedResponseDeserializer"),
"Error" to RT.smithyRuntimeApi(rc).resolve("client::interceptors::context::Error"),
"HttpResponse" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::HttpResponse"),
"OrchestratorError" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::OrchestratorError"),
"Output" to RT.smithyRuntimeApi(rc).resolve("client::interceptors::context::Output"),
"ResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::orchestrator::ResponseDeserializer"),
"ResponseDeserializer" to RT.smithyRuntimeApi(rc).resolve("client::ser_de::ResponseDeserializer"),
"TypedBox" to RT.smithyTypes(rc).resolve("type_erasure::TypedBox"),
)
}

View File

@ -297,7 +297,9 @@ data class CargoDependency(
fun smithyQuery(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-query")
fun smithyRuntime(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime")
.withFeature("client")
fun smithyRuntimeApi(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime-api")
.withFeature("client")
fun smithyRuntimeApiTestUtil(runtimeConfig: RuntimeConfig) =
smithyRuntimeApi(runtimeConfig).toDevDependency().withFeature("test-util")
fun smithyTypes(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-types")

View File

@ -336,8 +336,6 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)
fun configBag(runtimeConfig: RuntimeConfig): RuntimeType =
smithyTypes(runtimeConfig).resolve("config_bag::ConfigBag")
fun configBagAccessors(runtimeConfig: RuntimeConfig): RuntimeType =
smithyRuntimeApi(runtimeConfig).resolve("client::config_bag_accessors::ConfigBagAccessors")
fun runtimeComponents(runtimeConfig: RuntimeConfig) =
smithyRuntimeApi(runtimeConfig).resolve("client::runtime_components::RuntimeComponents")
fun runtimeComponentsBuilder(runtimeConfig: RuntimeConfig) =

View File

@ -34,17 +34,17 @@ There are two stages to identity and auth:
First, let's establish the aspects of auth that can be configured from the model at codegen time.
- **Data**
- **AuthOptionResolverParams:** parameters required to resolve auth options. These parameters are allowed
- **AuthSchemeOptionResolverParams:** parameters required to resolve auth scheme options. These parameters are allowed
to come from both the client config and the operation input structs.
- **HttpAuthSchemes:** a list of auth schemes that can be used to sign HTTP requests. This information
- **AuthSchemes:** a list of auth schemes that can be used to sign HTTP requests. This information
comes directly from the service model.
- **AuthSchemeProperties:** configuration from the auth scheme for the signer.
- **IdentityResolvers:** list of available identity resolvers.
- **Implementations**
- **IdentityResolver:** resolves an identity for use in authentication.
There can be multiple identity resolvers that need to be selected from.
- **HttpRequestSigner:** a signing implementation that signs a HTTP request.
- **AuthOptionResolver:** resolves a list of auth options for a given operation and its inputs.
- **Signer:** a signing implementation that signs a HTTP request.
- **AuthSchemeOptionResolver:** resolves a list of auth scheme options for a given operation and its inputs.
As it is undocumented (at time of writing), this document assumes that the code generator
creates one service-level runtime plugin, and an operation-level runtime plugin per operation, hence
@ -52,34 +52,34 @@ referred to as the service runtime plugin and operation runtime plugin.
The code generator emits code to add identity resolvers and HTTP auth schemes to the config bag
in the service runtime plugin. It then emits code to register an interceptor in the operation runtime
plugin that reads the operation input to generate the auth option resolver params (which also get added
plugin that reads the operation input to generate the auth scheme option resolver params (which also get added
to the config bag).
### The execution stage
At a high-level, the process of resolving an identity and signing a request looks as follows:
1. Retrieve the `AuthOptionResolverParams` from the config bag. The `AuthOptionResolverParams` allow client
config and operation inputs to play a role in which auth option is selected.
2. Retrieve the `AuthOptionResolver` from the config bag, and use it to resolve the auth options available
with the `AuthOptionResolverParams`. The returned auth options are in priority order.
1. Retrieve the `AuthSchemeOptionResolverParams` from the config bag. The `AuthSchemeOptionResolverParams` allow client
config and operation inputs to play a role in which auth scheme option is selected.
2. Retrieve the `AuthSchemeOptionResolver` from the config bag, and use it to resolve the auth scheme options available
with the `AuthSchemeOptionResolverParams`. The returned auth scheme options are in priority order.
3. Retrieve the `IdentityResolvers` list from the config bag.
4. For each auth option:
1. Attempt to find an HTTP auth scheme for that auth option in the config bag (from the `HttpAuthSchemes` list).
4. For each auth scheme option:
1. Attempt to find an HTTP auth scheme for that auth scheme option in the config bag (from the `AuthSchemes` list).
2. If an auth scheme is found:
1. Use the auth scheme to extract the correct identity resolver from the `IdentityResolvers` list.
2. Retrieve the `HttpRequestSigner` implementation from the auth scheme.
2. Retrieve the `Signer` implementation from the auth scheme.
3. Use the `IdentityResolver` to resolve the identity needed for signing.
4. Sign the request with the identity, and break out of the loop from step #4.
In general, it is assumed that if an HTTP auth scheme exists for an auth option, then an identity resolver
also exists for that auth option. Otherwise, the auth option was configured incorrectly during codegen.
In general, it is assumed that if an HTTP auth scheme exists for an auth scheme option, then an identity resolver
also exists for that auth scheme option. Otherwise, the auth option was configured incorrectly during codegen.
How this looks in Rust
----------------------
The client will use trait objects and dynamic dispatch for the `IdentityResolver`,
`HttpRequestSigner`, and `AuthOptionResolver` implementations. Generics could potentially be used,
`Signer`, and `AuthSchemeOptionResolver` implementations. Generics could potentially be used,
but the number of generic arguments and trait bounds in the orchestrator would balloon to
unmaintainable levels if each configurable implementation in it was made generic.
@ -87,38 +87,37 @@ These traits look like this:
```rust,ignore
#[derive(Clone, Debug)]
pub struct HttpAuthOption {
pub struct AuthSchemeId {
scheme_id: &'static str,
properties: Arc<PropertyBag>,
}
pub trait AuthOptionResolver: Send + Sync + Debug {
fn resolve_auth_options<'a>(
pub trait AuthSchemeOptionResolver: Send + Sync + Debug {
fn resolve_auth_scheme_options<'a>(
&'a self,
params: &AuthOptionResolverParams,
) -> Result<Cow<'a, [HttpAuthOption]>, BoxError>;
params: &AuthSchemeOptionResolverParams,
) -> Result<Cow<'a, [AuthSchemeId]>, BoxError>;
}
pub trait IdentityResolver: Send + Sync + Debug {
// `identity_properties` come from `HttpAuthOption::properties`
fn resolve_identity(&self, identity_properties: &PropertyBag) -> BoxFallibleFut<Identity>;
fn resolve_identity(&self, config: &ConfigBag) -> BoxFallibleFut<Identity>;
}
pub trait HttpRequestSigner: Send + Sync + Debug {
pub trait Signer: Send + Sync + Debug {
/// Return a signed version of the given request using the given identity.
///
/// If the provided identity is incompatible with this signer, an error must be returned.
fn sign_request(
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
// `signing_properties` come from `HttpAuthOption::properties`
signing_properties: &PropertyBag,
auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
runtime_components: &RuntimeComponents,
config_bag: &ConfigBag,
) -> Result<(), BoxError>;
}
```
`IdentityResolver` and `HttpRequestSigner` implementations are both given an `Identity`, but
`IdentityResolver` and `Signer` implementations are both given an `Identity`, but
will need to understand what the concrete data type underlying that identity is. The `Identity` struct
uses a `Arc<dyn Any>` to represent the actual identity data so that generics are not needed in
the traits:
@ -137,11 +136,13 @@ will use downcasting to access the identity data types they understand. For exam
it might look like the following:
```rust,ignore
fn sign_request(
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
signing_properties: &PropertyBag
auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
runtime_components: &RuntimeComponents,
config_bag: &ConfigBag,
) -> Result<(), BoxError> {
let aws_credentials = identity.data::<Credentials>()
.ok_or_else(|| "The SigV4 signer requires AWS credentials")?;
@ -181,8 +182,8 @@ The `expiration` field is a special case that is allowed onto the `Identity` str
cache implementations will always need to be aware of this piece of information, and having it as an `Option`
still allows for non-expiring identities.
Ultimately, this design constrains `HttpRequestSigner` implementations to concrete types. There is no world
where an `HttpRequestSigner` can operate across multiple unknown identity data types via trait, and that
Ultimately, this design constrains `Signer` implementations to concrete types. There is no world
where an `Signer` can operate across multiple unknown identity data types via trait, and that
should be OK since the signer implementation can always be wrapped with an implementation that is aware
of the concrete type provided by the identity resolver, and can do any necessary conversions.

View File

@ -11,6 +11,7 @@ repository = "https://github.com/awslabs/smithy-rs"
[features]
default = []
client = []
http-auth = ["dep:zeroize"]
test-util = []

View File

@ -1,8 +1,14 @@
# aws-smithy-runtime-api
**This crate is UNSTABLE! All internal and external interfaces are subject to change without notice.**
APIs needed to configure and customize the Smithy generated code.
Lightweight crate with traits and types necessary to configure runtime logic in the `aws-smithy-runtime` crate.
Most users will not need to use this crate directly as the most frequently used
APIs are re-exported in the generated clients. However, this crate will be useful
for anyone writing a library for others to use with their generated clients.
If you're needing to depend on this and you're not writing a library for Smithy
generated clients, then please file an issue on [smithy-rs](https://github.com/awslabs/smithy-rs)
as we likely missed re-exporting one of the APIs.
<!-- anchor_start:footer -->
This crate is part of the [AWS SDK for Rust](https://awslabs.github.io/aws-sdk-rust/) and the [smithy-rs](https://github.com/awslabs/smithy-rs) code generator. In most cases, it should not be used directly.

View File

@ -3,35 +3,24 @@
* SPDX-License-Identifier: Apache-2.0
*/
pub mod runtime_components;
/// Client orchestrator configuration accessors for the [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag).
pub mod config_bag_accessors;
pub mod endpoint;
/// Smithy identity used by auth and signing.
pub mod identity;
/// Smithy interceptors for smithy clients.
///
/// Interceptors are lifecycle hooks that can read/modify requests and responses.
pub mod interceptors;
pub mod orchestrator;
/// Smithy code related to retry handling and token bucket.
///
/// This code defines when and how failed requests should be retried. It also defines the behavior
/// used to limit the rate that requests are sent.
pub mod retries;
/// Runtime plugin type definitions.
pub mod runtime_components;
pub mod runtime_plugin;
/// Smithy auth runtime plugins
pub mod auth;
/// A type to track the number of requests sent by the orchestrator for a given operation.
pub mod request_attempts;
/// Smithy connectors and related code.
pub mod connectors;
pub mod ser_de;

View File

@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
//! APIs for request authentication.
use crate::box_error::BoxError;
use crate::client::identity::{Identity, SharedIdentityResolver};
use crate::client::orchestrator::HttpRequest;
@ -14,12 +16,18 @@ use std::borrow::Cow;
use std::fmt;
use std::sync::Arc;
/// Auth schemes for the HTTP `Authorization` header.
#[cfg(feature = "http-auth")]
pub mod http;
pub mod option_resolver;
/// Static auth scheme option resolver.
pub mod static_resolver;
/// New type around an auth scheme ID.
///
/// Each auth scheme must have a unique string identifier associated with it,
/// which is used to refer to auth schemes by the auth scheme option resolver, and
/// also used to select an identity resolver to use.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct AuthSchemeId {
scheme_id: &'static str,
@ -43,71 +51,116 @@ impl From<&'static str> for AuthSchemeId {
}
}
/// Parameters needed to resolve auth scheme options.
///
/// Most generated clients will use the [`StaticAuthSchemeOptionResolver`](static_resolver::StaticAuthSchemeOptionResolver),
/// which doesn't require any parameters for resolution (and has its own empty params struct).
///
/// However, more complex auth scheme resolvers may need modeled parameters in order to resolve
/// the auth scheme options. For those, this params struct holds a type erased box so that any
/// kind of parameters can be contained within, and type casted by the auth scheme option resolver
/// implementation.
#[derive(Debug)]
pub struct AuthOptionResolverParams(TypeErasedBox);
pub struct AuthSchemeOptionResolverParams(TypeErasedBox);
impl AuthOptionResolverParams {
impl AuthSchemeOptionResolverParams {
/// Creates a new [`AuthSchemeOptionResolverParams`].
pub fn new<T: fmt::Debug + Send + Sync + 'static>(params: T) -> Self {
Self(TypedBox::new(params).erase())
}
/// Returns the underlying parameters as the type `T` if they are that type.
pub fn get<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
self.0.downcast_ref()
}
}
impl Storable for AuthOptionResolverParams {
impl Storable for AuthSchemeOptionResolverParams {
type Storer = StoreReplace<Self>;
}
pub trait AuthOptionResolver: Send + Sync + fmt::Debug {
fn resolve_auth_options(
/// Resolver for auth scheme options.
///
/// The orchestrator needs to select an auth scheme to sign requests with, and potentially
/// from several different available auth schemes. Smithy models have a number of ways
/// to specify which operations can use which auth schemes under which conditions, as
/// documented in the [Smithy spec](https://smithy.io/2.0/spec/authentication-traits.html).
///
/// The orchestrator uses the auth scheme option resolver runtime component to resolve
/// an ordered list of options that are available to choose from for a given request.
/// This resolver can be a simple static list, such as with the
/// [`StaticAuthSchemeOptionResolver`](static_resolver::StaticAuthSchemeOptionResolver),
/// or it can be a complex code generated resolver that incorporates parameters from both
/// the model and the resolved endpoint.
pub trait AuthSchemeOptionResolver: Send + Sync + fmt::Debug {
/// Returns a list of available auth scheme options to choose from.
fn resolve_auth_scheme_options(
&self,
params: &AuthOptionResolverParams,
params: &AuthSchemeOptionResolverParams,
) -> Result<Cow<'_, [AuthSchemeId]>, BoxError>;
}
/// A shared auth scheme option resolver.
#[derive(Clone, Debug)]
pub struct SharedAuthOptionResolver(Arc<dyn AuthOptionResolver>);
pub struct SharedAuthSchemeOptionResolver(Arc<dyn AuthSchemeOptionResolver>);
impl SharedAuthOptionResolver {
pub fn new(auth_option_resolver: impl AuthOptionResolver + 'static) -> Self {
Self(Arc::new(auth_option_resolver))
impl SharedAuthSchemeOptionResolver {
/// Creates a new [`SharedAuthSchemeOptionResolver`].
pub fn new(auth_scheme_option_resolver: impl AuthSchemeOptionResolver + 'static) -> Self {
Self(Arc::new(auth_scheme_option_resolver))
}
}
impl AuthOptionResolver for SharedAuthOptionResolver {
fn resolve_auth_options(
impl AuthSchemeOptionResolver for SharedAuthSchemeOptionResolver {
fn resolve_auth_scheme_options(
&self,
params: &AuthOptionResolverParams,
params: &AuthSchemeOptionResolverParams,
) -> Result<Cow<'_, [AuthSchemeId]>, BoxError> {
(*self.0).resolve_auth_options(params)
(*self.0).resolve_auth_scheme_options(params)
}
}
pub trait HttpAuthScheme: Send + Sync + fmt::Debug {
/// An auth scheme.
///
/// Auth schemes have unique identifiers (the `scheme_id`),
/// and provide an identity resolver and a signer.
pub trait AuthScheme: Send + Sync + fmt::Debug {
/// Returns the unique identifier associated with this auth scheme.
///
/// This identifier is used to refer to this auth scheme from the
/// [`AuthSchemeOptionResolver`], and is also associated with
/// identity resolvers in the config.
fn scheme_id(&self) -> AuthSchemeId;
/// Returns the identity resolver that can resolve an identity for this scheme, if one is available.
///
/// The [`AuthScheme`] doesn't actually own an identity resolver. Rather, identity resolvers
/// are configured as runtime components. The auth scheme merely chooses a compatible identity
/// resolver from the runtime components via the [`GetIdentityResolver`] trait. The trait is
/// given rather than the full set of runtime components to prevent complex resolution logic
/// involving multiple components from taking place in this function, since that's not the
/// intended use of this design.
fn identity_resolver(
&self,
identity_resolvers: &dyn GetIdentityResolver,
) -> Option<SharedIdentityResolver>;
fn request_signer(&self) -> &dyn HttpRequestSigner;
/// Returns the signing implementation for this auth scheme.
fn signer(&self) -> &dyn Signer;
}
/// Container for a shared HTTP auth scheme implementation.
/// Container for a shared auth scheme implementation.
#[derive(Clone, Debug)]
pub struct SharedHttpAuthScheme(Arc<dyn HttpAuthScheme>);
pub struct SharedAuthScheme(Arc<dyn AuthScheme>);
impl SharedHttpAuthScheme {
/// Creates a new [`SharedHttpAuthScheme`] from the given auth scheme.
pub fn new(auth_scheme: impl HttpAuthScheme + 'static) -> Self {
impl SharedAuthScheme {
/// Creates a new [`SharedAuthScheme`] from the given auth scheme.
pub fn new(auth_scheme: impl AuthScheme + 'static) -> Self {
Self(Arc::new(auth_scheme))
}
}
impl HttpAuthScheme for SharedHttpAuthScheme {
impl AuthScheme for SharedAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
self.0.scheme_id()
}
@ -119,16 +172,17 @@ impl HttpAuthScheme for SharedHttpAuthScheme {
self.0.identity_resolver(identity_resolvers)
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
self.0.request_signer()
fn signer(&self) -> &dyn Signer {
self.0.signer()
}
}
pub trait HttpRequestSigner: Send + Sync + fmt::Debug {
/// Return a signed version of the given request using the given identity.
/// Signing implementation for an auth scheme.
pub trait Signer: Send + Sync + fmt::Debug {
/// Sign the given request with the given identity, components, and config.
///
/// If the provided identity is incompatible with this signer, an error must be returned.
fn sign_request(
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
@ -140,23 +194,33 @@ pub trait HttpRequestSigner: Send + Sync + fmt::Debug {
/// Endpoint configuration for the selected auth scheme.
///
/// The configuration held by this struct originates from the endpoint rule set in the service model.
///
/// This struct gets added to the request state by the auth orchestrator.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct AuthSchemeEndpointConfig<'a>(Option<&'a Document>);
impl<'a> AuthSchemeEndpointConfig<'a> {
/// Creates a new [`AuthSchemeEndpointConfig`].
pub fn new(config: Option<&'a Document>) -> Self {
Self(config)
}
/// Creates an empty AuthSchemeEndpointConfig.
/// Creates an empty [`AuthSchemeEndpointConfig`].
pub fn empty() -> Self {
Self(None)
}
pub fn config(&self) -> Option<&'a Document> {
/// Returns the endpoint configuration as a [`Document`].
pub fn as_document(&self) -> Option<&'a Document> {
self.0
}
}
impl<'a> From<Option<&'a Document>> for AuthSchemeEndpointConfig<'a> {
fn from(value: Option<&'a Document>) -> Self {
Self(value)
}
}
impl<'a> From<&'a Document> for AuthSchemeEndpointConfig<'a> {
fn from(value: &'a Document) -> Self {
Self(Some(value))
}
}

View File

@ -5,7 +5,14 @@
use crate::client::auth::AuthSchemeId;
/// Auth scheme ID for HTTP API key based authentication.
pub const HTTP_API_KEY_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-api-key-auth");
/// Auth scheme ID for HTTP Basic Auth.
pub const HTTP_BASIC_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-basic-auth");
/// Auth scheme ID for HTTP Bearer Auth.
pub const HTTP_BEARER_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-bearer-auth");
/// Auth scheme ID for HTTP Digest Auth.
pub const HTTP_DIGEST_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-digest-auth");

View File

@ -1,49 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use crate::box_error::BoxError;
use crate::client::auth::{AuthOptionResolver, AuthOptionResolverParams, AuthSchemeId};
use std::borrow::Cow;
/// New-type around a `Vec<HttpAuthOption>` that implements `AuthOptionResolver`.
///
/// This is useful for clients that don't require `AuthOptionResolverParams` to resolve auth options.
#[derive(Debug)]
pub struct StaticAuthOptionResolver {
auth_options: Vec<AuthSchemeId>,
}
impl StaticAuthOptionResolver {
/// Creates a new instance of `StaticAuthOptionResolver`.
pub fn new(auth_options: Vec<AuthSchemeId>) -> Self {
Self { auth_options }
}
}
impl AuthOptionResolver for StaticAuthOptionResolver {
fn resolve_auth_options(
&self,
_params: &AuthOptionResolverParams,
) -> Result<Cow<'_, [AuthSchemeId]>, BoxError> {
Ok(Cow::Borrowed(&self.auth_options))
}
}
/// Empty params to be used with [`StaticAuthOptionResolver`].
#[derive(Debug)]
pub struct StaticAuthOptionResolverParams;
impl StaticAuthOptionResolverParams {
/// Creates a new `StaticAuthOptionResolverParams`.
pub fn new() -> Self {
Self
}
}
impl From<StaticAuthOptionResolverParams> for AuthOptionResolverParams {
fn from(params: StaticAuthOptionResolverParams) -> Self {
AuthOptionResolverParams::new(params)
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use crate::box_error::BoxError;
use crate::client::auth::{AuthSchemeId, AuthSchemeOptionResolver, AuthSchemeOptionResolverParams};
use std::borrow::Cow;
/// New-type around a `Vec<AuthSchemeId>` that implements `AuthSchemeOptionResolver`.
#[derive(Debug)]
pub struct StaticAuthSchemeOptionResolver {
auth_scheme_options: Vec<AuthSchemeId>,
}
impl StaticAuthSchemeOptionResolver {
/// Creates a new instance of `StaticAuthSchemeOptionResolver`.
pub fn new(auth_scheme_options: Vec<AuthSchemeId>) -> Self {
Self {
auth_scheme_options,
}
}
}
impl AuthSchemeOptionResolver for StaticAuthSchemeOptionResolver {
fn resolve_auth_scheme_options(
&self,
_params: &AuthSchemeOptionResolverParams,
) -> Result<Cow<'_, [AuthSchemeId]>, BoxError> {
Ok(Cow::Borrowed(&self.auth_scheme_options))
}
}
/// Empty params to be used with [`StaticAuthSchemeOptionResolver`].
#[derive(Debug)]
pub struct StaticAuthSchemeOptionResolverParams;
impl StaticAuthSchemeOptionResolverParams {
/// Creates a new `StaticAuthSchemeOptionResolverParams`.
pub fn new() -> Self {
Self
}
}
impl From<StaticAuthSchemeOptionResolverParams> for AuthSchemeOptionResolverParams {
fn from(params: StaticAuthSchemeOptionResolverParams) -> Self {
AuthSchemeOptionResolverParams::new(params)
}
}

View File

@ -1,161 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use crate::client::auth::AuthOptionResolverParams;
use crate::client::orchestrator::{
DynResponseDeserializer, EndpointResolverParams, LoadedRequestBody, ResponseDeserializer,
SharedRequestSerializer, NOT_NEEDED,
};
use aws_smithy_types::config_bag::{CloneableLayer, ConfigBag, FrozenLayer, Layer};
// Place traits in a private module so that they can be used in the public API without being a part of the public API.
mod internal {
use aws_smithy_types::config_bag::{
CloneableLayer, ConfigBag, FrozenLayer, Layer, Storable, Store, StoreAppend, StoreReplace,
};
use std::fmt::Debug;
pub trait Settable {
fn unset<T: Send + Sync + Clone + Debug + 'static>(&mut self);
fn store_put<T>(&mut self, value: T)
where
T: Storable<Storer = StoreReplace<T>>;
fn store_append<T>(&mut self, item: T)
where
T: Storable<Storer = StoreAppend<T>>;
}
impl Settable for Layer {
fn unset<T: Send + Sync + Clone + Debug + 'static>(&mut self) {
Layer::unset::<T>(self);
}
fn store_put<T>(&mut self, value: T)
where
T: Storable<Storer = StoreReplace<T>>,
{
Layer::store_put(self, value);
}
fn store_append<T>(&mut self, item: T)
where
T: Storable<Storer = StoreAppend<T>>,
{
Layer::store_append(self, item);
}
}
pub trait Gettable {
fn load<T: Storable>(&self) -> <T::Storer as Store>::ReturnedType<'_>;
}
impl Gettable for ConfigBag {
fn load<T: Storable>(&self) -> <T::Storer as Store>::ReturnedType<'_> {
ConfigBag::load::<T>(self)
}
}
impl Gettable for CloneableLayer {
fn load<T: Storable>(&self) -> <T::Storer as Store>::ReturnedType<'_> {
Layer::load::<T>(self)
}
}
impl Gettable for Layer {
fn load<T: Storable>(&self) -> <T::Storer as Store>::ReturnedType<'_> {
Layer::load::<T>(self)
}
}
impl Gettable for FrozenLayer {
fn load<T: Storable>(&self) -> <T::Storer as Store>::ReturnedType<'_> {
Layer::load::<T>(self)
}
}
}
use internal::{Gettable, Settable};
pub trait ConfigBagAccessors {
fn auth_option_resolver_params(&self) -> &AuthOptionResolverParams
where
Self: Gettable,
{
self.load::<AuthOptionResolverParams>()
.expect("auth option resolver params must be set")
}
fn set_auth_option_resolver_params(
&mut self,
auth_option_resolver_params: AuthOptionResolverParams,
) where
Self: Settable,
{
self.store_put::<AuthOptionResolverParams>(auth_option_resolver_params);
}
fn endpoint_resolver_params(&self) -> &EndpointResolverParams
where
Self: Gettable,
{
self.load::<EndpointResolverParams>()
.expect("endpoint resolver params must be set")
}
fn set_endpoint_resolver_params(&mut self, endpoint_resolver_params: EndpointResolverParams)
where
Self: Settable,
{
self.store_put::<EndpointResolverParams>(endpoint_resolver_params);
}
fn request_serializer(&self) -> SharedRequestSerializer
where
Self: Gettable,
{
self.load::<SharedRequestSerializer>()
.expect("missing request serializer")
.clone()
}
fn set_request_serializer(&mut self, request_serializer: SharedRequestSerializer)
where
Self: Settable,
{
self.store_put::<SharedRequestSerializer>(request_serializer);
}
fn response_deserializer(&self) -> &dyn ResponseDeserializer
where
Self: Gettable,
{
self.load::<DynResponseDeserializer>()
.expect("missing response deserializer")
}
fn set_response_deserializer(&mut self, response_deserializer: DynResponseDeserializer)
where
Self: Settable,
{
self.store_put::<DynResponseDeserializer>(response_deserializer);
}
fn loaded_request_body(&self) -> &LoadedRequestBody
where
Self: Gettable,
{
self.load::<LoadedRequestBody>().unwrap_or(&NOT_NEEDED)
}
fn set_loaded_request_body(&mut self, loaded_request_body: LoadedRequestBody)
where
Self: Settable,
{
self.store_put::<LoadedRequestBody>(loaded_request_body);
}
}
impl ConfigBagAccessors for ConfigBag {}
impl ConfigBagAccessors for FrozenLayer {}
impl ConfigBagAccessors for CloneableLayer {}
impl ConfigBagAccessors for Layer {}

View File

@ -7,20 +7,30 @@ use crate::client::orchestrator::{BoxFuture, HttpRequest, HttpResponse};
use std::fmt;
use std::sync::Arc;
pub trait Connector: Send + Sync + fmt::Debug {
/// Trait with a `call` function that asynchronously converts a request into a response.
///
/// Ordinarily, a connector would use an underlying HTTP library such as [hyper](https://crates.io/crates/hyper),
/// and any associated HTTPS implementation alongside it to service requests.
///
/// However, it can also be useful to create fake connectors implementing this trait
/// for testing.
pub trait HttpConnector: Send + Sync + fmt::Debug {
/// Asynchronously converts a request into a response.
fn call(&self, request: HttpRequest) -> BoxFuture<HttpResponse>;
}
/// A shared [`HttpConnector`] implementation.
#[derive(Clone, Debug)]
pub struct SharedConnector(Arc<dyn Connector>);
pub struct SharedHttpConnector(Arc<dyn HttpConnector>);
impl SharedConnector {
pub fn new(connection: impl Connector + 'static) -> Self {
impl SharedHttpConnector {
/// Returns a new [`SharedHttpConnector`].
pub fn new(connection: impl HttpConnector + 'static) -> Self {
Self(Arc::new(connection))
}
}
impl Connector for SharedConnector {
impl HttpConnector for SharedHttpConnector {
fn call(&self, request: HttpRequest) -> BoxFuture<HttpResponse> {
(*self.0).call(request)
}

View File

@ -0,0 +1,62 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! APIs needed to configure endpoint resolution for clients.
use crate::client::orchestrator::Future;
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use aws_smithy_types::endpoint::Endpoint;
use aws_smithy_types::type_erasure::{TypeErasedBox, TypedBox};
use std::fmt;
use std::sync::Arc;
/// Parameters originating from the Smithy endpoint ruleset required for endpoint resolution.
///
/// The actual endpoint parameters are code generated from the Smithy model, and thus,
/// are not known to the runtime crates. Hence, this struct is really a new-type around
/// a [`TypeErasedBox`] that holds the actual concrete parameters in it.
#[derive(Debug)]
pub struct EndpointResolverParams(TypeErasedBox);
impl EndpointResolverParams {
/// Creates a new [`EndpointResolverParams`] from a concrete parameters instance.
pub fn new<T: fmt::Debug + Send + Sync + 'static>(params: T) -> Self {
Self(TypedBox::new(params).erase())
}
/// Attempts to downcast the underlying concrete parameters to `T` and return it as a reference.
pub fn get<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
self.0.downcast_ref()
}
}
impl Storable for EndpointResolverParams {
type Storer = StoreReplace<Self>;
}
/// Configurable endpoint resolver implementation.
pub trait EndpointResolver: Send + Sync + fmt::Debug {
/// Asynchronously resolves an endpoint to use from the given endpoint parameters.
fn resolve_endpoint(&self, params: &EndpointResolverParams) -> Future<Endpoint>;
}
/// Shared endpoint resolver.
///
/// This is a simple shared ownership wrapper type for the [`EndpointResolver`] trait.
#[derive(Clone, Debug)]
pub struct SharedEndpointResolver(Arc<dyn EndpointResolver>);
impl SharedEndpointResolver {
/// Creates a new [`SharedEndpointResolver`].
pub fn new(endpoint_resolver: impl EndpointResolver + 'static) -> Self {
Self(Arc::new(endpoint_resolver))
}
}
impl EndpointResolver for SharedEndpointResolver {
fn resolve_endpoint(&self, params: &EndpointResolverParams) -> Future<Endpoint> {
self.0.resolve_endpoint(params)
}
}

View File

@ -15,8 +15,19 @@ use std::time::SystemTime;
#[cfg(feature = "http-auth")]
pub mod http;
/// Resolves an identity for a request.
/// Resolver for identities.
///
/// Every [`AuthScheme`](crate::client::auth::AuthScheme) has one or more compatible
/// identity resolvers, which are selected from runtime components by the auth scheme
/// implementation itself.
///
/// The identity resolver must return a [`Future`] with the resolved identity, or an error
/// if resolution failed. There is no optionality for identity resolvers. The identity either
/// resolves successfully, or it fails. The orchestrator will choose exactly one auth scheme
/// to use, and thus, its chosen identity resolver is the only identity resolver that runs.
/// There is no fallback to other auth schemes in the absense of an identity.
pub trait IdentityResolver: Send + Sync + Debug {
/// Asynchronously resolves an identity for a request using the given config.
fn resolve_identity(&self, config_bag: &ConfigBag) -> Future<Identity>;
}
@ -67,6 +78,17 @@ impl ConfiguredIdentityResolver {
}
}
/// An identity that can be used for authentication.
///
/// The [`Identity`] is a container for any arbitrary identity data that may be used
/// by a [`Signer`](crate::client::auth::Signer) implementation. Under the hood, it
/// has an `Arc<dyn Any>`, and it is the responsibility of the signer to downcast
/// to the appropriate data type using the `data()` function.
///
/// The `Identity` also holds an optional expiration time, which may duplicate
/// an expiration time on the identity data. This is because an `Arc<dyn Any>`
/// can't be downcast to any arbitrary trait, and expiring identities are
/// common enough to be built-in.
#[derive(Clone)]
pub struct Identity {
data: Arc<dyn Any + Send + Sync>,
@ -76,6 +98,7 @@ pub struct Identity {
}
impl Identity {
/// Creates a new identity with the given data and expiration time.
pub fn new<T>(data: T, expiration: Option<SystemTime>) -> Self
where
T: Any + Debug + Send + Sync,
@ -87,10 +110,12 @@ impl Identity {
}
}
/// Returns the raw identity data.
pub fn data<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
self.data.downcast_ref()
}
/// Returns the expiration time for this identity, if any.
pub fn expiration(&self) -> Option<&SystemTime> {
self.expiration.as_ref()
}

View File

@ -3,6 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
//! Interceptors for clients.
//!
//! Interceptors are operation lifecycle hooks that can read/modify requests and responses.
use crate::box_error::BoxError;
use crate::client::interceptors::context::{
AfterDeserializationInterceptorContextRef, BeforeDeserializationInterceptorContextMut,

View File

@ -36,6 +36,7 @@ use std::fmt::Debug;
use std::{fmt, mem};
use tracing::{debug, error, trace};
// TODO(enableNewSmithyRuntimeLaunch): New-type `Input`/`Output`/`Error`
pub type Input = TypeErasedBox;
pub type Output = TypeErasedBox;
pub type Error = TypeErasedError;

View File

@ -193,18 +193,22 @@ impl<'a, I, O, E: Debug> From<&'a InterceptorContext<I, O, E>>
}
impl<'a, I, O, E: Debug> FinalizerInterceptorContextRef<'a, I, O, E> {
/// Returns the operation input.
pub fn input(&self) -> Option<&I> {
self.inner.input.as_ref()
}
/// Returns the serialized request.
pub fn request(&self) -> Option<&Request> {
self.inner.request.as_ref()
}
/// Returns the raw response.
pub fn response(&self) -> Option<&Response> {
self.inner.response.as_ref()
}
/// Returns the deserialized operation output or error.
pub fn output_or_error(&self) -> Option<Result<&O, &OrchestratorError<E>>> {
self.inner.output_or_error.as_ref().map(|o| o.as_ref())
}
@ -223,34 +227,42 @@ impl<'a, I, O, E: Debug> From<&'a mut InterceptorContext<I, O, E>>
}
impl<'a, I, O, E: Debug> FinalizerInterceptorContextMut<'a, I, O, E> {
/// Returns the operation input.
pub fn input(&self) -> Option<&I> {
self.inner.input.as_ref()
}
/// Returns the serialized request.
pub fn request(&self) -> Option<&Request> {
self.inner.request.as_ref()
}
/// Returns the raw response.
pub fn response(&self) -> Option<&Response> {
self.inner.response.as_ref()
}
/// Returns the deserialized operation output or error.
pub fn output_or_error(&self) -> Option<Result<&O, &OrchestratorError<E>>> {
self.inner.output_or_error.as_ref().map(|o| o.as_ref())
}
/// Mutably returns the operation input.
pub fn input_mut(&mut self) -> Option<&mut I> {
self.inner.input.as_mut()
}
/// Mutably returns the serialized request.
pub fn request_mut(&mut self) -> Option<&mut Request> {
self.inner.request.as_mut()
}
/// Mutably returns the raw response.
pub fn response_mut(&mut self) -> Option<&mut Response> {
self.inner.response.as_mut()
}
/// Mutably returns the deserialized operation output or error.
pub fn output_or_error_mut(&mut self) -> Option<&mut Result<O, OrchestratorError<E>>> {
self.inner.output_or_error.as_mut()
}

View File

@ -181,6 +181,7 @@ pub struct ContextAttachedError {
}
impl ContextAttachedError {
/// Creates a new `ContextAttachedError` with the given `context` and `source`.
pub fn new(context: impl Into<String>, source: impl Into<BoxError>) -> Self {
Self {
context: context.into(),

View File

@ -3,132 +3,50 @@
* SPDX-License-Identifier: Apache-2.0
*/
//! Client request orchestration.
//!
//! The orchestrator handles the full request/response lifecycle including:
//! - Request serialization
//! - Endpoint resolution
//! - Identity resolution
//! - Signing
//! - Request transmission with retry and timeouts
//! - Response deserialization
//!
//! There are several hook points in the orchestration where [interceptors](crate::client::interceptors)
//! can read and modify the input, request, response, or output/error.
use crate::box_error::BoxError;
use crate::client::interceptors::context::{Error, Input, Output};
use crate::client::interceptors::context::phase::Phase;
use crate::client::interceptors::InterceptorError;
use aws_smithy_async::future::now_or_later::NowOrLater;
use aws_smithy_http::body::SdkBody;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
use aws_smithy_types::endpoint::Endpoint;
use aws_smithy_types::type_erasure::{TypeErasedBox, TypedBox};
use aws_smithy_http::result::{ConnectorError, SdkError};
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use aws_smithy_types::type_erasure::TypeErasedError;
use bytes::Bytes;
use std::fmt;
use std::fmt::Debug;
use std::future::Future as StdFuture;
use std::pin::Pin;
use std::sync::Arc;
/// Errors that can occur while running the orchestrator.
mod error;
pub use error::OrchestratorError;
/// Type alias for the HTTP request type that the orchestrator uses.
pub type HttpRequest = http::Request<SdkBody>;
/// Type alias for the HTTP response type that the orchestrator uses.
pub type HttpResponse = http::Response<SdkBody>;
/// Type alias for boxed futures that are returned from several traits since async trait functions are not stable yet (as of 2023-07-21).
///
/// See [the Rust blog](https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html) for
/// more information on async functions in traits.
pub type BoxFuture<T> = Pin<Box<dyn StdFuture<Output = Result<T, BoxError>> + Send>>;
/// Type alias for futures that are returned from several traits since async trait functions are not stable yet (as of 2023-07-21).
///
/// See [the Rust blog](https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html) for
/// more information on async functions in traits.
pub type Future<T> = NowOrLater<Result<T, BoxError>, BoxFuture<T>>;
pub trait RequestSerializer: Send + Sync + fmt::Debug {
fn serialize_input(&self, input: Input, cfg: &mut ConfigBag) -> Result<HttpRequest, BoxError>;
}
#[derive(Clone, Debug)]
pub struct SharedRequestSerializer(Arc<dyn RequestSerializer>);
impl SharedRequestSerializer {
pub fn new(serializer: impl RequestSerializer + 'static) -> Self {
Self(Arc::new(serializer))
}
}
impl RequestSerializer for SharedRequestSerializer {
fn serialize_input(&self, input: Input, cfg: &mut ConfigBag) -> Result<HttpRequest, BoxError> {
self.0.serialize_input(input, cfg)
}
}
impl Storable for SharedRequestSerializer {
type Storer = StoreReplace<Self>;
}
pub trait ResponseDeserializer: Send + Sync + fmt::Debug {
fn deserialize_streaming(
&self,
response: &mut HttpResponse,
) -> Option<Result<Output, OrchestratorError<Error>>> {
let _ = response;
None
}
fn deserialize_nonstreaming(
&self,
response: &HttpResponse,
) -> Result<Output, OrchestratorError<Error>>;
}
#[derive(Debug)]
pub struct DynResponseDeserializer(Box<dyn ResponseDeserializer>);
impl DynResponseDeserializer {
pub fn new(serializer: impl ResponseDeserializer + 'static) -> Self {
Self(Box::new(serializer))
}
}
impl ResponseDeserializer for DynResponseDeserializer {
fn deserialize_nonstreaming(
&self,
response: &HttpResponse,
) -> Result<Output, OrchestratorError<Error>> {
self.0.deserialize_nonstreaming(response)
}
fn deserialize_streaming(
&self,
response: &mut HttpResponse,
) -> Option<Result<Output, OrchestratorError<Error>>> {
self.0.deserialize_streaming(response)
}
}
impl Storable for DynResponseDeserializer {
type Storer = StoreReplace<Self>;
}
#[derive(Debug)]
pub struct EndpointResolverParams(TypeErasedBox);
impl EndpointResolverParams {
pub fn new<T: fmt::Debug + Send + Sync + 'static>(params: T) -> Self {
Self(TypedBox::new(params).erase())
}
pub fn get<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
self.0.downcast_ref()
}
}
impl Storable for EndpointResolverParams {
type Storer = StoreReplace<Self>;
}
pub trait EndpointResolver: Send + Sync + fmt::Debug {
fn resolve_endpoint(&self, params: &EndpointResolverParams) -> Future<Endpoint>;
}
#[derive(Clone, Debug)]
pub struct SharedEndpointResolver(Arc<dyn EndpointResolver>);
impl SharedEndpointResolver {
pub fn new(endpoint_resolver: impl EndpointResolver + 'static) -> Self {
Self(Arc::new(endpoint_resolver))
}
}
impl EndpointResolver for SharedEndpointResolver {
fn resolve_endpoint(&self, params: &EndpointResolverParams) -> Future<Endpoint> {
self.0.resolve_endpoint(params)
}
}
/// Informs the orchestrator on whether or not the request body needs to be loaded into memory before transmit.
///
/// This enum gets placed into the `ConfigBag` to change the orchestrator behavior.
@ -153,4 +71,143 @@ impl Storable for LoadedRequestBody {
type Storer = StoreReplace<Self>;
}
pub(crate) const NOT_NEEDED: LoadedRequestBody = LoadedRequestBody::NotNeeded;
// TODO(enableNewSmithyRuntimeLaunch): Make OrchestratorError adhere to the errors RFC
/// Errors that can occur while running the orchestrator.
#[derive(Debug)]
#[non_exhaustive]
pub enum OrchestratorError<E> {
/// An error occurred within an interceptor.
Interceptor { err: InterceptorError },
/// An error returned by a service.
Operation { err: E },
/// An error that occurs when a request times out.
Timeout { err: BoxError },
/// An error that occurs when request dispatch fails.
Connector { err: ConnectorError },
/// An error that occurs when a response can't be deserialized.
Response { err: BoxError },
/// A general orchestrator error.
Other { err: BoxError },
}
impl<E: Debug> OrchestratorError<E> {
/// Create a new `OrchestratorError` from a [`BoxError`].
pub fn other(err: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
let err = err.into();
Self::Other { err }
}
/// Create a new `OrchestratorError` from an error received from a service.
pub fn operation(err: E) -> Self {
Self::Operation { err }
}
/// Create a new `OrchestratorError::Interceptor` from an [`InterceptorError`].
pub fn interceptor(err: InterceptorError) -> Self {
Self::Interceptor { err }
}
/// Create a new `OrchestratorError::Timeout` from a [`BoxError`].
pub fn timeout(err: BoxError) -> Self {
Self::Timeout { err }
}
/// Create a new `OrchestratorError::Response` from a [`BoxError`].
pub fn response(err: BoxError) -> Self {
Self::Response { err }
}
/// Create a new `OrchestratorError::Connector` from a [`ConnectorError`].
pub fn connector(err: ConnectorError) -> Self {
Self::Connector { err }
}
/// Convert the `OrchestratorError` into `Some` operation specific error if it is one. Otherwise,
/// return `None`.
pub fn as_operation_error(&self) -> Option<&E> {
match self {
Self::Operation { err } => Some(err),
_ => None,
}
}
/// Convert the `OrchestratorError` into an [`SdkError`].
pub(crate) fn into_sdk_error(
self,
phase: &Phase,
response: Option<HttpResponse>,
) -> SdkError<E, HttpResponse> {
match self {
Self::Interceptor { err } => {
use Phase::*;
match phase {
BeforeSerialization | Serialization => SdkError::construction_failure(err),
BeforeTransmit | Transmit => match response {
Some(response) => SdkError::response_error(err, response),
None => SdkError::dispatch_failure(ConnectorError::other(err.into(), None)),
},
BeforeDeserialization | Deserialization | AfterDeserialization => {
SdkError::response_error(err, response.expect("phase has a response"))
}
}
}
Self::Operation { err } => {
debug_assert!(phase.is_after_deserialization(), "operation errors are a result of successfully receiving and parsing a response from the server. Therefore, we must be in the 'After Deserialization' phase.");
SdkError::service_error(err, response.expect("phase has a response"))
}
Self::Connector { err } => SdkError::dispatch_failure(err),
Self::Timeout { err } => SdkError::timeout_error(err),
Self::Response { err } => SdkError::response_error(err, response.unwrap()),
Self::Other { err } => {
use Phase::*;
match phase {
BeforeSerialization | Serialization => SdkError::construction_failure(err),
BeforeTransmit | Transmit => convert_dispatch_error(err, response),
BeforeDeserialization | Deserialization | AfterDeserialization => {
SdkError::response_error(err, response.expect("phase has a response"))
}
}
}
}
}
}
fn convert_dispatch_error<O>(
err: BoxError,
response: Option<HttpResponse>,
) -> SdkError<O, HttpResponse> {
let err = match err.downcast::<ConnectorError>() {
Ok(connector_error) => {
return SdkError::dispatch_failure(*connector_error);
}
Err(e) => e,
};
match response {
Some(response) => SdkError::response_error(err, response),
None => SdkError::dispatch_failure(ConnectorError::other(err, None)),
}
}
impl<E> From<InterceptorError> for OrchestratorError<E>
where
E: Debug + std::error::Error + 'static,
{
fn from(err: InterceptorError) -> Self {
Self::interceptor(err)
}
}
impl From<TypeErasedError> for OrchestratorError<TypeErasedError> {
fn from(err: TypeErasedError) -> Self {
Self::operation(err)
}
}
impl<E> From<aws_smithy_http::byte_stream::error::Error> for OrchestratorError<E>
where
E: Debug + std::error::Error + 'static,
{
fn from(err: aws_smithy_http::byte_stream::error::Error) -> Self {
Self::other(err)
}
}

View File

@ -1,151 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use super::BoxError;
use crate::client::interceptors::context::phase::Phase;
use crate::client::interceptors::InterceptorError;
use crate::client::orchestrator::HttpResponse;
use aws_smithy_http::result::{ConnectorError, SdkError};
use aws_smithy_types::type_erasure::TypeErasedError;
use std::fmt::Debug;
#[derive(Debug)]
#[non_exhaustive]
pub enum OrchestratorError<E> {
/// An error occurred within an interceptor.
Interceptor { err: InterceptorError },
/// An error returned by a service.
Operation { err: E },
/// An error that occurs when a request times out.
Timeout { err: BoxError },
/// An error that occurs when request dispatch fails.
Connector { err: ConnectorError },
/// An error that occurs when a response can't be deserialized.
Response { err: BoxError },
/// A general orchestrator error.
Other { err: BoxError },
}
impl<E: Debug> OrchestratorError<E> {
/// Create a new `OrchestratorError` from a [`BoxError`].
pub fn other(err: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
let err = err.into();
Self::Other { err }
}
/// Create a new `OrchestratorError` from an error received from a service.
pub fn operation(err: E) -> Self {
Self::Operation { err }
}
/// Create a new `OrchestratorError::Interceptor` from an [`InterceptorError`].
pub fn interceptor(err: InterceptorError) -> Self {
Self::Interceptor { err }
}
/// Create a new `OrchestratorError::Timeout` from a [`BoxError`].
pub fn timeout(err: BoxError) -> Self {
Self::Timeout { err }
}
/// Create a new `OrchestratorError::Response` from a [`BoxError`].
pub fn response(err: BoxError) -> Self {
Self::Response { err }
}
/// Create a new `OrchestratorError::Connector` from a [`ConnectorError`].
pub fn connector(err: ConnectorError) -> Self {
Self::Connector { err }
}
/// Convert the `OrchestratorError` into `Some` operation specific error if it is one. Otherwise,
/// return `None`.
pub fn as_operation_error(&self) -> Option<&E> {
match self {
Self::Operation { err } => Some(err),
_ => None,
}
}
/// Convert the `OrchestratorError` into an [`SdkError`].
pub(crate) fn into_sdk_error(
self,
phase: &Phase,
response: Option<HttpResponse>,
) -> SdkError<E, HttpResponse> {
match self {
Self::Interceptor { err } => {
use Phase::*;
match phase {
BeforeSerialization | Serialization => SdkError::construction_failure(err),
BeforeTransmit | Transmit => match response {
Some(response) => SdkError::response_error(err, response),
None => SdkError::dispatch_failure(ConnectorError::other(err.into(), None)),
},
BeforeDeserialization | Deserialization | AfterDeserialization => {
SdkError::response_error(err, response.expect("phase has a response"))
}
}
}
Self::Operation { err } => {
debug_assert!(phase.is_after_deserialization(), "operation errors are a result of successfully receiving and parsing a response from the server. Therefore, we must be in the 'After Deserialization' phase.");
SdkError::service_error(err, response.expect("phase has a response"))
}
Self::Connector { err } => SdkError::dispatch_failure(err),
Self::Timeout { err } => SdkError::timeout_error(err),
Self::Response { err } => SdkError::response_error(err, response.unwrap()),
Self::Other { err } => {
use Phase::*;
match phase {
BeforeSerialization | Serialization => SdkError::construction_failure(err),
BeforeTransmit | Transmit => convert_dispatch_error(err, response),
BeforeDeserialization | Deserialization | AfterDeserialization => {
SdkError::response_error(err, response.expect("phase has a response"))
}
}
}
}
}
}
fn convert_dispatch_error<O>(
err: BoxError,
response: Option<HttpResponse>,
) -> SdkError<O, HttpResponse> {
let err = match err.downcast::<ConnectorError>() {
Ok(connector_error) => {
return SdkError::dispatch_failure(*connector_error);
}
Err(e) => e,
};
match response {
Some(response) => SdkError::response_error(err, response),
None => SdkError::dispatch_failure(ConnectorError::other(err, None)),
}
}
impl<E> From<InterceptorError> for OrchestratorError<E>
where
E: Debug + std::error::Error + 'static,
{
fn from(err: InterceptorError) -> Self {
Self::interceptor(err)
}
}
impl From<TypeErasedError> for OrchestratorError<TypeErasedError> {
fn from(err: TypeErasedError) -> Self {
Self::operation(err)
}
}
impl<E> From<aws_smithy_http::byte_stream::error::Error> for OrchestratorError<E>
where
E: Debug + std::error::Error + 'static,
{
fn from(err: aws_smithy_http::byte_stream::error::Error) -> Self {
Self::other(err)
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use aws_smithy_types::config_bag::{Storable, StoreReplace};
#[derive(Debug, Clone, Copy)]
pub struct RequestAttempts {
attempts: u32,
}
impl RequestAttempts {
#[cfg(any(feature = "test-util", test))]
pub fn new(attempts: u32) -> Self {
Self { attempts }
}
pub fn attempts(&self) -> u32 {
self.attempts
}
}
impl From<u32> for RequestAttempts {
fn from(attempts: u32) -> Self {
Self { attempts }
}
}
impl Storable for RequestAttempts {
type Storer = StoreReplace<Self>;
}

View File

@ -3,8 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
//! Retry handling and token bucket.
//!
//! This code defines when and how failed requests should be retried. It also defines the behavior
//! used to limit the rate that requests are sent.
use crate::client::interceptors::context::InterceptorContext;
use aws_smithy_types::config_bag::ConfigBag;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
use std::fmt::Debug;
use std::time::Duration;
use tracing::trace;
@ -14,13 +19,17 @@ pub use aws_smithy_types::retry::ErrorKind;
#[derive(Debug, Clone, PartialEq, Eq)]
/// An answer to the question "should I make a request attempt?"
pub enum ShouldAttempt {
/// Yes, an attempt should be made
Yes,
/// No, no attempt should be made
No,
/// Yes, an attempt should be made, but only after the given amount of time has passed
YesAfterDelay(Duration),
}
#[cfg(feature = "test-util")]
impl ShouldAttempt {
/// Returns the delay duration if this is a `YesAfterDelay` variant.
pub fn expect_delay(self) -> Duration {
match self {
ShouldAttempt::YesAfterDelay(delay) => delay,
@ -29,13 +38,26 @@ impl ShouldAttempt {
}
}
/// Decider for whether or not to attempt a request, and when.
///
/// The orchestrator consults the retry strategy every time before making a request.
/// This includes the initial request, and any retry attempts thereafter. The
/// orchestrator will retry indefinitely (until success) if the retry strategy
/// always returns `ShouldAttempt::Yes` from `should_attempt_retry`.
pub trait RetryStrategy: Send + Sync + Debug {
/// Decides if the initial attempt should be made.
fn should_attempt_initial_request(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<ShouldAttempt, BoxError>;
/// Decides if a retry should be done.
///
/// The previous attempt's output or error are provided in the
/// [`InterceptorContext`] when this is called.
///
/// `ShouldAttempt::YesAfterDelay` can be used to add a backoff time.
fn should_attempt_retry(
&self,
context: &InterceptorContext,
@ -44,10 +66,12 @@ pub trait RetryStrategy: Send + Sync + Debug {
) -> Result<ShouldAttempt, BoxError>;
}
/// A shared retry strategy.
#[derive(Clone, Debug)]
pub struct SharedRetryStrategy(Arc<dyn RetryStrategy>);
impl SharedRetryStrategy {
/// Creates a new [`SharedRetryStrategy`] from a retry strategy.
pub fn new(retry_strategy: impl RetryStrategy + 'static) -> Self {
Self(Arc::new(retry_strategy))
}
@ -74,10 +98,13 @@ impl RetryStrategy for SharedRetryStrategy {
}
}
/// Classification result from [`ClassifyRetry`].
#[non_exhaustive]
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum RetryReason {
/// There was an unexpected error, and this is the kind of error so that it can be properly retried.
Error(ErrorKind),
/// The server explicitly told us to back off by this amount of time.
Explicit(Duration),
}
@ -91,12 +118,14 @@ pub trait ClassifyRetry: Send + Sync + Debug {
fn name(&self) -> &'static str;
}
/// Classifies an error into a [`RetryReason`].
#[derive(Clone, Debug)]
pub struct RetryClassifiers {
inner: Vec<Arc<dyn ClassifyRetry>>,
}
impl RetryClassifiers {
/// Creates a new [`RetryClassifiers`].
pub fn new() -> Self {
Self {
// It's always expected that at least one classifier will be defined,
@ -105,6 +134,7 @@ impl RetryClassifiers {
}
}
/// Adds a classifier to this collection.
pub fn with_classifier(mut self, retry_classifier: impl ClassifyRetry + 'static) -> Self {
self.inner.push(Arc::new(retry_classifier));
self
@ -138,6 +168,43 @@ impl ClassifyRetry for RetryClassifiers {
}
}
/// A type to track the number of requests sent by the orchestrator for a given operation.
///
/// `RequestAttempts` is added to the `ConfigBag` by the orchestrator,
/// and holds the current attempt number.
#[derive(Debug, Clone, Copy)]
pub struct RequestAttempts {
attempts: u32,
}
impl RequestAttempts {
/// Creates a new [`RequestAttempts`] with the given number of attempts.
pub fn new(attempts: u32) -> Self {
Self { attempts }
}
/// Returns the number of attempts.
pub fn attempts(&self) -> u32 {
self.attempts
}
}
impl From<u32> for RequestAttempts {
fn from(attempts: u32) -> Self {
Self::new(attempts)
}
}
impl From<RequestAttempts> for u32 {
fn from(value: RequestAttempts) -> Self {
value.attempts()
}
}
impl Storable for RequestAttempts {
type Storer = StoreReplace<Self>;
}
#[cfg(feature = "test-util")]
mod test_util {
use super::{ClassifyRetry, ErrorKind, RetryReason};

View File

@ -3,13 +3,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
//! Runtime components used to make a request and handle a response.
//!
//! Runtime components are trait implementations that are _always_ used by the orchestrator.
//! There are other trait implementations that can be configured for a client, but if they
//! aren't directly and always used by the orchestrator, then they are placed in the
//! [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag) instead of in
//! [`RuntimeComponents`](RuntimeComponents).
use crate::client::auth::{
AuthSchemeId, HttpAuthScheme, SharedAuthOptionResolver, SharedHttpAuthScheme,
AuthScheme, AuthSchemeId, SharedAuthScheme, SharedAuthSchemeOptionResolver,
};
use crate::client::connectors::SharedConnector;
use crate::client::connectors::SharedHttpConnector;
use crate::client::endpoint::SharedEndpointResolver;
use crate::client::identity::{ConfiguredIdentityResolver, SharedIdentityResolver};
use crate::client::interceptors::SharedInterceptor;
use crate::client::orchestrator::SharedEndpointResolver;
use crate::client::retries::{RetryClassifiers, SharedRetryStrategy};
use aws_smithy_async::rt::sleep::SharedAsyncSleep;
use aws_smithy_async::time::SharedTimeSource;
@ -134,6 +142,7 @@ macro_rules! declare_runtime_components {
$($field_name: runtime_component_field_type!($outer_type $inner_type $($option)?),)+
}
/// Builder for [`RuntimeComponents`].
#[derive(Clone, Debug)]
pub struct $builder_name {
builder_name: &'static str,
@ -171,16 +180,16 @@ macro_rules! declare_runtime_components {
declare_runtime_components! {
fields for RuntimeComponents and RuntimeComponentsBuilder {
#[required]
auth_option_resolver: Option<SharedAuthOptionResolver>,
auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
// A connector is not required since a client could technically only be used for presigning
connector: Option<SharedConnector>,
http_connector: Option<SharedHttpConnector>,
#[required]
endpoint_resolver: Option<SharedEndpointResolver>,
#[atLeastOneRequired]
http_auth_schemes: Vec<SharedHttpAuthScheme>,
auth_schemes: Vec<SharedAuthScheme>,
#[atLeastOneRequired]
identity_resolvers: Vec<ConfiguredIdentityResolver>,
@ -204,14 +213,14 @@ impl RuntimeComponents {
RuntimeComponentsBuilder::new(name)
}
/// Returns the auth option resolver.
pub fn auth_option_resolver(&self) -> SharedAuthOptionResolver {
self.auth_option_resolver.value.clone()
/// Returns the auth scheme option resolver.
pub fn auth_scheme_option_resolver(&self) -> SharedAuthSchemeOptionResolver {
self.auth_scheme_option_resolver.value.clone()
}
/// Returns the connector.
pub fn connector(&self) -> Option<SharedConnector> {
self.connector.as_ref().map(|s| s.value.clone())
pub fn http_connector(&self) -> Option<SharedHttpConnector> {
self.http_connector.as_ref().map(|s| s.value.clone())
}
/// Returns the endpoint resolver.
@ -220,8 +229,8 @@ impl RuntimeComponents {
}
/// Returns the requested auth scheme if it is set.
pub fn http_auth_scheme(&self, scheme_id: AuthSchemeId) -> Option<SharedHttpAuthScheme> {
self.http_auth_schemes
pub fn auth_scheme(&self, scheme_id: AuthSchemeId) -> Option<SharedAuthScheme> {
self.auth_schemes
.iter()
.find(|s| s.value.scheme_id() == scheme_id)
.map(|s| s.value.clone())
@ -254,44 +263,46 @@ impl RuntimeComponents {
}
impl RuntimeComponentsBuilder {
/// Returns the auth option resolver.
pub fn auth_option_resolver(&self) -> Option<SharedAuthOptionResolver> {
self.auth_option_resolver.as_ref().map(|s| s.value.clone())
/// Returns the auth scheme option resolver.
pub fn auth_scheme_option_resolver(&self) -> Option<SharedAuthSchemeOptionResolver> {
self.auth_scheme_option_resolver
.as_ref()
.map(|s| s.value.clone())
}
/// Sets the auth option resolver.
pub fn set_auth_option_resolver(
/// Sets the auth scheme option resolver.
pub fn set_auth_scheme_option_resolver(
&mut self,
auth_option_resolver: Option<SharedAuthOptionResolver>,
auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
) -> &mut Self {
self.auth_option_resolver =
auth_option_resolver.map(|r| Tracked::new(self.builder_name, r));
self.auth_scheme_option_resolver =
auth_scheme_option_resolver.map(|r| Tracked::new(self.builder_name, r));
self
}
/// Sets the auth option resolver.
pub fn with_auth_option_resolver(
/// Sets the auth scheme option resolver.
pub fn with_auth_scheme_option_resolver(
mut self,
auth_option_resolver: Option<SharedAuthOptionResolver>,
auth_scheme_option_resolver: Option<SharedAuthSchemeOptionResolver>,
) -> Self {
self.set_auth_option_resolver(auth_option_resolver);
self.set_auth_scheme_option_resolver(auth_scheme_option_resolver);
self
}
/// Returns the connector.
pub fn connector(&self) -> Option<SharedConnector> {
self.connector.as_ref().map(|s| s.value.clone())
/// Returns the HTTP connector.
pub fn http_connector(&self) -> Option<SharedHttpConnector> {
self.http_connector.as_ref().map(|s| s.value.clone())
}
/// Sets the connector.
pub fn set_connector(&mut self, connector: Option<SharedConnector>) -> &mut Self {
self.connector = connector.map(|c| Tracked::new(self.builder_name, c));
/// Sets the HTTP connector.
pub fn set_http_connector(&mut self, connector: Option<SharedHttpConnector>) -> &mut Self {
self.http_connector = connector.map(|c| Tracked::new(self.builder_name, c));
self
}
/// Sets the connector.
pub fn with_connector(mut self, connector: Option<SharedConnector>) -> Self {
self.set_connector(connector);
/// Sets the HTTP connector.
pub fn with_http_connector(mut self, connector: Option<SharedHttpConnector>) -> Self {
self.set_http_connector(connector);
self
}
@ -318,21 +329,21 @@ impl RuntimeComponentsBuilder {
self
}
/// Returns the HTTP auth schemes.
pub fn http_auth_schemes(&self) -> impl Iterator<Item = SharedHttpAuthScheme> + '_ {
self.http_auth_schemes.iter().map(|s| s.value.clone())
/// Returns the auth schemes.
pub fn auth_schemes(&self) -> impl Iterator<Item = SharedAuthScheme> + '_ {
self.auth_schemes.iter().map(|s| s.value.clone())
}
/// Adds a HTTP auth scheme.
pub fn push_http_auth_scheme(&mut self, auth_scheme: SharedHttpAuthScheme) -> &mut Self {
self.http_auth_schemes
/// Adds an auth scheme.
pub fn push_auth_scheme(&mut self, auth_scheme: SharedAuthScheme) -> &mut Self {
self.auth_schemes
.push(Tracked::new(self.builder_name, auth_scheme));
self
}
/// Adds a HTTP auth scheme.
pub fn with_http_auth_scheme(mut self, auth_scheme: SharedHttpAuthScheme) -> Self {
self.push_http_auth_scheme(auth_scheme);
/// Adds an auth scheme.
pub fn with_auth_scheme(mut self, auth_scheme: SharedAuthScheme) -> Self {
self.push_auth_scheme(auth_scheme);
self
}
@ -499,11 +510,12 @@ impl RuntimeComponentsBuilder {
/// Creates a runtime components builder with all the required components filled in with fake (panicking) implementations.
#[cfg(feature = "test-util")]
pub fn for_tests() -> Self {
use crate::client::auth::AuthOptionResolver;
use crate::client::connectors::Connector;
use crate::client::auth::AuthSchemeOptionResolver;
use crate::client::connectors::HttpConnector;
use crate::client::endpoint::{EndpointResolver, EndpointResolverParams};
use crate::client::identity::Identity;
use crate::client::identity::IdentityResolver;
use crate::client::orchestrator::{EndpointResolver, EndpointResolverParams, Future};
use crate::client::orchestrator::Future;
use crate::client::retries::RetryStrategy;
use aws_smithy_async::rt::sleep::AsyncSleep;
use aws_smithy_async::time::TimeSource;
@ -511,20 +523,20 @@ impl RuntimeComponentsBuilder {
use aws_smithy_types::endpoint::Endpoint;
#[derive(Debug)]
struct FakeAuthOptionResolver;
impl AuthOptionResolver for FakeAuthOptionResolver {
fn resolve_auth_options(
struct FakeAuthSchemeOptionResolver;
impl AuthSchemeOptionResolver for FakeAuthSchemeOptionResolver {
fn resolve_auth_scheme_options(
&self,
_: &crate::client::auth::AuthOptionResolverParams,
_: &crate::client::auth::AuthSchemeOptionResolverParams,
) -> Result<std::borrow::Cow<'_, [AuthSchemeId]>, crate::box_error::BoxError>
{
unreachable!("fake auth option resolver must be overridden for this test")
unreachable!("fake auth scheme option resolver must be overridden for this test")
}
}
#[derive(Debug)]
struct FakeConnector;
impl Connector for FakeConnector {
impl HttpConnector for FakeConnector {
fn call(
&self,
_: crate::client::orchestrator::HttpRequest,
@ -543,8 +555,8 @@ impl RuntimeComponentsBuilder {
}
#[derive(Debug)]
struct FakeHttpAuthScheme;
impl HttpAuthScheme for FakeHttpAuthScheme {
struct FakeAuthScheme;
impl AuthScheme for FakeAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
AuthSchemeId::new("fake")
}
@ -556,7 +568,7 @@ impl RuntimeComponentsBuilder {
None
}
fn request_signer(&self) -> &dyn crate::client::auth::HttpRequestSigner {
fn signer(&self) -> &dyn crate::client::auth::Signer {
unreachable!("fake http auth scheme must be overridden for this test")
}
}
@ -609,18 +621,19 @@ impl RuntimeComponentsBuilder {
}
Self::new("aws_smithy_runtime_api::client::runtime_components::RuntimeComponentBuilder::for_tests")
.with_auth_option_resolver(Some(SharedAuthOptionResolver::new(FakeAuthOptionResolver)))
.with_connector(Some(SharedConnector::new(FakeConnector)))
.with_auth_scheme(SharedAuthScheme::new(FakeAuthScheme))
.with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new(FakeAuthSchemeOptionResolver)))
.with_endpoint_resolver(Some(SharedEndpointResolver::new(FakeEndpointResolver)))
.with_http_auth_scheme(SharedHttpAuthScheme::new(FakeHttpAuthScheme))
.with_http_connector(Some(SharedHttpConnector::new(FakeConnector)))
.with_identity_resolver(AuthSchemeId::new("fake"), SharedIdentityResolver::new(FakeIdentityResolver))
.with_retry_classifiers(Some(RetryClassifiers::new()))
.with_retry_strategy(Some(SharedRetryStrategy::new(FakeRetryStrategy)))
.with_time_source(Some(SharedTimeSource::new(FakeTimeSource)))
.with_sleep_impl(Some(SharedAsyncSleep::new(FakeSleep)))
.with_time_source(Some(SharedTimeSource::new(FakeTimeSource)))
}
}
/// An error that occurs when building runtime components.
#[derive(Debug)]
pub struct BuildError(&'static str);
@ -634,7 +647,7 @@ impl fmt::Display for BuildError {
/// A trait for retrieving a shared identity resolver.
///
/// This trait exists so that [`HttpAuthScheme::identity_resolver`](crate::client::auth::HttpAuthScheme::identity_resolver)
/// This trait exists so that [`AuthScheme::identity_resolver`](crate::client::auth::AuthScheme::identity_resolver)
/// can have access to configured identity resolvers without having access to all the runtime components.
pub trait GetIdentityResolver: Send + Sync {
/// Returns the requested identity resolver if it is set.

View File

@ -3,6 +3,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
//! Runtime plugin type definitions.
//!
//! Runtime plugins are used to extend the runtime with custom behavior.
//! This can include:
//! - Registering interceptors
//! - Registering auth schemes
//! - Adding entries to the [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag) for orchestration
//! - Setting runtime components
//!
//! Runtime plugins are divided into service/operation "levels", with service runtime plugins
//! executing before operation runtime plugins. Runtime plugins configured in a service
//! config will always be at the service level, while runtime plugins added during
//! operation customization will be at the operation level. Custom runtime plugins will
//! always run after the default runtime plugins within their level.
use crate::box_error::BoxError;
use crate::client::runtime_components::{
RuntimeComponentsBuilder, EMPTY_RUNTIME_COMPONENTS_BUILDER,
@ -12,25 +27,44 @@ use std::borrow::Cow;
use std::fmt::Debug;
use std::sync::Arc;
/// RuntimePlugin Trait
/// Runtime plugin trait
///
/// A RuntimePlugin is the unit of configuration for augmenting the SDK with new behavior.
/// A `RuntimePlugin` is the unit of configuration for augmenting the SDK with new behavior.
///
/// Runtime plugins can register interceptors, set runtime components, and modify configuration.
pub trait RuntimePlugin: Debug + Send + Sync {
/// Optionally returns additional config that should be added to the [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag).
///
/// As a best practice, a frozen layer should be stored on the runtime plugin instance as
/// a member, and then cloned upon return since that clone is cheap. Constructing a new
/// [`Layer`](aws_smithy_types::config_bag::Layer) and freezing it will require a lot of allocations.
fn config(&self) -> Option<FrozenLayer> {
None
}
/// Returns a [`RuntimeComponentsBuilder`](RuntimeComponentsBuilder) to incorporate into the final runtime components.
///
/// The order of runtime plugins determines which runtime components "win". Components set by later runtime plugins will
/// override those set by earlier runtime plugins.
///
/// If no runtime component changes are desired, just return an empty builder.
///
/// This method returns a [`Cow`] for flexibility. Some implementers may want to store the components builder
/// as a member and return a reference to it, while others may need to create the builder every call. If possible,
/// returning a reference is preferred for performance.
fn runtime_components(&self) -> Cow<'_, RuntimeComponentsBuilder> {
Cow::Borrowed(&EMPTY_RUNTIME_COMPONENTS_BUILDER)
}
}
/// Shared runtime plugin
///
/// Allows for multiple places to share ownership of one runtime plugin.
#[derive(Debug, Clone)]
pub struct SharedRuntimePlugin(Arc<dyn RuntimePlugin>);
impl SharedRuntimePlugin {
/// Returns a new [`SharedRuntimePlugin`].
pub fn new(plugin: impl RuntimePlugin + 'static) -> Self {
Self(Arc::new(plugin))
}
@ -46,6 +80,47 @@ impl RuntimePlugin for SharedRuntimePlugin {
}
}
/// Runtime plugin that simply returns the config and components given at construction time.
#[derive(Default, Debug)]
pub struct StaticRuntimePlugin {
config: Option<FrozenLayer>,
runtime_components: Option<RuntimeComponentsBuilder>,
}
impl StaticRuntimePlugin {
/// Returns a new [`StaticRuntimePlugin`].
pub fn new() -> Self {
Default::default()
}
/// Changes the config.
pub fn with_config(mut self, config: FrozenLayer) -> Self {
self.config = Some(config);
self
}
/// Changes the runtime components.
pub fn with_runtime_components(mut self, runtime_components: RuntimeComponentsBuilder) -> Self {
self.runtime_components = Some(runtime_components);
self
}
}
impl RuntimePlugin for StaticRuntimePlugin {
fn config(&self) -> Option<FrozenLayer> {
self.config.clone()
}
fn runtime_components(&self) -> Cow<'_, RuntimeComponentsBuilder> {
self.runtime_components
.as_ref()
.map(Cow::Borrowed)
.unwrap_or_else(|| RuntimePlugin::runtime_components(self))
}
}
/// Used internally in the orchestrator implementation and in the generated code. Not intended to be used elsewhere.
#[doc(hidden)]
#[derive(Default, Clone, Debug)]
pub struct RuntimePlugins {
client_plugins: Vec<SharedRuntimePlugin>,
@ -99,41 +174,6 @@ impl RuntimePlugins {
}
}
#[derive(Default, Debug)]
pub struct StaticRuntimePlugin {
config: Option<FrozenLayer>,
runtime_components: Option<RuntimeComponentsBuilder>,
}
impl StaticRuntimePlugin {
pub fn new() -> Self {
Default::default()
}
pub fn with_config(mut self, config: FrozenLayer) -> Self {
self.config = Some(config);
self
}
pub fn with_runtime_components(mut self, runtime_components: RuntimeComponentsBuilder) -> Self {
self.runtime_components = Some(runtime_components);
self
}
}
impl RuntimePlugin for StaticRuntimePlugin {
fn config(&self) -> Option<FrozenLayer> {
self.config.clone()
}
fn runtime_components(&self) -> Cow<'_, RuntimeComponentsBuilder> {
self.runtime_components
.as_ref()
.map(Cow::Borrowed)
.unwrap_or_else(|| RuntimePlugin::runtime_components(self))
}
}
#[cfg(test)]
mod tests {
use super::{RuntimePlugin, RuntimePlugins};

View File

@ -0,0 +1,105 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! Serialization/deserialization for the orchestrator.
use crate::box_error::BoxError;
use crate::client::interceptors::context::{Error, Input, Output};
use crate::client::orchestrator::{HttpRequest, HttpResponse, OrchestratorError};
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
use std::fmt;
use std::sync::Arc;
/// Serialization implementation that converts an [`Input`] into an [`HttpRequest`].
pub trait RequestSerializer: Send + Sync + fmt::Debug {
/// Serializes the input into an HTTP request.
///
/// The type of the [`Input`] must be known ahead of time by the request serializer
/// implementation, and must be downcasted to get access to the information necessary
/// for serialization.
///
/// The request serializer is generally added to the [`ConfigBag`] by the operation's
/// code generated runtime plugin, which is aware of the correct input/output/error types.
fn serialize_input(&self, input: Input, cfg: &mut ConfigBag) -> Result<HttpRequest, BoxError>;
}
/// A shared request serializer.
///
/// This is a simple shared ownership wrapper type for the [`RequestSerializer`] trait.
#[derive(Clone, Debug)]
pub struct SharedRequestSerializer(Arc<dyn RequestSerializer>);
impl SharedRequestSerializer {
/// Creates a new shared request serializer.
pub fn new(serializer: impl RequestSerializer + 'static) -> Self {
Self(Arc::new(serializer))
}
}
impl RequestSerializer for SharedRequestSerializer {
fn serialize_input(&self, input: Input, cfg: &mut ConfigBag) -> Result<HttpRequest, BoxError> {
self.0.serialize_input(input, cfg)
}
}
impl Storable for SharedRequestSerializer {
type Storer = StoreReplace<Self>;
}
/// Deserialization implementation that converts an [`HttpResponse`] into an [`Output`] or [`Error`].
pub trait ResponseDeserializer: Send + Sync + fmt::Debug {
/// For streaming requests, deserializes the response headers.
///
/// The orchestrator will call `deserialize_streaming` first, and if it returns `None`,
/// then it will continue onto `deserialize_nonstreaming`. This method should only be
/// implemented for streaming requests where the streaming response body needs to be a part
/// of the deserialized output.
fn deserialize_streaming(
&self,
response: &mut HttpResponse,
) -> Option<Result<Output, OrchestratorError<Error>>> {
let _ = response;
None
}
/// Deserialize the entire response including its body into an output or error.
fn deserialize_nonstreaming(
&self,
response: &HttpResponse,
) -> Result<Output, OrchestratorError<Error>>;
}
/// Shared response deserializer.
///
/// This is a simple shared ownership wrapper type for the [`ResponseDeserializer`] trait.
#[derive(Debug)]
pub struct SharedResponseDeserializer(Arc<dyn ResponseDeserializer>);
impl SharedResponseDeserializer {
/// Creates a new [`SharedResponseDeserializer`].
pub fn new(serializer: impl ResponseDeserializer + 'static) -> Self {
Self(Arc::new(serializer))
}
}
impl ResponseDeserializer for SharedResponseDeserializer {
fn deserialize_nonstreaming(
&self,
response: &HttpResponse,
) -> Result<Output, OrchestratorError<Error>> {
self.0.deserialize_nonstreaming(response)
}
fn deserialize_streaming(
&self,
response: &mut HttpResponse,
) -> Option<Result<Output, OrchestratorError<Error>>> {
self.0.deserialize_streaming(response)
}
}
impl Storable for SharedResponseDeserializer {
type Storer = StoreReplace<Self>;
}

View File

@ -4,6 +4,7 @@
*/
#![warn(
// TODO(enableNewSmithyRuntimeLaunch): Add in remaining missing docs
// missing_docs,
rustdoc::missing_crate_level_docs,
unreachable_pub,
@ -11,12 +12,26 @@
)]
#![allow(clippy::new_without_default)]
//! Basic types for the new smithy client orchestrator.
//! APIs needed to configure and customize the Smithy generated code.
//!
//! Most users will not need to use this crate directly as the most frequently used
//! APIs are re-exported in the generated clients. However, this crate will be useful
//! for anyone writing a library for others to use with their generated clients.
//!
//! If you're needing to depend on this and you're not writing a library for Smithy
//! generated clients, then please file an issue on [smithy-rs](https://github.com/awslabs/smithy-rs)
//! as we likely missed re-exporting one of the APIs.
//!
//! All client-specific code is in the [`client`](crate::client) root level module
//! to leave room for smithy-rs server APIs in the future.
/// A boxed error that is `Send` and `Sync`.
pub mod box_error;
/// Smithy runtime for client orchestration.
/// APIs for client orchestration.
#[cfg(feature = "client")]
pub mod client;
/// Internal builder macros. Not intended to be used outside of the aws-smithy-runtime crates.
#[doc(hidden)]
pub mod macros;

View File

@ -111,6 +111,7 @@
/// }
/// }
/// ```
#[doc(hidden)]
#[macro_export]
macro_rules! builder {
($($tt:tt)+) => {
@ -128,6 +129,7 @@ macro_rules! builder {
/// Define a new builder struct, its fields, and their docs. This macro is intended to be called
/// by the `builder!` macro and should not be called directly.
#[doc(hidden)]
#[macro_export]
macro_rules! builder_struct {
($($_setter_name:ident, $field_name:ident, $ty:ty, $doc:literal $(,)?)+) => {
@ -143,6 +145,7 @@ macro_rules! builder_struct {
/// Define setter methods for a builder struct. Must be called from within an `impl` block. This
/// macro is intended to be called by the `builder!` macro and should not be called directly.
#[doc(hidden)]
#[macro_export]
macro_rules! builder_methods {
($fn_name:ident, $arg_name:ident, $ty:ty, $doc:literal, $($tail:tt)+) => {

View File

@ -10,6 +10,7 @@ repository = "https://github.com/awslabs/smithy-rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
client = ["aws-smithy-runtime-api/client"]
http-auth = ["aws-smithy-runtime-api/http-auth"]
test-util = ["dep:aws-smithy-protocol-test", "dep:tracing-subscriber"]

View File

@ -1,7 +1,5 @@
# aws-smithy-runtime
**This crate is UNSTABLE! All internal and external interfaces are subject to change without notice.**
Runtime support logic and types for smithy-rs generated code.
<!-- anchor_start:footer -->

View File

@ -10,7 +10,7 @@ use aws_smithy_runtime_api::client::auth::http::{
HTTP_DIGEST_AUTH_SCHEME_ID,
};
use aws_smithy_runtime_api::client::auth::{
AuthSchemeEndpointConfig, AuthSchemeId, HttpAuthScheme, HttpRequestSigner,
AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, Signer,
};
use aws_smithy_runtime_api::client::identity::http::{Login, Token};
use aws_smithy_runtime_api::client::identity::{Identity, SharedIdentityResolver};
@ -51,7 +51,7 @@ impl ApiKeyAuthScheme {
}
}
impl HttpAuthScheme for ApiKeyAuthScheme {
impl AuthScheme for ApiKeyAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
HTTP_API_KEY_AUTH_SCHEME_ID
}
@ -63,7 +63,7 @@ impl HttpAuthScheme for ApiKeyAuthScheme {
identity_resolvers.identity_resolver(self.scheme_id())
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
fn signer(&self) -> &dyn Signer {
&self.signer
}
}
@ -75,8 +75,8 @@ struct ApiKeySigner {
name: String,
}
impl HttpRequestSigner for ApiKeySigner {
fn sign_request(
impl Signer for ApiKeySigner {
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
@ -122,7 +122,7 @@ impl BasicAuthScheme {
}
}
impl HttpAuthScheme for BasicAuthScheme {
impl AuthScheme for BasicAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
HTTP_BASIC_AUTH_SCHEME_ID
}
@ -134,7 +134,7 @@ impl HttpAuthScheme for BasicAuthScheme {
identity_resolvers.identity_resolver(self.scheme_id())
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
fn signer(&self) -> &dyn Signer {
&self.signer
}
}
@ -142,8 +142,8 @@ impl HttpAuthScheme for BasicAuthScheme {
#[derive(Debug, Default)]
struct BasicAuthSigner;
impl HttpRequestSigner for BasicAuthSigner {
fn sign_request(
impl Signer for BasicAuthSigner {
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
@ -181,7 +181,7 @@ impl BearerAuthScheme {
}
}
impl HttpAuthScheme for BearerAuthScheme {
impl AuthScheme for BearerAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
HTTP_BEARER_AUTH_SCHEME_ID
}
@ -193,7 +193,7 @@ impl HttpAuthScheme for BearerAuthScheme {
identity_resolvers.identity_resolver(self.scheme_id())
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
fn signer(&self) -> &dyn Signer {
&self.signer
}
}
@ -201,8 +201,8 @@ impl HttpAuthScheme for BearerAuthScheme {
#[derive(Debug, Default)]
struct BearerAuthSigner;
impl HttpRequestSigner for BearerAuthSigner {
fn sign_request(
impl Signer for BearerAuthSigner {
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
@ -238,7 +238,7 @@ impl DigestAuthScheme {
}
}
impl HttpAuthScheme for DigestAuthScheme {
impl AuthScheme for DigestAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
HTTP_DIGEST_AUTH_SCHEME_ID
}
@ -250,7 +250,7 @@ impl HttpAuthScheme for DigestAuthScheme {
identity_resolvers.identity_resolver(self.scheme_id())
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
fn signer(&self) -> &dyn Signer {
&self.signer
}
}
@ -258,8 +258,8 @@ impl HttpAuthScheme for DigestAuthScheme {
#[derive(Debug, Default)]
struct DigestAuthSigner;
impl HttpRequestSigner for DigestAuthSigner {
fn sign_request(
impl Signer for DigestAuthSigner {
fn sign_http_request(
&self,
_request: &mut HttpRequest,
_identity: &Identity,
@ -295,7 +295,7 @@ mod tests {
.body(SdkBody::empty())
.unwrap();
signer
.sign_request(
.sign_http_request(
&mut request,
&identity,
AuthSchemeEndpointConfig::empty(),
@ -325,7 +325,7 @@ mod tests {
.body(SdkBody::empty())
.unwrap();
signer
.sign_request(
.sign_http_request(
&mut request,
&identity,
AuthSchemeEndpointConfig::empty(),
@ -349,7 +349,7 @@ mod tests {
let mut request = http::Request::builder().body(SdkBody::empty()).unwrap();
signer
.sign_request(
.sign_http_request(
&mut request,
&identity,
AuthSchemeEndpointConfig::empty(),
@ -372,7 +372,7 @@ mod tests {
let identity = Identity::new(Token::new("some-token", None), None);
let mut request = http::Request::builder().body(SdkBody::empty()).unwrap();
signer
.sign_request(
.sign_http_request(
&mut request,
&identity,
AuthSchemeEndpointConfig::empty(),

View File

@ -8,7 +8,7 @@
use crate::client::identity::no_auth::NoAuthIdentityResolver;
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::auth::{
AuthSchemeEndpointConfig, AuthSchemeId, HttpAuthScheme, HttpRequestSigner, SharedHttpAuthScheme,
AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, SharedAuthScheme, Signer,
};
use aws_smithy_runtime_api::client::identity::{Identity, SharedIdentityResolver};
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
@ -43,7 +43,7 @@ impl NoAuthRuntimePlugin {
NO_AUTH_SCHEME_ID,
SharedIdentityResolver::new(NoAuthIdentityResolver::new()),
)
.with_http_auth_scheme(SharedHttpAuthScheme::new(NoAuthScheme::new())),
.with_auth_scheme(SharedAuthScheme::new(NoAuthScheme::new())),
)
}
}
@ -68,8 +68,8 @@ impl NoAuthScheme {
#[derive(Debug, Default)]
struct NoAuthSigner;
impl HttpRequestSigner for NoAuthSigner {
fn sign_request(
impl Signer for NoAuthSigner {
fn sign_http_request(
&self,
_request: &mut HttpRequest,
_identity: &Identity,
@ -81,7 +81,7 @@ impl HttpRequestSigner for NoAuthSigner {
}
}
impl HttpAuthScheme for NoAuthScheme {
impl AuthScheme for NoAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
NO_AUTH_SCHEME_ID
}
@ -93,7 +93,7 @@ impl HttpAuthScheme for NoAuthScheme {
identity_resolvers.identity_resolver(NO_AUTH_SCHEME_ID)
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
fn signer(&self) -> &dyn Signer {
&self.signer
}
}

View File

@ -7,9 +7,13 @@ pub mod connection_poisoning;
#[cfg(feature = "test-util")]
pub mod test_util;
// TODO(enableNewSmithyRuntimeCleanup): Delete this module
/// Unstable API for interfacing the old middleware connectors with the newer orchestrator connectors.
///
/// Important: This module and its contents will be removed in the next release.
pub mod adapter {
use aws_smithy_client::erase::DynConnector;
use aws_smithy_runtime_api::client::connectors::Connector;
use aws_smithy_runtime_api::client::connectors::HttpConnector;
use aws_smithy_runtime_api::client::orchestrator::{BoxFuture, HttpRequest, HttpResponse};
use std::sync::{Arc, Mutex};
@ -27,7 +31,7 @@ pub mod adapter {
}
}
impl Connector for DynConnectorAdapter {
impl HttpConnector for DynConnectorAdapter {
fn call(&self, request: HttpRequest) -> BoxFuture<HttpResponse> {
let future = self.dyn_connector.lock().unwrap().call_lite(request);
future

View File

@ -9,7 +9,7 @@ use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
use aws_smithy_http::body::SdkBody;
use aws_smithy_http::result::ConnectorError;
use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType};
use aws_smithy_runtime_api::client::connectors::Connector;
use aws_smithy_runtime_api::client::connectors::HttpConnector;
use aws_smithy_runtime_api::client::orchestrator::{BoxFuture, HttpRequest, HttpResponse};
use http::header::{HeaderName, CONTENT_TYPE};
use std::fmt::Debug;
@ -181,7 +181,7 @@ impl ValidateRequest {
}
}
/// TestConnection for use as a [`Connector`].
/// TestConnection for use as a [`HttpConnector`].
///
/// A basic test connection. It will:
/// - Respond to requests with a preloaded series of responses
@ -222,7 +222,7 @@ impl TestConnection {
}
}
impl Connector for TestConnection {
impl HttpConnector for TestConnection {
fn call(&self, request: HttpRequest) -> BoxFuture<HttpResponse> {
let (res, simulated_latency) = if let Some(event) = self.data.lock().unwrap().pop() {
self.requests.lock().unwrap().push(ValidateRequest {

View File

@ -15,19 +15,20 @@ use aws_smithy_http::body::SdkBody;
use aws_smithy_http::byte_stream::ByteStream;
use aws_smithy_http::result::SdkError;
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::connectors::Connector;
use aws_smithy_runtime_api::client::connectors::HttpConnector;
use aws_smithy_runtime_api::client::interceptors::context::{
Error, Input, InterceptorContext, Output, RewindResult,
};
use aws_smithy_runtime_api::client::interceptors::Interceptors;
use aws_smithy_runtime_api::client::orchestrator::{
DynResponseDeserializer, HttpResponse, LoadedRequestBody, OrchestratorError, RequestSerializer,
ResponseDeserializer, SharedRequestSerializer,
HttpResponse, LoadedRequestBody, OrchestratorError,
};
use aws_smithy_runtime_api::client::request_attempts::RequestAttempts;
use aws_smithy_runtime_api::client::retries::{RetryStrategy, ShouldAttempt};
use aws_smithy_runtime_api::client::retries::{RequestAttempts, RetryStrategy, ShouldAttempt};
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins;
use aws_smithy_runtime_api::client::ser_de::{
RequestSerializer, ResponseDeserializer, SharedRequestSerializer, SharedResponseDeserializer,
};
use aws_smithy_types::config_bag::ConfigBag;
use std::mem;
use tracing::{debug, debug_span, instrument, trace, Instrument};
@ -334,7 +335,7 @@ async fn try_attempt(
let response = halt_on_err!([ctx] => {
let request = ctx.take_request().expect("set during serialization");
trace!(request = ?request, "transmitting request");
let connector = halt_on_err!([ctx] => runtime_components.connector().ok_or_else(||
let connector = halt_on_err!([ctx] => runtime_components.http_connector().ok_or_else(||
OrchestratorError::other("No HTTP connector was available to send this request. \
Enable the `rustls` crate feature or set a connector to fix this.")
));
@ -359,7 +360,7 @@ async fn try_attempt(
let output_or_error = async {
let response = ctx.response_mut().expect("set during transmit");
let response_deserializer = cfg
.load::<DynResponseDeserializer>()
.load::<SharedResponseDeserializer>()
.expect("a request deserializer must be in the config bag");
let maybe_deserialized = {
let _span = debug_span!("deserialize_streaming").entered();
@ -420,11 +421,14 @@ mod tests {
deserializer::CannedResponseDeserializer, serializer::CannedRequestSerializer,
};
use ::http::{Request, Response, StatusCode};
use aws_smithy_runtime_api::client::auth::option_resolver::StaticAuthOptionResolver;
use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolver;
use aws_smithy_runtime_api::client::auth::{
AuthOptionResolverParams, SharedAuthOptionResolver,
AuthSchemeOptionResolverParams, SharedAuthSchemeOptionResolver,
};
use aws_smithy_runtime_api::client::connectors::{HttpConnector, SharedHttpConnector};
use aws_smithy_runtime_api::client::endpoint::{
EndpointResolverParams, SharedEndpointResolver,
};
use aws_smithy_runtime_api::client::connectors::{Connector, SharedConnector};
use aws_smithy_runtime_api::client::interceptors::context::{
AfterDeserializationInterceptorContextRef, BeforeDeserializationInterceptorContextMut,
BeforeDeserializationInterceptorContextRef, BeforeSerializationInterceptorContextMut,
@ -433,10 +437,7 @@ mod tests {
FinalizerInterceptorContextRef,
};
use aws_smithy_runtime_api::client::interceptors::{Interceptor, SharedInterceptor};
use aws_smithy_runtime_api::client::orchestrator::{
BoxFuture, DynResponseDeserializer, EndpointResolverParams, Future, HttpRequest,
SharedEndpointResolver, SharedRequestSerializer,
};
use aws_smithy_runtime_api::client::orchestrator::{BoxFuture, Future, HttpRequest};
use aws_smithy_runtime_api::client::retries::SharedRetryStrategy;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, RuntimePlugins};
@ -474,7 +475,7 @@ mod tests {
}
}
impl Connector for OkConnector {
impl HttpConnector for OkConnector {
fn call(&self, _request: HttpRequest) -> BoxFuture<HttpResponse> {
Box::pin(Future::ready(Ok(::http::Response::builder()
.status(200)
@ -496,9 +497,9 @@ mod tests {
.with_endpoint_resolver(Some(SharedEndpointResolver::new(
StaticUriEndpointResolver::http_localhost(8080),
)))
.with_connector(Some(SharedConnector::new(OkConnector::new())))
.with_auth_option_resolver(Some(SharedAuthOptionResolver::new(
StaticAuthOptionResolver::new(vec![NO_AUTH_SCHEME_ID]),
.with_http_connector(Some(SharedHttpConnector::new(OkConnector::new())))
.with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new(
StaticAuthSchemeOptionResolver::new(vec![NO_AUTH_SCHEME_ID]),
))),
}
}
@ -507,10 +508,10 @@ mod tests {
impl RuntimePlugin for TestOperationRuntimePlugin {
fn config(&self) -> Option<FrozenLayer> {
let mut layer = Layer::new("TestOperationRuntimePlugin");
layer.store_put(AuthOptionResolverParams::new("idontcare"));
layer.store_put(AuthSchemeOptionResolverParams::new("idontcare"));
layer.store_put(EndpointResolverParams::new("dontcare"));
layer.store_put(SharedRequestSerializer::new(new_request_serializer()));
layer.store_put(DynResponseDeserializer::new(new_response_deserializer()));
layer.store_put(SharedResponseDeserializer::new(new_response_deserializer()));
Some(layer.freeze())
}

View File

@ -5,9 +5,9 @@
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::auth::{
AuthOptionResolver, AuthSchemeEndpointConfig, AuthSchemeId, HttpAuthScheme,
AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, AuthSchemeOptionResolver,
AuthSchemeOptionResolverParams,
};
use aws_smithy_runtime_api::client::config_bag_accessors::ConfigBagAccessors;
use aws_smithy_runtime_api::client::identity::IdentityResolver;
use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
@ -49,7 +49,7 @@ impl fmt::Display for AuthOrchestrationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoMatchingAuthScheme => f.write_str(
"no auth scheme matched auth options. This is a bug. Please file an issue.",
"no auth scheme matched auth scheme options. This is a bug. Please file an issue.",
),
Self::BadAuthSchemeEndpointConfig(message) => f.write_str(message),
Self::AuthSchemeEndpointConfigMismatch(supported_schemes) => {
@ -70,24 +70,26 @@ pub(super) async fn orchestrate_auth(
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<(), BoxError> {
let params = cfg.auth_option_resolver_params();
let auth_option_resolver = runtime_components.auth_option_resolver();
let auth_options = auth_option_resolver.resolve_auth_options(params)?;
let params = cfg
.load::<AuthSchemeOptionResolverParams>()
.expect("auth scheme option resolver params must be set");
let option_resolver = runtime_components.auth_scheme_option_resolver();
let options = option_resolver.resolve_auth_scheme_options(params)?;
trace!(
auth_option_resolver_params = ?params,
auth_options = ?auth_options,
auth_scheme_option_resolver_params = ?params,
auth_scheme_options = ?options,
"orchestrating auth",
);
for &scheme_id in auth_options.as_ref() {
if let Some(auth_scheme) = runtime_components.http_auth_scheme(scheme_id) {
for &scheme_id in options.as_ref() {
if let Some(auth_scheme) = runtime_components.auth_scheme(scheme_id) {
if let Some(identity_resolver) = auth_scheme.identity_resolver(runtime_components) {
let request_signer = auth_scheme.request_signer();
let signer = auth_scheme.signer();
trace!(
auth_scheme = ?auth_scheme,
identity_resolver = ?identity_resolver,
request_signer = ?request_signer,
signer = ?signer,
"resolved auth scheme, identity resolver, and signing implementation"
);
@ -103,7 +105,7 @@ pub(super) async fn orchestrate_auth(
trace!("signing request");
let request = ctx.request_mut().expect("set during serialization");
request_signer.sign_request(
signer.sign_http_request(
request,
&identity,
auth_scheme_endpoint_config,
@ -125,7 +127,7 @@ fn extract_endpoint_auth_scheme_config(
let auth_schemes = match endpoint.properties().get("authSchemes") {
Some(Document::Array(schemes)) => schemes,
// no auth schemes:
None => return Ok(AuthSchemeEndpointConfig::new(None)),
None => return Ok(AuthSchemeEndpointConfig::from(None)),
_other => {
return Err(AuthOrchestrationError::BadAuthSchemeEndpointConfig(
"expected an array for `authSchemes` in endpoint config".into(),
@ -144,17 +146,17 @@ fn extract_endpoint_auth_scheme_config(
.ok_or_else(|| {
AuthOrchestrationError::auth_scheme_endpoint_config_mismatch(auth_schemes.iter())
})?;
Ok(AuthSchemeEndpointConfig::new(Some(auth_scheme_config)))
Ok(AuthSchemeEndpointConfig::from(Some(auth_scheme_config)))
}
#[cfg(all(test, feature = "test-util"))]
mod tests {
use super::*;
use aws_smithy_http::body::SdkBody;
use aws_smithy_runtime_api::client::auth::option_resolver::StaticAuthOptionResolver;
use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolver;
use aws_smithy_runtime_api::client::auth::{
AuthOptionResolverParams, AuthSchemeId, HttpAuthScheme, HttpRequestSigner,
SharedAuthOptionResolver, SharedHttpAuthScheme,
AuthScheme, AuthSchemeId, AuthSchemeOptionResolverParams, SharedAuthScheme,
SharedAuthSchemeOptionResolver, Signer,
};
use aws_smithy_runtime_api::client::identity::{
Identity, IdentityResolver, SharedIdentityResolver,
@ -181,8 +183,8 @@ mod tests {
#[derive(Debug)]
struct TestSigner;
impl HttpRequestSigner for TestSigner {
fn sign_request(
impl Signer for TestSigner {
fn sign_http_request(
&self,
request: &mut HttpRequest,
_identity: &Identity,
@ -203,7 +205,7 @@ mod tests {
struct TestAuthScheme {
signer: TestSigner,
}
impl HttpAuthScheme for TestAuthScheme {
impl AuthScheme for TestAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
TEST_SCHEME_ID
}
@ -215,7 +217,7 @@ mod tests {
identity_resolvers.identity_resolver(self.scheme_id())
}
fn request_signer(&self) -> &dyn HttpRequestSigner {
fn signer(&self) -> &dyn Signer {
&self.signer
}
}
@ -227,21 +229,19 @@ mod tests {
ctx.enter_before_transmit_phase();
let runtime_components = RuntimeComponentsBuilder::for_tests()
.with_auth_option_resolver(Some(SharedAuthOptionResolver::new(
StaticAuthOptionResolver::new(vec![TEST_SCHEME_ID]),
.with_auth_scheme(SharedAuthScheme::new(TestAuthScheme { signer: TestSigner }))
.with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new(
StaticAuthSchemeOptionResolver::new(vec![TEST_SCHEME_ID]),
)))
.with_identity_resolver(
TEST_SCHEME_ID,
SharedIdentityResolver::new(TestIdentityResolver),
)
.with_http_auth_scheme(SharedHttpAuthScheme::new(TestAuthScheme {
signer: TestSigner,
}))
.build()
.unwrap();
let mut layer: Layer = Layer::new("test");
layer.store_put(AuthOptionResolverParams::new("doesntmatter"));
layer.store_put(AuthSchemeOptionResolverParams::new("doesntmatter"));
layer.store_put(Endpoint::builder().url("dontcare").build());
let cfg = ConfigBag::of_layers(vec![layer]);
@ -279,10 +279,10 @@ mod tests {
identity: impl IdentityResolver + 'static,
) -> (RuntimeComponents, ConfigBag) {
let runtime_components = RuntimeComponentsBuilder::for_tests()
.with_http_auth_scheme(SharedHttpAuthScheme::new(BasicAuthScheme::new()))
.with_http_auth_scheme(SharedHttpAuthScheme::new(BearerAuthScheme::new()))
.with_auth_option_resolver(Some(SharedAuthOptionResolver::new(
StaticAuthOptionResolver::new(vec![
.with_auth_scheme(SharedAuthScheme::new(BasicAuthScheme::new()))
.with_auth_scheme(SharedAuthScheme::new(BearerAuthScheme::new()))
.with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new(
StaticAuthSchemeOptionResolver::new(vec![
HTTP_BASIC_AUTH_SCHEME_ID,
HTTP_BEARER_AUTH_SCHEME_ID,
]),
@ -293,7 +293,7 @@ mod tests {
let mut layer = Layer::new("test");
layer.store_put(Endpoint::builder().url("dontcare").build());
layer.store_put(AuthOptionResolverParams::new("doesntmatter"));
layer.store_put(AuthSchemeOptionResolverParams::new("doesntmatter"));
(runtime_components, ConfigBag::of_layers(vec![layer]))
}
@ -343,7 +343,7 @@ mod tests {
.build();
let config = extract_endpoint_auth_scheme_config(&endpoint, "test-scheme-id".into())
.expect("success");
assert!(config.config().is_none());
assert!(config.as_document().is_none());
}
#[test]
@ -412,7 +412,7 @@ mod tests {
assert_eq!(
"magic string value",
config
.config()
.as_document()
.expect("config is set")
.as_object()
.expect("it's an object")

View File

@ -9,11 +9,9 @@ use aws_smithy_http::endpoint::{
SharedEndpointResolver,
};
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::config_bag_accessors::ConfigBagAccessors;
use aws_smithy_runtime_api::client::endpoint::{EndpointResolver, EndpointResolverParams};
use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext;
use aws_smithy_runtime_api::client::orchestrator::{
EndpointResolver, EndpointResolverParams, Future, HttpRequest,
};
use aws_smithy_runtime_api::client::orchestrator::{Future, HttpRequest};
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
use aws_smithy_types::endpoint::Endpoint;
@ -109,7 +107,9 @@ pub(super) async fn orchestrate_endpoint(
) -> Result<(), BoxError> {
trace!("orchestrating endpoint resolution");
let params = cfg.endpoint_resolver_params();
let params = cfg
.load::<EndpointResolverParams>()
.expect("endpoint resolver params must be set");
let endpoint_prefix = cfg.load::<EndpointPrefix>();
let request = ctx.request_mut().expect("set during serialization");

View File

@ -5,9 +5,8 @@
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext;
use aws_smithy_runtime_api::client::request_attempts::RequestAttempts;
use aws_smithy_runtime_api::client::retries::{
ClassifyRetry, RetryReason, RetryStrategy, ShouldAttempt,
ClassifyRetry, RequestAttempts, RetryReason, RetryStrategy, ShouldAttempt,
};
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_types::config_bag::ConfigBag;

View File

@ -10,9 +10,8 @@ use crate::client::retries::strategy::standard::ReleaseResult::{
use crate::client::retries::token_bucket::TokenBucket;
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext;
use aws_smithy_runtime_api::client::request_attempts::RequestAttempts;
use aws_smithy_runtime_api::client::retries::{
ClassifyRetry, RetryReason, RetryStrategy, ShouldAttempt,
ClassifyRetry, RequestAttempts, RetryReason, RetryStrategy, ShouldAttempt,
};
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};

View File

@ -3,12 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
use aws_smithy_runtime_api::client::config_bag_accessors::ConfigBagAccessors;
use aws_smithy_runtime_api::client::interceptors::context::{Error, Output};
use aws_smithy_runtime_api::client::orchestrator::{
DynResponseDeserializer, HttpResponse, OrchestratorError, ResponseDeserializer,
};
use aws_smithy_runtime_api::client::orchestrator::{HttpResponse, OrchestratorError};
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
use aws_smithy_runtime_api::client::ser_de::{ResponseDeserializer, SharedResponseDeserializer};
use aws_smithy_types::config_bag::{FrozenLayer, Layer};
use std::sync::Mutex;
@ -46,7 +44,7 @@ impl ResponseDeserializer for CannedResponseDeserializer {
impl RuntimePlugin for CannedResponseDeserializer {
fn config(&self) -> Option<FrozenLayer> {
let mut cfg = Layer::new("CannedResponse");
cfg.set_response_deserializer(DynResponseDeserializer::new(Self {
cfg.store_put(SharedResponseDeserializer::new(Self {
inner: Mutex::new(self.take()),
}));
Some(cfg.freeze())

View File

@ -4,11 +4,10 @@
*/
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::config_bag_accessors::ConfigBagAccessors;
use aws_smithy_runtime_api::client::interceptors::context::Input;
use aws_smithy_runtime_api::client::orchestrator::SharedRequestSerializer;
use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, RequestSerializer};
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
use aws_smithy_runtime_api::client::ser_de::{RequestSerializer, SharedRequestSerializer};
use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer};
use std::sync::Mutex;
@ -52,7 +51,7 @@ impl RequestSerializer for CannedRequestSerializer {
impl RuntimePlugin for CannedRequestSerializer {
fn config(&self) -> Option<FrozenLayer> {
let mut cfg = Layer::new("CannedRequest");
cfg.set_request_serializer(SharedRequestSerializer::new(Self {
cfg.store_put(SharedRequestSerializer::new(Self {
inner: Mutex::new(self.take()),
}));
Some(cfg.freeze())

View File

@ -19,6 +19,7 @@
)]
/// Runtime support logic for generated clients.
#[cfg(feature = "client")]
pub mod client;
pub mod static_partition_map;

View File

@ -22,7 +22,7 @@ async-trait = "0.1"
aws-smithy-http = { path = "../aws-smithy-http" }
aws-smithy-http-server = { path = "../aws-smithy-http-server" }
aws-smithy-json = { path = "../aws-smithy-json" }
aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api" }
aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api", features = ["client"] }
aws-smithy-types = { path = "../aws-smithy-types" }
aws-smithy-xml = { path = "../aws-smithy-xml" }
bytes = "1"