mirror of https://github.com/smithy-lang/smithy-rs
Orchestrator `ResponseDeserializer` codegen and auth (#2494)
* Convert interceptor fns to macro invocations Co-authored-by: John DiSanti <jdisanti@amazon.com> * Add unmodeled error to ServiceError and bring in EventLog Co-authored-by: John DiSanti <jdisanti@amazon.com> * Simplify and test `EventLog` * Attempt to integrate the event log with the orchestrator * fix: error type in integration test update: retry handling in orchestrator * update: set the runtime plugins to do nothing instead of panic when called on * Introduce a construct for type erasure * Eliminate several generics and add phased error handling * Code generate the `ResponseDeserializer` trait impls * CI fixes * Reorganize the new runtime crates * Create the `Identity` type * Reorganize orchestrator traits and accessors * Set up code generated test environment for the new runtime * Add initial auth orchestration implementation * Fix clippy lint * Incorporate feedback * Fix external types lint --------- Co-authored-by: Zelda Hessler <zhessler@amazon.com>
This commit is contained in:
parent
b65a645ce1
commit
6b21e46ef1
|
@ -58,6 +58,12 @@ impl<B> RequestId for http::Response<B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl RequestId for HeaderMap {
|
||||
fn request_id(&self) -> Option<&str> {
|
||||
extract_request_id(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, E> RequestId for Result<O, E>
|
||||
where
|
||||
O: RequestId,
|
||||
|
|
|
@ -59,6 +59,12 @@ impl<B> RequestIdExt for http::Response<B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl RequestIdExt for HeaderMap {
|
||||
fn extended_request_id(&self) -> Option<&str> {
|
||||
extract_extended_request_id(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, E> RequestIdExt for Result<O, E>
|
||||
where
|
||||
O: RequestIdExt,
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
extra["displayName"] = "Smithy :: Rust :: Codegen :: Test"
|
||||
extra["moduleName"] = "software.amazon.smithy.kotlin.codegen.test"
|
||||
extra["displayName"] = "Smithy :: Rust :: AWS-SDK :: Ad-hoc Test"
|
||||
extra["moduleName"] = "software.amazon.smithy.rust.awssdk.adhoc.test"
|
||||
|
||||
tasks["jar"].enabled = false
|
||||
|
||||
|
|
|
@ -83,13 +83,13 @@ abstract class BaseRequestIdDecorator : ClientCodegenDecorator {
|
|||
when (section) {
|
||||
is OperationSection.PopulateErrorMetadataExtras -> {
|
||||
rustTemplate(
|
||||
"${section.builderName} = #{apply_to_error}(${section.builderName}, ${section.responseName}.headers());",
|
||||
"${section.builderName} = #{apply_to_error}(${section.builderName}, ${section.responseHeadersName});",
|
||||
"apply_to_error" to applyToError(codegenContext),
|
||||
)
|
||||
}
|
||||
is OperationSection.MutateOutput -> {
|
||||
rust(
|
||||
"output._set_$fieldName(#T::$accessorFunctionName(response).map(str::to_string));",
|
||||
"output._set_$fieldName(#T::$accessorFunctionName(${section.responseHeadersName}).map(str::to_string));",
|
||||
accessorTrait(codegenContext),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuild
|
|||
import software.amazon.smithy.rust.codegen.core.util.expectMember
|
||||
import software.amazon.smithy.rust.codegen.core.util.getTrait
|
||||
import software.amazon.smithy.rust.codegen.core.util.inputShape
|
||||
import software.amazon.smithy.rust.codegen.core.util.letIf
|
||||
import software.amazon.smithy.rust.codegen.core.util.orNull
|
||||
|
||||
fun RuntimeConfig.awsInlineableBodyWithChecksum() = RuntimeType.forInlineDependency(
|
||||
|
@ -41,12 +42,16 @@ class HttpRequestChecksumDecorator : ClientCodegenDecorator {
|
|||
override val name: String = "HttpRequestChecksum"
|
||||
override val order: Byte = 0
|
||||
|
||||
// TODO(enableNewSmithyRuntime): Implement checksumming via interceptor and delete this decorator
|
||||
private fun applies(codegenContext: ClientCodegenContext): Boolean =
|
||||
!codegenContext.settings.codegenConfig.enableNewSmithyRuntime
|
||||
|
||||
override fun operationCustomizations(
|
||||
codegenContext: ClientCodegenContext,
|
||||
operation: OperationShape,
|
||||
baseCustomizations: List<OperationCustomization>,
|
||||
): List<OperationCustomization> {
|
||||
return baseCustomizations + HttpRequestChecksumCustomization(codegenContext, operation)
|
||||
): List<OperationCustomization> = baseCustomizations.letIf(applies(codegenContext)) {
|
||||
it + HttpRequestChecksumCustomization(codegenContext, operation)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSectio
|
|||
import software.amazon.smithy.rust.codegen.core.util.expectMember
|
||||
import software.amazon.smithy.rust.codegen.core.util.getTrait
|
||||
import software.amazon.smithy.rust.codegen.core.util.inputShape
|
||||
import software.amazon.smithy.rust.codegen.core.util.letIf
|
||||
import software.amazon.smithy.rust.codegen.core.util.orNull
|
||||
|
||||
private fun HttpChecksumTrait.requestValidationModeMember(
|
||||
|
@ -31,12 +32,16 @@ class HttpResponseChecksumDecorator : ClientCodegenDecorator {
|
|||
override val name: String = "HttpResponseChecksum"
|
||||
override val order: Byte = 0
|
||||
|
||||
// TODO(enableNewSmithyRuntime): Implement checksumming via interceptor and delete this decorator
|
||||
private fun applies(codegenContext: ClientCodegenContext): Boolean =
|
||||
!codegenContext.settings.codegenConfig.enableNewSmithyRuntime
|
||||
|
||||
override fun operationCustomizations(
|
||||
codegenContext: ClientCodegenContext,
|
||||
operation: OperationShape,
|
||||
baseCustomizations: List<OperationCustomization>,
|
||||
): List<OperationCustomization> {
|
||||
return baseCustomizations + HttpResponseChecksumCustomization(codegenContext, operation)
|
||||
): List<OperationCustomization> = baseCustomizations.letIf(applies(codegenContext)) {
|
||||
it + HttpResponseChecksumCustomization(codegenContext, operation)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -103,21 +103,21 @@ class S3ProtocolOverride(codegenContext: CodegenContext) : RestXml(codegenContex
|
|||
override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType {
|
||||
return ProtocolFunctions.crossOperationFn("parse_http_error_metadata") { fnName ->
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorBuilder}, #{XmlDecodeError}>",
|
||||
"pub fn $fnName(response_status: u16, _response_headers: &#{HeaderMap}, response_body: &[u8]) -> Result<#{ErrorBuilder}, #{XmlDecodeError}>",
|
||||
*errorScope,
|
||||
) {
|
||||
rustTemplate(
|
||||
"""
|
||||
// S3 HEAD responses have no response body to for an error code. Therefore,
|
||||
// check the HTTP response status and populate an error code for 404s.
|
||||
if response.body().is_empty() {
|
||||
if response_body.is_empty() {
|
||||
let mut builder = #{ErrorMetadata}::builder();
|
||||
if response.status().as_u16() == 404 {
|
||||
if response_status == 404 {
|
||||
builder = builder.code("NotFound");
|
||||
}
|
||||
Ok(builder)
|
||||
} else {
|
||||
#{base_errors}::parse_error_metadata(response.body().as_ref())
|
||||
#{base_errors}::parse_error_metadata(response_body)
|
||||
}
|
||||
""",
|
||||
*errorScope,
|
||||
|
|
|
@ -100,7 +100,8 @@ fun generateSmithyBuild(services: AwsServices): String {
|
|||
"includeFluentClient": false,
|
||||
"renameErrors": false,
|
||||
"eventStreamAllowList": [$eventStreamAllowListMembers],
|
||||
"enableNewCrateOrganizationScheme": true
|
||||
"enableNewCrateOrganizationScheme": true,
|
||||
"enableNewSmithyRuntime": false
|
||||
},
|
||||
"service": "${service.service}",
|
||||
"module": "$moduleName",
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_runtime::{AuthOrchestrator, BoxError};
|
||||
use aws_smithy_runtime_api::client::orchestrator::BoxError;
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetObjectAuthOrc {}
|
||||
|
@ -19,41 +18,7 @@ impl GetObjectAuthOrc {
|
|||
|
||||
impl RuntimePlugin for GetObjectAuthOrc {
|
||||
fn configure(&self, _cfg: &mut ConfigBag) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthOrchestrator<http::Request<SdkBody>> for GetObjectAuthOrc {
|
||||
fn auth_request(
|
||||
&self,
|
||||
_req: &mut http::Request<SdkBody>,
|
||||
_cfg: &ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
|
||||
// let signer = SigV4Signer::new();
|
||||
// let operation_config = props
|
||||
// .get::<OperationSigningConfig>()
|
||||
// .ok_or("missing signing config".to_string())?;
|
||||
//
|
||||
// let (operation_config, request_config, creds) = match &operation_config.signing_requirements
|
||||
// {
|
||||
// SigningRequirements::Disabled => return Ok(()),
|
||||
// SigningRequirements::Optional => {
|
||||
// match aws_sig_auth::middleware::signing_config(props) {
|
||||
// Ok(parts) => parts,
|
||||
// Err(_) => return Ok(()),
|
||||
// }
|
||||
// }
|
||||
// SigningRequirements::Required => {
|
||||
// aws_sig_auth::middleware::signing_config(props).map_err(Box::new)?
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// let _signature = signer
|
||||
// .sign(&operation_config, &request_config, &creds, req)
|
||||
// .expect("signing goes just fine");
|
||||
//
|
||||
// Ok(())
|
||||
// TODO(orchestrator) put an auth orchestrator in the bag
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
use aws_smithy_client::conns::Https;
|
||||
use aws_smithy_client::hyper_ext::Adapter;
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_runtime::{BoxError, BoxFallibleFut, Connection};
|
||||
use aws_smithy_runtime_api::client::orchestrator::{
|
||||
BoxError, BoxFallibleFut, Connection, HttpRequest,
|
||||
};
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HyperConnection {
|
||||
|
@ -17,7 +19,8 @@ pub struct HyperConnection {
|
|||
|
||||
impl RuntimePlugin for HyperConnection {
|
||||
fn configure(&self, _cfg: &mut ConfigBag) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
// TODO(orchestrator) put a connection in the bag
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,10 +32,10 @@ impl HyperConnection {
|
|||
}
|
||||
}
|
||||
|
||||
impl Connection<http::Request<SdkBody>, http::Response<SdkBody>> for HyperConnection {
|
||||
impl Connection for HyperConnection {
|
||||
fn call(
|
||||
&self,
|
||||
_req: &mut http::Request<SdkBody>,
|
||||
_req: &mut HttpRequest,
|
||||
_cfg: &ConfigBag,
|
||||
) -> BoxFallibleFut<http::Response<SdkBody>> {
|
||||
todo!("hyper's connector wants to take ownership of req");
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_sdk_s3::operation::get_object::GetObjectOutput;
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_runtime::{BoxError, ResponseDeserializer};
|
||||
use aws_smithy_runtime_api::client::interceptors::context::OutputOrError;
|
||||
use aws_smithy_runtime_api::client::orchestrator::{BoxError, HttpResponse, ResponseDeserializer};
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetObjectResponseDeserializer {}
|
||||
|
@ -20,356 +19,17 @@ impl GetObjectResponseDeserializer {
|
|||
|
||||
impl RuntimePlugin for GetObjectResponseDeserializer {
|
||||
fn configure(&self, _cfg: &mut ConfigBag) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
// TODO(orchestrator) put a deserializer in the bag
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseDeserializer<http::Response<SdkBody>, GetObjectOutput>
|
||||
for GetObjectResponseDeserializer
|
||||
{
|
||||
fn deserialize_response(
|
||||
&self,
|
||||
_res: &mut http::Response<SdkBody>,
|
||||
_cfg: &ConfigBag,
|
||||
) -> Result<GetObjectOutput, BoxError> {
|
||||
impl ResponseDeserializer for GetObjectResponseDeserializer {
|
||||
fn deserialize_streaming(&self, _response: &mut HttpResponse) -> Option<OutputOrError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn deserialize_nonstreaming(&self, _response: &HttpResponse) -> OutputOrError {
|
||||
todo!()
|
||||
// Ok({
|
||||
// #[allow(unused_mut)]
|
||||
// let mut output = aws_sdk_s3::output::get_object_output::Builder::default();
|
||||
// let _ = res;
|
||||
// output = output.set_accept_ranges(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_accept_ranges(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse AcceptRanges from header `accept-ranges",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_body(Some(
|
||||
// aws_sdk_s3::http_serde::deser_payload_get_object_get_object_output_body(
|
||||
// res.body_mut(),
|
||||
// )?,
|
||||
// ));
|
||||
// output = output.set_bucket_key_enabled(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_bucket_key_enabled(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse BucketKeyEnabled from header `x-amz-server-side-encryption-bucket-key-enabled"))?
|
||||
// );
|
||||
// output = output.set_cache_control(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_cache_control(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse CacheControl from header `Cache-Control",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_checksum_crc32(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_checksum_crc32(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ChecksumCRC32 from header `x-amz-checksum-crc32",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_checksum_crc32_c(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_checksum_crc32_c(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ChecksumCRC32C from header `x-amz-checksum-crc32c",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_checksum_sha1(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_checksum_sha1(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ChecksumSHA1 from header `x-amz-checksum-sha1",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_checksum_sha256(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_checksum_sha256(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ChecksumSHA256 from header `x-amz-checksum-sha256",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_content_disposition(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_content_disposition(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ContentDisposition from header `Content-Disposition",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_content_encoding(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_content_encoding(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ContentEncoding from header `Content-Encoding",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_content_language(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_content_language(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ContentLanguage from header `Content-Language",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_content_length(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_content_length(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ContentLength from header `Content-Length",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_content_range(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_content_range(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ContentRange from header `Content-Range",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_content_type(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_content_type(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ContentType from header `Content-Type",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_delete_marker(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_delete_marker(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse DeleteMarker from header `x-amz-delete-marker",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_e_tag(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_e_tag(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse ETag from header `ETag"))?,
|
||||
// );
|
||||
// output = output.set_expiration(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_expiration(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse Expiration from header `x-amz-expiration",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_expires(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_expires(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled("Failed to parse Expires from header `Expires")
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_last_modified(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_last_modified(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse LastModified from header `Last-Modified",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_metadata(
|
||||
// aws_sdk_s3::http_serde::deser_prefix_header_get_object_get_object_output_metadata(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse Metadata from prefix header `x-amz-meta-",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_missing_meta(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_missing_meta(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse MissingMeta from header `x-amz-missing-meta",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_object_lock_legal_hold_status(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_object_lock_legal_hold_status(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse ObjectLockLegalHoldStatus from header `x-amz-object-lock-legal-hold"))?
|
||||
// );
|
||||
// output = output.set_object_lock_mode(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_object_lock_mode(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ObjectLockMode from header `x-amz-object-lock-mode",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_object_lock_retain_until_date(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_object_lock_retain_until_date(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse ObjectLockRetainUntilDate from header `x-amz-object-lock-retain-until-date"))?
|
||||
// );
|
||||
// output = output.set_parts_count(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_parts_count(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse PartsCount from header `x-amz-mp-parts-count",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_replication_status(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_replication_status(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse ReplicationStatus from header `x-amz-replication-status",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_request_charged(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_request_charged(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse RequestCharged from header `x-amz-request-charged",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_restore(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_restore(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled("Failed to parse Restore from header `x-amz-restore")
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_sse_customer_algorithm(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_sse_customer_algorithm(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse SSECustomerAlgorithm from header `x-amz-server-side-encryption-customer-algorithm"))?
|
||||
// );
|
||||
// output = output.set_sse_customer_key_md5(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_sse_customer_key_md5(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse SSECustomerKeyMD5 from header `x-amz-server-side-encryption-customer-key-MD5"))?
|
||||
// );
|
||||
// output = output.set_ssekms_key_id(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_ssekms_key_id(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse SSEKMSKeyId from header `x-amz-server-side-encryption-aws-kms-key-id"))?
|
||||
// );
|
||||
// output = output.set_server_side_encryption(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_server_side_encryption(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse ServerSideEncryption from header `x-amz-server-side-encryption"))?
|
||||
// );
|
||||
// output = output.set_storage_class(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_storage_class(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse StorageClass from header `x-amz-storage-class",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_tag_count(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_tag_count(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse TagCount from header `x-amz-tagging-count",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_version_id(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_version_id(
|
||||
// res.headers(),
|
||||
// )
|
||||
// .map_err(|_| {
|
||||
// GetObjectError::unhandled(
|
||||
// "Failed to parse VersionId from header `x-amz-version-id",
|
||||
// )
|
||||
// })?,
|
||||
// );
|
||||
// output = output.set_website_redirect_location(
|
||||
// aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_website_redirect_location(res.headers())
|
||||
// .map_err(|_| GetObjectError::unhandled("Failed to parse WebsiteRedirectLocation from header `x-amz-website-redirect-location"))?
|
||||
// );
|
||||
// output._set_extended_request_id(
|
||||
// aws_sdk_s3::s3_request_id::RequestIdExt::extended_request_id(res)
|
||||
// .map(str::to_string),
|
||||
// );
|
||||
// output._set_request_id(
|
||||
// aws_http::request_id::RequestId::request_id(res).map(str::to_string),
|
||||
// );
|
||||
// let response_algorithms = ["crc32", "crc32c", "sha256", "sha1"].as_slice();
|
||||
// let checksum_mode = cfg.get::<aws_sdk_s3::model::ChecksumMode>();
|
||||
// // Per [the spec](https://awslabs.github.io/smithy/1.0/spec/aws/aws-core.html#http-response-checksums),
|
||||
// // we check to see if it's the `ENABLED` variant
|
||||
// if matches!(
|
||||
// checksum_mode,
|
||||
// Some(&aws_sdk_s3::model::ChecksumMode::Enabled)
|
||||
// ) {
|
||||
// if let Some((checksum_algorithm, precalculated_checksum)) =
|
||||
// aws_sdk_s3::http_body_checksum::check_headers_for_precalculated_checksum(
|
||||
// res.headers(),
|
||||
// response_algorithms,
|
||||
// )
|
||||
// {
|
||||
// let bytestream = output.body.take().map(|bytestream| {
|
||||
// bytestream.map(move |sdk_body| {
|
||||
// aws_sdk_s3::http_body_checksum::wrap_body_with_checksum_validator(
|
||||
// sdk_body,
|
||||
// checksum_algorithm,
|
||||
// precalculated_checksum.clone(),
|
||||
// )
|
||||
// })
|
||||
// });
|
||||
// output = output.set_body(bytestream);
|
||||
// }
|
||||
// }
|
||||
// output.build()
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_runtime::{BoxError, EndpointOrchestrator};
|
||||
use aws_smithy_http::event_stream::BoxError;
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetObjectEndpointOrc {}
|
||||
|
@ -19,86 +18,7 @@ impl GetObjectEndpointOrc {
|
|||
|
||||
impl RuntimePlugin for GetObjectEndpointOrc {
|
||||
fn configure(&self, _cfg: &mut ConfigBag) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl EndpointOrchestrator<http::Request<SdkBody>> for GetObjectEndpointOrc {
|
||||
fn resolve_and_apply_endpoint(
|
||||
&self,
|
||||
_req: &mut http::Request<SdkBody>,
|
||||
_cfg: &ConfigBag,
|
||||
) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
// let endpoint = endpoint_resolver.resolve_endpoint(&endpoint_parameters)?;
|
||||
// let (tx_req, props) = ctx
|
||||
// .tx_request_mut()
|
||||
// .expect("We call this after setting the tx request");
|
||||
//
|
||||
// // Apply the endpoint
|
||||
// let uri: Uri = endpoint.url().parse().map_err(|err| {
|
||||
// ResolveEndpointError::from_source("endpoint did not have a valid uri", err)
|
||||
// })?;
|
||||
// apply_endpoint(tx_req.uri_mut(), &uri, props.get::<EndpointPrefix>()).map_err(|err| {
|
||||
// ResolveEndpointError::message(format!(
|
||||
// "failed to apply endpoint `{:?}` to request `{:?}`",
|
||||
// uri, tx_req
|
||||
// ))
|
||||
// .with_source(Some(err.into()))
|
||||
// })?;
|
||||
// for (header_name, header_values) in endpoint.headers() {
|
||||
// tx_req.headers_mut().remove(header_name);
|
||||
// for value in header_values {
|
||||
// tx_req.headers_mut().insert(
|
||||
// HeaderName::from_str(header_name).map_err(|err| {
|
||||
// ResolveEndpointError::message("invalid header name")
|
||||
// .with_source(Some(err.into()))
|
||||
// })?,
|
||||
// HeaderValue::from_str(value).map_err(|err| {
|
||||
// ResolveEndpointError::message("invalid header value")
|
||||
// .with_source(Some(err.into()))
|
||||
// })?,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn resolve_auth_schemes(&self) -> Result<Vec<String>, BoxError> {
|
||||
todo!()
|
||||
|
||||
// let endpoint = endpoint_resolver
|
||||
// .resolve_endpoint(params)
|
||||
// .map_err(SdkError::construction_failure)?;
|
||||
// let auth_schemes = match endpoint.properties().get("authSchemes") {
|
||||
// Some(Document::Array(schemes)) => schemes,
|
||||
// None => {
|
||||
// return Ok(vec![]);
|
||||
// }
|
||||
// _other => {
|
||||
// return Err(SdkError::construction_failure(
|
||||
// "expected bad things".to_string(),
|
||||
// ));
|
||||
// }
|
||||
// };
|
||||
// let auth_schemes = auth_schemes
|
||||
// .iter()
|
||||
// .flat_map(|doc| match doc {
|
||||
// Document::Object(map) => Some(map),
|
||||
// _ => None,
|
||||
// })
|
||||
// .map(|it| {
|
||||
// let name = match it.get("name") {
|
||||
// Some(Document::String(s)) => Some(s.as_str()),
|
||||
// _ => None,
|
||||
// };
|
||||
// AuthSchemeOptions::new(
|
||||
// name.unwrap().to_string(),
|
||||
// /* there are no identity properties yet */
|
||||
// None,
|
||||
// Some(Document::Object(it.clone())),
|
||||
// )
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
// Ok(auth_schemes)
|
||||
// TODO(orchestrator) put an endpoint orchestrator in the bag
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,15 +11,14 @@ mod interceptors;
|
|||
mod retry;
|
||||
mod ser;
|
||||
|
||||
use aws_sdk_s3::operation::get_object::{GetObjectInput, GetObjectOutput};
|
||||
use aws_sdk_s3::operation::get_object::{GetObjectError, GetObjectInput, GetObjectOutput};
|
||||
use aws_sdk_s3::types::ChecksumMode;
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_runtime::{invoke, BoxError};
|
||||
use aws_smithy_runtime::client::orchestrator::invoke;
|
||||
use aws_smithy_runtime_api::client::interceptors::Interceptors;
|
||||
use aws_smithy_runtime_api::client::orchestrator::{BoxError, HttpRequest, HttpResponse};
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::interceptors::Interceptors;
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugins;
|
||||
use std::str::from_utf8;
|
||||
use tracing::info;
|
||||
use aws_smithy_runtime_api::type_erasure::TypedBox;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), BoxError> {
|
||||
|
@ -29,11 +28,14 @@ async fn main() -> Result<(), BoxError> {
|
|||
let sdk_config = aws_config::load_from_env().await;
|
||||
let _service_config = aws_sdk_s3::Config::from(&sdk_config);
|
||||
|
||||
let input = GetObjectInput::builder()
|
||||
.bucket("zhessler-test-bucket")
|
||||
.key("1000-lines.txt")
|
||||
.checksum_mode(ChecksumMode::Enabled)
|
||||
.build()?;
|
||||
let input = TypedBox::new(
|
||||
GetObjectInput::builder()
|
||||
.bucket("zhessler-test-bucket")
|
||||
.key("1000-lines.txt")
|
||||
.checksum_mode(ChecksumMode::Enabled)
|
||||
.build()?,
|
||||
)
|
||||
.erase();
|
||||
|
||||
let mut runtime_plugins = RuntimePlugins::new();
|
||||
|
||||
|
@ -48,18 +50,21 @@ async fn main() -> Result<(), BoxError> {
|
|||
.with_operation_plugin(ser::GetObjectInputSerializer::new());
|
||||
|
||||
let mut cfg = ConfigBag::base();
|
||||
let mut interceptors: Interceptors<
|
||||
GetObjectInput,
|
||||
http::Request<SdkBody>,
|
||||
http::Response<SdkBody>,
|
||||
Result<GetObjectOutput, BoxError>,
|
||||
> = Interceptors::new();
|
||||
let res = invoke(input, &mut interceptors, &runtime_plugins, &mut cfg).await?;
|
||||
|
||||
let body = res.body.collect().await?.to_vec();
|
||||
let body_string = from_utf8(&body)?;
|
||||
|
||||
info!("{body_string}");
|
||||
let mut interceptors: Interceptors<HttpRequest, HttpResponse> = Interceptors::new();
|
||||
let output = TypedBox::<GetObjectOutput>::assume_from(
|
||||
invoke(input, &mut interceptors, &runtime_plugins, &mut cfg)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
err.map_service_error(|err| {
|
||||
TypedBox::<GetObjectError>::assume_from(err)
|
||||
.expect("error is GetObjectError")
|
||||
.unwrap()
|
||||
})
|
||||
})?,
|
||||
)
|
||||
.expect("output is GetObjectOutput")
|
||||
.unwrap();
|
||||
|
||||
dbg!(output);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_sdk_s3::operation::get_object::{GetObjectError, GetObjectOutput};
|
||||
use aws_smithy_runtime::{BoxError, RetryStrategy};
|
||||
use aws_smithy_runtime_api::client::interceptors::InterceptorContext;
|
||||
use aws_smithy_runtime_api::client::orchestrator::{
|
||||
BoxError, HttpRequest, HttpResponse, RetryStrategy,
|
||||
};
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetObjectRetryStrategy {}
|
||||
|
@ -19,14 +21,19 @@ impl GetObjectRetryStrategy {
|
|||
|
||||
impl RuntimePlugin for GetObjectRetryStrategy {
|
||||
fn configure(&self, _cfg: &mut ConfigBag) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
// TODO(orchestrator) put a retry strategy in the bag
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RetryStrategy<Result<GetObjectOutput, GetObjectError>> for GetObjectRetryStrategy {
|
||||
fn should_retry(
|
||||
impl RetryStrategy for GetObjectRetryStrategy {
|
||||
fn should_attempt_initial_request(&self, _cfg: &ConfigBag) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn should_attempt_retry(
|
||||
&self,
|
||||
_res: &Result<GetObjectOutput, GetObjectError>,
|
||||
_context: &InterceptorContext<HttpRequest, HttpResponse>,
|
||||
_cfg: &ConfigBag,
|
||||
) -> Result<bool, BoxError> {
|
||||
todo!()
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_sdk_s3::operation::get_object::GetObjectInput;
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_runtime::{BoxError, RequestSerializer};
|
||||
use aws_smithy_http::event_stream::BoxError;
|
||||
use aws_smithy_runtime_api::client::interceptors::context::Input;
|
||||
use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, RequestSerializer};
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetObjectInputSerializer {}
|
||||
|
@ -20,125 +20,13 @@ impl GetObjectInputSerializer {
|
|||
|
||||
impl RuntimePlugin for GetObjectInputSerializer {
|
||||
fn configure(&self, _cfg: &mut ConfigBag) -> Result<(), BoxError> {
|
||||
todo!()
|
||||
// TODO(orchestrator) put a serializer in the bag
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestSerializer<GetObjectInput, http::Request<SdkBody>> for GetObjectInputSerializer {
|
||||
fn serialize_request(
|
||||
&self,
|
||||
_input: &mut GetObjectInput,
|
||||
_cfg: &ConfigBag,
|
||||
) -> Result<http::Request<SdkBody>, BoxError> {
|
||||
impl RequestSerializer for GetObjectInputSerializer {
|
||||
fn serialize_input(&self, _input: &Input, _cfg: &ConfigBag) -> Result<HttpRequest, BoxError> {
|
||||
todo!()
|
||||
// let request = {
|
||||
// fn uri_base(_input: &GetObjectInput, output: &mut String) -> Result<(), BuildError> {
|
||||
// use std::fmt::Write;
|
||||
//
|
||||
// let input_30 = &_input.key;
|
||||
// let input_30 = input_30
|
||||
// .as_ref()
|
||||
// .ok_or_else(|| BuildError::missing_field("key", "cannot be empty or unset"))?;
|
||||
// let key = aws_smithy_http::label::fmt_string(
|
||||
// input_30,
|
||||
// aws_smithy_http::label::EncodingStrategy::Greedy,
|
||||
// );
|
||||
// if key.is_empty() {
|
||||
// return Err(BuildError::missing_field("key", "cannot be empty or unset"));
|
||||
// }
|
||||
// write!(output, "/{Key}", Key = key).expect("formatting should succeed");
|
||||
// Ok(())
|
||||
// }
|
||||
// fn uri_query(
|
||||
// _input: &GetObjectInput,
|
||||
// mut output: &mut String,
|
||||
// ) -> Result<(), BuildError> {
|
||||
// let mut query = aws_smithy_http::query::Writer::new(&mut output);
|
||||
// query.push_kv("x-id", "GetObject");
|
||||
// if let Some(inner_31) = &_input.response_cache_control {
|
||||
// {
|
||||
// query.push_kv(
|
||||
// "response-cache-control",
|
||||
// &aws_smithy_http::query::fmt_string(&inner_31),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// if let Some(inner_32) = &_input.response_content_disposition {
|
||||
// {
|
||||
// query.push_kv(
|
||||
// "response-content-disposition",
|
||||
// &aws_smithy_http::query::fmt_string(&inner_32),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// if let Some(inner_33) = &_input.response_content_encoding {
|
||||
// {
|
||||
// query.push_kv(
|
||||
// "response-content-encoding",
|
||||
// &aws_smithy_http::query::fmt_string(&inner_33),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// if let Some(inner_34) = &_input.response_content_language {
|
||||
// {
|
||||
// query.push_kv(
|
||||
// "response-content-language",
|
||||
// &aws_smithy_http::query::fmt_string(&inner_34),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// if let Some(inner_35) = &_input.response_content_type {
|
||||
// {
|
||||
// query.push_kv(
|
||||
// "response-content-type",
|
||||
// &aws_smithy_http::query::fmt_string(&inner_35),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// if let Some(inner_36) = &_input.response_expires {
|
||||
// {
|
||||
// query.push_kv(
|
||||
// "response-expires",
|
||||
// &aws_smithy_http::query::fmt_timestamp(
|
||||
// inner_36,
|
||||
// aws_smithy_types::date_time::Format::HttpDate,
|
||||
// )?,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// if let Some(inner_37) = &_input.version_id {
|
||||
// {
|
||||
// query.push_kv("versionId", &aws_smithy_http::query::fmt_string(&inner_37));
|
||||
// }
|
||||
// }
|
||||
// if let Some(inner_38) = &_input.part_number {
|
||||
// if *inner_38 != 0 {
|
||||
// query.push_kv(
|
||||
// "partNumber",
|
||||
// aws_smithy_types::primitive::Encoder::from(*inner_38).encode(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
//
|
||||
// fn update_http_builder(
|
||||
// input: &GetObjectInput,
|
||||
// builder: http::request::Builder,
|
||||
// ) -> Result<http::request::Builder, BuildError> {
|
||||
// let mut uri = String::new();
|
||||
// uri_base(input, &mut uri)?;
|
||||
// uri_query(input, &mut uri)?;
|
||||
// let builder = aws_sdk_s3::http_serde::add_headers_get_object(input, builder)?;
|
||||
// Ok(builder.method("GET").uri(uri))
|
||||
// }
|
||||
// let builder = update_http_builder(&input, http::request::Builder::new())?;
|
||||
// builder
|
||||
// };
|
||||
//
|
||||
// let _properties = aws_smithy_http::property_bag::SharedPropertyBag::new();
|
||||
// #[allow(clippy::useless_conversion)]
|
||||
// let body = aws_smithy_http::body::SdkBody::from("");
|
||||
// Ok(request.body(body).expect("should be valid request"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/smithy-build.json
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
extra["displayName"] = "Smithy :: Rust :: AWS-SDK :: SRA Test"
|
||||
extra["moduleName"] = "software.amazon.smithy.rust.awssdk.sra.test"
|
||||
|
||||
tasks["jar"].enabled = false
|
||||
|
||||
plugins {
|
||||
id("software.amazon.smithy")
|
||||
}
|
||||
|
||||
val smithyVersion: String by project
|
||||
val defaultRustDocFlags: String by project
|
||||
val properties = PropertyRetriever(rootProject, project)
|
||||
|
||||
val pluginName = "rust-client-codegen"
|
||||
val workingDirUnderBuildDir = "smithyprojections/sdk-sra-test/"
|
||||
|
||||
configure<software.amazon.smithy.gradle.SmithyExtension> {
|
||||
outputDirectory = file("$buildDir/$workingDirUnderBuildDir")
|
||||
}
|
||||
|
||||
buildscript {
|
||||
val smithyVersion: String by project
|
||||
dependencies {
|
||||
classpath("software.amazon.smithy:smithy-cli:$smithyVersion")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":aws:sdk-codegen"))
|
||||
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
|
||||
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
|
||||
}
|
||||
|
||||
val allCodegenTests = listOf(
|
||||
CodegenTest(
|
||||
"com.amazonaws.dynamodb#DynamoDB_20120810",
|
||||
"aws-sdk-dynamodb",
|
||||
imports = listOf("../sdk/aws-models/dynamodb.json"),
|
||||
extraConfig = """
|
||||
,
|
||||
"codegen": {
|
||||
"includeFluentClient": false,
|
||||
"enableNewSmithyRuntime": true
|
||||
},
|
||||
"customizationConfig": {
|
||||
"awsSdk": {
|
||||
"generateReadme": false
|
||||
}
|
||||
}
|
||||
""",
|
||||
),
|
||||
CodegenTest(
|
||||
"com.amazonaws.s3#AmazonS3",
|
||||
"aws-sdk-s3",
|
||||
imports = listOf("../sdk/aws-models/s3.json", "../sdk/aws-models/s3-tests.smithy"),
|
||||
extraConfig = """
|
||||
,
|
||||
"codegen": {
|
||||
"includeFluentClient": false,
|
||||
"enableNewSmithyRuntime": true
|
||||
},
|
||||
"customizationConfig": {
|
||||
"awsSdk": {
|
||||
"generateReadme": false
|
||||
}
|
||||
}
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
project.registerGenerateSmithyBuildTask(rootProject, pluginName, allCodegenTests)
|
||||
project.registerGenerateCargoWorkspaceTask(rootProject, pluginName, allCodegenTests, workingDirUnderBuildDir)
|
||||
project.registerGenerateCargoConfigTomlTask(buildDir.resolve(workingDirUnderBuildDir))
|
||||
|
||||
tasks["smithyBuildJar"].dependsOn("generateSmithyBuild")
|
||||
tasks["assemble"].finalizedBy("generateCargoWorkspace")
|
||||
|
||||
project.registerModifyMtimeTask()
|
||||
project.registerCargoCommandsTasks(buildDir.resolve(workingDirUnderBuildDir), defaultRustDocFlags)
|
||||
|
||||
tasks["test"].finalizedBy(cargoCommands(properties).map { it.toString })
|
||||
|
||||
tasks["clean"].doFirst { delete("smithy-build.json") }
|
|
@ -88,6 +88,8 @@ data class ClientCodegenConfig(
|
|||
val eventStreamAllowList: Set<String> = defaultEventStreamAllowList,
|
||||
// TODO(CrateReorganization): Remove this once we commit to the breaking change
|
||||
val enableNewCrateOrganizationScheme: Boolean = defaultEnableNewCrateOrganizationScheme,
|
||||
// TODO(SmithyRuntime): Remove this once we commit to switch to aws-smithy-runtime and aws-smithy-runtime-api
|
||||
val enableNewSmithyRuntime: Boolean = defaultEnableNewSmithyRuntime,
|
||||
) : CoreCodegenConfig(
|
||||
formatTimeoutSeconds, debugMode,
|
||||
) {
|
||||
|
@ -97,6 +99,7 @@ data class ClientCodegenConfig(
|
|||
private const val defaultAddMessageToErrors = true
|
||||
private val defaultEventStreamAllowList: Set<String> = emptySet()
|
||||
private const val defaultEnableNewCrateOrganizationScheme = true
|
||||
private const val defaultEnableNewSmithyRuntime = false
|
||||
|
||||
fun fromCodegenConfigAndNode(coreCodegenConfig: CoreCodegenConfig, node: Optional<ObjectNode>) =
|
||||
if (node.isPresent) {
|
||||
|
@ -110,13 +113,12 @@ data class ClientCodegenConfig(
|
|||
includeFluentClient = node.get().getBooleanMemberOrDefault("includeFluentClient", defaultIncludeFluentClient),
|
||||
addMessageToErrors = node.get().getBooleanMemberOrDefault("addMessageToErrors", defaultAddMessageToErrors),
|
||||
enableNewCrateOrganizationScheme = node.get().getBooleanMemberOrDefault("enableNewCrateOrganizationScheme", defaultEnableNewCrateOrganizationScheme),
|
||||
enableNewSmithyRuntime = node.get().getBooleanMemberOrDefault("enableNewSmithyRuntime", defaultEnableNewSmithyRuntime),
|
||||
)
|
||||
} else {
|
||||
ClientCodegenConfig(
|
||||
formatTimeoutSeconds = coreCodegenConfig.formatTimeoutSeconds,
|
||||
debugMode = coreCodegenConfig.debugMode,
|
||||
eventStreamAllowList = defaultEventStreamAllowList,
|
||||
enableNewCrateOrganizationScheme = defaultEnableNewCrateOrganizationScheme,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.http.Respons
|
|||
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator
|
||||
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.MakeOperationGenerator
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.assignment
|
||||
|
@ -23,7 +24,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
|
|||
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.withBlock
|
||||
import software.amazon.smithy.rust.codegen.core.rustlang.writable
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
|
||||
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
|
||||
|
@ -63,7 +63,7 @@ class HttpBoundProtocolGenerator(
|
|||
)
|
||||
|
||||
class HttpBoundProtocolTraitImplGenerator(
|
||||
private val codegenContext: CodegenContext,
|
||||
private val codegenContext: ClientCodegenContext,
|
||||
private val protocol: Protocol,
|
||||
) : ProtocolTraitImplGenerator {
|
||||
private val symbolProvider = codegenContext.symbolProvider
|
||||
|
@ -78,7 +78,27 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
"http" to RuntimeType.Http,
|
||||
"operation" to RuntimeType.operationModule(runtimeConfig),
|
||||
"Bytes" to RuntimeType.Bytes,
|
||||
"SdkBody" to RuntimeType.sdkBody(runtimeConfig),
|
||||
)
|
||||
private val orchestratorCodegenScope by lazy {
|
||||
val interceptorContext =
|
||||
CargoDependency.smithyRuntimeApi(runtimeConfig).toType().resolve("client::interceptors::context")
|
||||
val orchestrator =
|
||||
CargoDependency.smithyRuntimeApi(runtimeConfig).toType().resolve("client::orchestrator")
|
||||
arrayOf(
|
||||
"Error" to interceptorContext.resolve("Error"),
|
||||
"HttpResponse" to orchestrator.resolve("HttpResponse"),
|
||||
"Instrument" to CargoDependency.Tracing.toType().resolve("Instrument"),
|
||||
"Output" to interceptorContext.resolve("Output"),
|
||||
"OutputOrError" to interceptorContext.resolve("OutputOrError"),
|
||||
"ResponseDeserializer" to orchestrator.resolve("ResponseDeserializer"),
|
||||
"SdkBody" to RuntimeType.sdkBody(runtimeConfig),
|
||||
"SdkError" to RuntimeType.sdkError(runtimeConfig),
|
||||
"TypedBox" to CargoDependency.smithyRuntimeApi(runtimeConfig).toType().resolve("type_erasure::TypedBox"),
|
||||
"debug_span" to RuntimeType.Tracing.resolve("debug_span"),
|
||||
"type_erase_result" to typeEraseResult(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun generateTraitImpls(
|
||||
operationWriter: RustWriter,
|
||||
|
@ -91,40 +111,127 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
// For streaming response bodies, we need to generate a different implementation of the parse traits.
|
||||
// These will first offer the streaming input to the parser & potentially read the body into memory
|
||||
// if an error occurred or if the streaming parser indicates that it needs the full data to proceed.
|
||||
if (operationShape.outputShape(model).hasStreamingMember(model)) {
|
||||
with(operationWriter) {
|
||||
renderStreamingTraits(operationName, outputSymbol, operationShape, customizations)
|
||||
}
|
||||
val streaming = operationShape.outputShape(model).hasStreamingMember(model)
|
||||
if (streaming) {
|
||||
operationWriter.renderStreamingTraits(operationName, outputSymbol, operationShape, customizations)
|
||||
} else {
|
||||
with(operationWriter) {
|
||||
renderNonStreamingTraits(operationName, outputSymbol, operationShape, customizations)
|
||||
}
|
||||
operationWriter.renderNonStreamingTraits(operationName, outputSymbol, operationShape, customizations)
|
||||
}
|
||||
|
||||
if (codegenContext.settings.codegenConfig.enableNewSmithyRuntime) {
|
||||
operationWriter.renderRuntimeTraits(operationName, outputSymbol, operationShape, customizations, streaming)
|
||||
}
|
||||
}
|
||||
|
||||
private fun RustWriter.renderNonStreamingTraits(
|
||||
private fun typeEraseResult(): RuntimeType = ProtocolFunctions.crossOperationFn("type_erase_result") { fnName ->
|
||||
rustTemplate(
|
||||
"""
|
||||
pub(crate) fn $fnName<O, E>(result: Result<O, E>) -> Result<#{Output}, #{Error}>
|
||||
where
|
||||
O: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
result.map(|output| #{TypedBox}::new(output).erase())
|
||||
.map_err(|error| #{TypedBox}::new(error).erase())
|
||||
}
|
||||
""",
|
||||
*orchestratorCodegenScope,
|
||||
)
|
||||
}
|
||||
|
||||
private fun RustWriter.renderRuntimeTraits(
|
||||
operationName: String?,
|
||||
outputSymbol: Symbol,
|
||||
operationShape: OperationShape,
|
||||
customizations: List<OperationCustomization>,
|
||||
streaming: Boolean,
|
||||
) {
|
||||
rustTemplate(
|
||||
"""
|
||||
impl #{ResponseDeserializer} for $operationName {
|
||||
#{deserialize_streaming}
|
||||
|
||||
fn deserialize_nonstreaming(&self, response: &#{HttpResponse}) -> #{OutputOrError} {
|
||||
#{deserialize_nonstreaming}
|
||||
}
|
||||
}
|
||||
""",
|
||||
*orchestratorCodegenScope,
|
||||
"O" to outputSymbol,
|
||||
"E" to symbolProvider.symbolForOperationError(operationShape),
|
||||
"deserialize_streaming" to writable {
|
||||
if (streaming) {
|
||||
deserializeStreaming(operationShape, customizations)
|
||||
}
|
||||
},
|
||||
"deserialize_nonstreaming" to writable {
|
||||
when (streaming) {
|
||||
true -> deserializeStreamingError(operationShape, customizations)
|
||||
else -> deserializeNonStreaming(operationShape, customizations)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun RustWriter.deserializeStreaming(
|
||||
operationShape: OperationShape,
|
||||
customizations: List<OperationCustomization>,
|
||||
) {
|
||||
val successCode = httpBindingResolver.httpTrait(operationShape).code
|
||||
rustTemplate(
|
||||
"""
|
||||
impl #{ParseStrict} for $operationName {
|
||||
type Output = std::result::Result<#{O}, #{E}>;
|
||||
fn parse(&self, response: &#{http}::Response<#{Bytes}>) -> Self::Output {
|
||||
#{BeforeParseResponse}
|
||||
if !response.status().is_success() && response.status().as_u16() != $successCode {
|
||||
#{parse_error}(response)
|
||||
} else {
|
||||
#{parse_response}(response)
|
||||
}
|
||||
fn deserialize_streaming(&self, response: &mut #{HttpResponse}) -> Option<#{OutputOrError}> {
|
||||
#{BeforeParseResponse}
|
||||
|
||||
// If this is an error, defer to the non-streaming parser
|
||||
if !response.status().is_success() && response.status().as_u16() != $successCode {
|
||||
return None;
|
||||
}
|
||||
}""",
|
||||
*codegenScope,
|
||||
"O" to outputSymbol,
|
||||
"E" to symbolProvider.symbolForOperationError(operationShape),
|
||||
Some(#{type_erase_result}(#{parse_streaming_response}(response)))
|
||||
}
|
||||
""",
|
||||
*orchestratorCodegenScope,
|
||||
"parse_streaming_response" to parseStreamingResponse(operationShape, customizations),
|
||||
"BeforeParseResponse" to writable {
|
||||
writeCustomizations(customizations, OperationSection.BeforeParseResponse(customizations, "response"))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun RustWriter.deserializeStreamingError(
|
||||
operationShape: OperationShape,
|
||||
customizations: List<OperationCustomization>,
|
||||
) {
|
||||
rustTemplate(
|
||||
"""
|
||||
// For streaming operations, we only hit this case if its an error
|
||||
let body = response.body().bytes().expect("body loaded");
|
||||
#{type_erase_result}(#{parse_error}(response.status().as_u16(), response.headers(), body))
|
||||
""",
|
||||
*orchestratorCodegenScope,
|
||||
"parse_error" to parseError(operationShape, customizations),
|
||||
)
|
||||
}
|
||||
|
||||
private fun RustWriter.deserializeNonStreaming(
|
||||
operationShape: OperationShape,
|
||||
customizations: List<OperationCustomization>,
|
||||
) {
|
||||
val successCode = httpBindingResolver.httpTrait(operationShape).code
|
||||
rustTemplate(
|
||||
"""
|
||||
let (success, status) = (response.status().is_success(), response.status().as_u16());
|
||||
let headers = response.headers();
|
||||
let body = response.body().bytes().expect("body loaded");
|
||||
#{BeforeParseResponse}
|
||||
let parse_result = if !success && status != $successCode {
|
||||
#{parse_error}(status, headers, body)
|
||||
} else {
|
||||
#{parse_response}(status, headers, body)
|
||||
};
|
||||
#{type_erase_result}(parse_result)
|
||||
""",
|
||||
*orchestratorCodegenScope,
|
||||
"parse_error" to parseError(operationShape, customizations),
|
||||
"parse_response" to parseResponse(operationShape, customizations),
|
||||
"BeforeParseResponse" to writable {
|
||||
|
@ -133,6 +240,45 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
)
|
||||
}
|
||||
|
||||
// TODO(enableNewSmithyRuntime): Delete this when cleaning up `enableNewSmithyRuntime`
|
||||
private fun RustWriter.renderNonStreamingTraits(
|
||||
operationName: String?,
|
||||
outputSymbol: Symbol,
|
||||
operationShape: OperationShape,
|
||||
customizations: List<OperationCustomization>,
|
||||
) {
|
||||
val successCode = httpBindingResolver.httpTrait(operationShape).code
|
||||
val localScope = arrayOf(
|
||||
"O" to outputSymbol,
|
||||
"E" to symbolProvider.symbolForOperationError(operationShape),
|
||||
"parse_error" to parseError(operationShape, customizations),
|
||||
"parse_response" to parseResponse(operationShape, customizations),
|
||||
"BeforeParseResponse" to writable {
|
||||
writeCustomizations(customizations, OperationSection.BeforeParseResponse(customizations, "response"))
|
||||
},
|
||||
)
|
||||
rustTemplate(
|
||||
"""
|
||||
impl #{ParseStrict} for $operationName {
|
||||
type Output = std::result::Result<#{O}, #{E}>;
|
||||
fn parse(&self, response: &#{http}::Response<#{Bytes}>) -> Self::Output {
|
||||
let (success, status) = (response.status().is_success(), response.status().as_u16());
|
||||
let headers = response.headers();
|
||||
let body = response.body().as_ref();
|
||||
#{BeforeParseResponse}
|
||||
if !success && status != $successCode {
|
||||
#{parse_error}(status, headers, body)
|
||||
} else {
|
||||
#{parse_response}(status, headers, body)
|
||||
}
|
||||
}
|
||||
}""",
|
||||
*codegenScope,
|
||||
*localScope,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO(enableNewSmithyRuntime): Delete this when cleaning up `enableNewSmithyRuntime`
|
||||
private fun RustWriter.renderStreamingTraits(
|
||||
operationName: String,
|
||||
outputSymbol: Symbol,
|
||||
|
@ -154,13 +300,13 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
}
|
||||
fn parse_loaded(&self, response: &#{http}::Response<#{Bytes}>) -> Self::Output {
|
||||
// if streaming, we only hit this case if its an error
|
||||
#{parse_error}(response)
|
||||
#{parse_error}(response.status().as_u16(), response.headers(), response.body().as_ref())
|
||||
}
|
||||
}
|
||||
""",
|
||||
"O" to outputSymbol,
|
||||
"E" to symbolProvider.symbolForOperationError(operationShape),
|
||||
"parse_streaming_response" to parseStreamingResponse(operationShape, customizations),
|
||||
"parse_streaming_response" to parseStreamingResponseNoRt(operationShape, customizations),
|
||||
"parse_error" to parseError(operationShape, customizations),
|
||||
"BeforeParseResponse" to writable {
|
||||
writeCustomizations(customizations, OperationSection.BeforeParseResponse(customizations, "response"))
|
||||
|
@ -176,20 +322,25 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
return protocolFunctions.deserializeFn(operationShape, fnNameSuffix = "http_error") { fnName ->
|
||||
Attribute.AllowClippyUnnecessaryWraps.render(this)
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(response: &#{http}::Response<#{Bytes}>) -> std::result::Result<#{O}, #{E}>",
|
||||
"pub fn $fnName(_response_status: u16, _response_headers: &#{http}::header::HeaderMap, _response_body: &[u8]) -> std::result::Result<#{O}, #{E}>",
|
||||
*codegenScope,
|
||||
"O" to outputSymbol,
|
||||
"E" to errorSymbol,
|
||||
) {
|
||||
Attribute.AllowUnusedMut.render(this)
|
||||
rust(
|
||||
"let mut generic_builder = #T(response).map_err(#T::unhandled)?;",
|
||||
"let mut generic_builder = #T(_response_status, _response_headers, _response_body).map_err(#T::unhandled)?;",
|
||||
protocol.parseHttpErrorMetadata(operationShape),
|
||||
errorSymbol,
|
||||
)
|
||||
writeCustomizations(
|
||||
customizations,
|
||||
OperationSection.PopulateErrorMetadataExtras(customizations, "generic_builder", "response"),
|
||||
OperationSection.PopulateErrorMetadataExtras(
|
||||
customizations,
|
||||
"generic_builder",
|
||||
"_response_status",
|
||||
"_response_headers",
|
||||
),
|
||||
)
|
||||
rust("let generic = generic_builder.build();")
|
||||
if (operationShape.operationErrors(model).isNotEmpty()) {
|
||||
|
@ -260,6 +411,43 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
val outputSymbol = symbolProvider.toSymbol(outputShape)
|
||||
val errorSymbol = symbolProvider.symbolForOperationError(operationShape)
|
||||
return protocolFunctions.deserializeFn(operationShape, fnNameSuffix = "http_response") { fnName ->
|
||||
Attribute.AllowClippyUnnecessaryWraps.render(this)
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(response: &mut #{http}::Response<#{SdkBody}>) -> std::result::Result<#{O}, #{E}>",
|
||||
*codegenScope,
|
||||
"O" to outputSymbol,
|
||||
"E" to errorSymbol,
|
||||
) {
|
||||
rustTemplate(
|
||||
"""
|
||||
let mut _response_body = #{SdkBody}::taken();
|
||||
std::mem::swap(&mut _response_body, response.body_mut());
|
||||
let _response_body = &mut _response_body;
|
||||
|
||||
let _response_status = response.status().as_u16();
|
||||
let _response_headers = response.headers();
|
||||
""",
|
||||
*codegenScope,
|
||||
)
|
||||
withBlock("Ok({", "})") {
|
||||
renderShapeParser(
|
||||
operationShape,
|
||||
outputShape,
|
||||
httpBindingResolver.responseBindings(operationShape),
|
||||
errorSymbol,
|
||||
customizations,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(enableNewSmithyRuntime): Delete this when cleaning up `enableNewSmithyRuntime`
|
||||
private fun parseStreamingResponseNoRt(operationShape: OperationShape, customizations: List<OperationCustomization>): RuntimeType {
|
||||
val outputShape = operationShape.outputShape(model)
|
||||
val outputSymbol = symbolProvider.toSymbol(outputShape)
|
||||
val errorSymbol = symbolProvider.symbolForOperationError(operationShape)
|
||||
return protocolFunctions.deserializeFn(operationShape, fnNameSuffix = "http_response_") { fnName ->
|
||||
Attribute.AllowClippyUnnecessaryWraps.render(this)
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(op_response: &mut #{operation}::Response) -> std::result::Result<#{O}, #{E}>",
|
||||
|
@ -270,6 +458,17 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
// Not all implementations will use the property bag, but some will
|
||||
Attribute.AllowUnusedVariables.render(this)
|
||||
rust("let (response, properties) = op_response.parts_mut();")
|
||||
rustTemplate(
|
||||
"""
|
||||
let mut _response_body = #{SdkBody}::taken();
|
||||
std::mem::swap(&mut _response_body, response.body_mut());
|
||||
let _response_body = &mut _response_body;
|
||||
|
||||
let _response_status = response.status().as_u16();
|
||||
let _response_headers = response.headers();
|
||||
""",
|
||||
*codegenScope,
|
||||
)
|
||||
withBlock("Ok({", "})") {
|
||||
renderShapeParser(
|
||||
operationShape,
|
||||
|
@ -290,7 +489,7 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
return protocolFunctions.deserializeFn(operationShape, fnNameSuffix = "http_response") { fnName ->
|
||||
Attribute.AllowClippyUnnecessaryWraps.render(this)
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(response: &#{http}::Response<#{Bytes}>) -> std::result::Result<#{O}, #{E}>",
|
||||
"pub fn $fnName(_response_status: u16, _response_headers: &#{http}::header::HeaderMap, _response_body: &[u8]) -> std::result::Result<#{O}, #{E}>",
|
||||
*codegenScope,
|
||||
"O" to outputSymbol,
|
||||
"E" to errorSymbol,
|
||||
|
@ -319,12 +518,10 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
val structuredDataParser = protocol.structuredDataParser(operationShape)
|
||||
Attribute.AllowUnusedMut.render(this)
|
||||
rust("let mut output = #T::default();", symbolProvider.symbolForBuilder(outputShape))
|
||||
// avoid non-usage warnings for response
|
||||
rust("let _ = response;")
|
||||
if (outputShape.id == operationShape.output.get()) {
|
||||
structuredDataParser.operationParser(operationShape)?.also { parser ->
|
||||
rust(
|
||||
"output = #T(response.body().as_ref(), output).map_err(#T::unhandled)?;",
|
||||
"output = #T(_response_body, output).map_err(#T::unhandled)?;",
|
||||
parser,
|
||||
errorSymbol,
|
||||
)
|
||||
|
@ -333,7 +530,7 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
check(outputShape.hasTrait<ErrorTrait>()) { "should only be called on outputs or errors $outputShape" }
|
||||
structuredDataParser.errorParser(outputShape)?.also { parser ->
|
||||
rust(
|
||||
"output = #T(response.body().as_ref(), output).map_err(#T::unhandled)?;",
|
||||
"output = #T(_response_body, output).map_err(#T::unhandled)?;",
|
||||
parser, errorSymbol,
|
||||
)
|
||||
}
|
||||
|
@ -354,7 +551,10 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
""
|
||||
}
|
||||
|
||||
writeCustomizations(customizations, OperationSection.MutateOutput(customizations, operationShape))
|
||||
writeCustomizations(
|
||||
customizations,
|
||||
OperationSection.MutateOutput(customizations, operationShape, "_response_headers"),
|
||||
)
|
||||
|
||||
rust("output.build()$err")
|
||||
}
|
||||
|
@ -377,7 +577,7 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
val fnName = httpBindingGenerator.generateDeserializeHeaderFn(binding)
|
||||
rust(
|
||||
"""
|
||||
#T(response.headers())
|
||||
#T(_response_headers)
|
||||
.map_err(|_|#T::unhandled("Failed to parse ${member.memberName} from header `${binding.locationName}"))?
|
||||
""",
|
||||
fnName, errorSymbol,
|
||||
|
@ -397,20 +597,20 @@ class HttpBoundProtocolTraitImplGenerator(
|
|||
payloadParser = payloadParser,
|
||||
)
|
||||
return if (binding.member.isStreaming(model)) {
|
||||
writable { rust("Some(#T(response.body_mut())?)", deserializer) }
|
||||
writable { rust("Some(#T(_response_body)?)", deserializer) }
|
||||
} else {
|
||||
writable { rust("#T(response.body().as_ref())?", deserializer) }
|
||||
writable { rust("#T(_response_body)?", deserializer) }
|
||||
}
|
||||
}
|
||||
HttpLocation.RESPONSE_CODE -> writable {
|
||||
rust("Some(response.status().as_u16() as _)")
|
||||
rust("Some(_response_status as _)")
|
||||
}
|
||||
HttpLocation.PREFIX_HEADERS -> {
|
||||
val sym = httpBindingGenerator.generateDeserializePrefixHeaderFn(binding)
|
||||
writable {
|
||||
rustTemplate(
|
||||
"""
|
||||
#{deser}(response.headers())
|
||||
#{deser}(_response_headers)
|
||||
.map_err(|_|
|
||||
#{err}::unhandled("Failed to parse ${member.memberName} from prefix header `${binding.locationName}")
|
||||
)?
|
||||
|
|
|
@ -266,6 +266,8 @@ data class CargoDependency(
|
|||
runtimeConfig.smithyRuntimeCrate("smithy-protocol-test", scope = DependencyScope.Dev)
|
||||
|
||||
fun smithyQuery(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-query")
|
||||
fun smithyRuntime(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime")
|
||||
fun smithyRuntimeApi(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime-api")
|
||||
fun smithyTypes(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-types")
|
||||
fun smithyXml(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-xml")
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@ sealed class OperationSection(name: String) : Section(name) {
|
|||
data class MutateOutput(
|
||||
override val customizations: List<OperationCustomization>,
|
||||
val operationShape: OperationShape,
|
||||
/** Name of the response headers map (for referring to it in Rust code) */
|
||||
val responseHeadersName: String,
|
||||
) : OperationSection("MutateOutput")
|
||||
|
||||
/**
|
||||
|
@ -62,8 +64,10 @@ sealed class OperationSection(name: String) : Section(name) {
|
|||
override val customizations: List<OperationCustomization>,
|
||||
/** Name of the generic error builder (for referring to it in Rust code) */
|
||||
val builderName: String,
|
||||
/** Name of the response (for referring to it in Rust code) */
|
||||
val responseName: String,
|
||||
/** Name of the response status (for referring to it in Rust code) */
|
||||
val responseStatusName: String,
|
||||
/** Name of the response headers map (for referring to it in Rust code) */
|
||||
val responseHeadersName: String,
|
||||
) : OperationSection("PopulateErrorMetadataExtras")
|
||||
|
||||
/**
|
||||
|
|
|
@ -130,7 +130,6 @@ open class AwsJson(
|
|||
"HeaderMap" to RuntimeType.Http.resolve("HeaderMap"),
|
||||
"JsonError" to CargoDependency.smithyJson(runtimeConfig).toType()
|
||||
.resolve("deserialize::error::DeserializeError"),
|
||||
"Response" to RuntimeType.Http.resolve("Response"),
|
||||
"json_errors" to RuntimeType.jsonErrors(runtimeConfig),
|
||||
)
|
||||
|
||||
|
@ -159,8 +158,8 @@ open class AwsJson(
|
|||
ProtocolFunctions.crossOperationFn("parse_http_error_metadata") { fnName ->
|
||||
rustTemplate(
|
||||
"""
|
||||
pub fn $fnName(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> {
|
||||
#{json_errors}::parse_error_metadata(response.body(), response.headers())
|
||||
pub fn $fnName(_response_status: u16, response_headers: &#{HeaderMap}, response_body: &[u8]) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> {
|
||||
#{json_errors}::parse_error_metadata(response_body, response_headers)
|
||||
}
|
||||
""",
|
||||
*errorScope,
|
||||
|
|
|
@ -60,10 +60,10 @@ class AwsQueryProtocol(private val codegenContext: CodegenContext) : Protocol {
|
|||
override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType =
|
||||
ProtocolFunctions.crossOperationFn("parse_http_error_metadata") { fnName ->
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>",
|
||||
"pub fn $fnName(_response_status: u16, _response_headers: &#{HeaderMap}, response_body: &[u8]) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>",
|
||||
*errorScope,
|
||||
) {
|
||||
rust("#T::parse_error_metadata(response.body().as_ref())", awsQueryErrors)
|
||||
rust("#T::parse_error_metadata(response_body)", awsQueryErrors)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,11 +49,11 @@ class AwsQueryCompatible(
|
|||
private val errorScope = arrayOf(
|
||||
"Bytes" to RuntimeType.Bytes,
|
||||
"ErrorMetadataBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig),
|
||||
"HeaderMap" to RuntimeType.HttpHeaderMap,
|
||||
"JsonError" to CargoDependency.smithyJson(runtimeConfig).toType()
|
||||
.resolve("deserialize::error::DeserializeError"),
|
||||
"Response" to RuntimeType.Http.resolve("Response"),
|
||||
"json_errors" to RuntimeType.jsonErrors(runtimeConfig),
|
||||
"aws_query_compatible_errors" to RuntimeType.awsQueryCompatibleErrors(runtimeConfig),
|
||||
"json_errors" to RuntimeType.jsonErrors(runtimeConfig),
|
||||
)
|
||||
|
||||
override val httpBindingResolver: HttpBindingResolver =
|
||||
|
@ -74,11 +74,11 @@ class AwsQueryCompatible(
|
|||
ProtocolFunctions.crossOperationFn("parse_http_error_metadata") { fnName ->
|
||||
rustTemplate(
|
||||
"""
|
||||
pub fn $fnName(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> {
|
||||
pub fn $fnName(_response_status: u16, response_headers: &#{HeaderMap}, response_body: &[u8]) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> {
|
||||
let mut builder =
|
||||
#{json_errors}::parse_error_metadata(response.body(), response.headers())?;
|
||||
#{json_errors}::parse_error_metadata(response_body, response_headers)?;
|
||||
if let Some((error_code, error_type)) =
|
||||
#{aws_query_compatible_errors}::parse_aws_query_compatible_error(response.headers())
|
||||
#{aws_query_compatible_errors}::parse_aws_query_compatible_error(response_headers)
|
||||
{
|
||||
builder = builder.code(error_code);
|
||||
builder = builder.custom("type", error_type);
|
||||
|
|
|
@ -52,10 +52,10 @@ class Ec2QueryProtocol(private val codegenContext: CodegenContext) : Protocol {
|
|||
override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType =
|
||||
ProtocolFunctions.crossOperationFn("parse_http_error_metadata") { fnName ->
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>",
|
||||
"pub fn $fnName(_response_status: u16, _response_headers: &#{HeaderMap}, response_body: &[u8]) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>",
|
||||
*errorScope,
|
||||
) {
|
||||
rust("#T::parse_error_metadata(response.body().as_ref())", ec2QueryErrors)
|
||||
rust("#T::parse_error_metadata(response_body)", ec2QueryErrors)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ interface Protocol {
|
|||
/**
|
||||
* Generates a function signature like the following:
|
||||
* ```rust
|
||||
* fn parse_http_error_metadata(response: &Response<Bytes>) -> aws_smithy_types::error::Builder
|
||||
* fn parse_http_error_metadata(response_status: u16, response_headers: HeaderMap, response_body: &[u8]) -> aws_smithy_types::error::Builder
|
||||
* ```
|
||||
*/
|
||||
fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType
|
||||
|
|
|
@ -100,8 +100,8 @@ open class RestJson(val codegenContext: CodegenContext) : Protocol {
|
|||
ProtocolFunctions.crossOperationFn("parse_http_error_metadata") { fnName ->
|
||||
rustTemplate(
|
||||
"""
|
||||
pub fn $fnName(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> {
|
||||
#{json_errors}::parse_error_metadata(response.body(), response.headers())
|
||||
pub fn $fnName(_response_status: u16, response_headers: &#{HeaderMap}, response_body: &[u8]) -> Result<#{ErrorMetadataBuilder}, #{JsonError}> {
|
||||
#{json_errors}::parse_error_metadata(response_body, response_headers)
|
||||
}
|
||||
""",
|
||||
*errorScope,
|
||||
|
|
|
@ -51,10 +51,10 @@ open class RestXml(val codegenContext: CodegenContext) : Protocol {
|
|||
override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType =
|
||||
ProtocolFunctions.crossOperationFn("parse_http_error_metadata") { fnName ->
|
||||
rustBlockTemplate(
|
||||
"pub fn $fnName(response: &#{Response}<#{Bytes}>) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>",
|
||||
"pub fn $fnName(_response_status: u16, _response_headers: &#{HeaderMap}, response_body: &[u8]) -> Result<#{ErrorMetadataBuilder}, #{XmlDecodeError}>",
|
||||
*errorScope,
|
||||
) {
|
||||
rust("#T::parse_error_metadata(response.body().as_ref())", restXmlErrors)
|
||||
rust("#T::parse_error_metadata(response_body)", restXmlErrors)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -433,6 +433,21 @@ impl<E, R> SdkError<E, R> {
|
|||
ServiceError(context) => Ok(context.source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the service error type in `SdkError::ServiceError`
|
||||
#[doc(hidden)]
|
||||
pub fn map_service_error<E2>(self, map: impl FnOnce(E) -> E2) -> SdkError<E2, R> {
|
||||
match self {
|
||||
Self::ServiceError(context) => SdkError::<E2, R>::ServiceError(ServiceError {
|
||||
source: map(context.source),
|
||||
raw: context.raw,
|
||||
}),
|
||||
Self::ConstructionFailure(context) => SdkError::<E2, R>::ConstructionFailure(context),
|
||||
Self::DispatchFailure(context) => SdkError::<E2, R>::DispatchFailure(context),
|
||||
Self::ResponseError(context) => SdkError::<E2, R>::ResponseError(context),
|
||||
Self::TimeoutError(context) => SdkError::<E2, R>::TimeoutError(context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, R> Display for SdkError<E, R> {
|
||||
|
|
|
@ -14,6 +14,7 @@ publish = false
|
|||
aws-smithy-types = { path = "../aws-smithy-types" }
|
||||
aws-smithy-http = { path = "../aws-smithy-http" }
|
||||
tokio = { version = "1.25", features = ["sync"] }
|
||||
http = "0.2.3"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
allowed_external_types = [
|
||||
"aws_smithy_types::*",
|
||||
"aws_smithy_http::*",
|
||||
|
||||
"http::request::Request",
|
||||
"http::response::Response",
|
||||
]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/// 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_plugin;
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_smithy_types::DateTime;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Identity {
|
||||
data: Arc<dyn Any + Send + Sync>,
|
||||
expiration: Option<DateTime>,
|
||||
}
|
||||
|
||||
impl Identity {
|
||||
pub fn new(data: impl Any + Send + Sync, expiration: Option<DateTime>) -> Self {
|
||||
Self {
|
||||
data: Arc::new(data),
|
||||
expiration,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data<T: 'static>(&self) -> Option<&T> {
|
||||
self.data.downcast_ref()
|
||||
}
|
||||
|
||||
pub fn expiration(&self) -> Option<&DateTime> {
|
||||
self.expiration.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use aws_smithy_types::date_time::Format;
|
||||
|
||||
#[test]
|
||||
fn check_send_sync() {
|
||||
fn is_send_sync<T: Send + Sync>(_: T) {}
|
||||
is_send_sync(Identity::new("foo", None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_retrieve_identity() {
|
||||
#[derive(Debug)]
|
||||
struct MyIdentityData {
|
||||
first: String,
|
||||
last: String,
|
||||
}
|
||||
|
||||
let expiration =
|
||||
DateTime::from_str("2023-03-15T00:00:00.000Z", Format::DateTimeWithOffset).unwrap();
|
||||
let identity = Identity::new(
|
||||
MyIdentityData {
|
||||
first: "foo".into(),
|
||||
last: "bar".into(),
|
||||
},
|
||||
Some(expiration.clone()),
|
||||
);
|
||||
|
||||
assert_eq!("foo", identity.data::<MyIdentityData>().unwrap().first);
|
||||
assert_eq!("bar", identity.data::<MyIdentityData>().unwrap().last);
|
||||
assert_eq!(Some(&expiration), identity.expiration());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,622 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
|
||||
use crate::config_bag::ConfigBag;
|
||||
pub use context::InterceptorContext;
|
||||
pub use error::InterceptorError;
|
||||
|
||||
macro_rules! interceptor_trait_fn {
|
||||
($name:ident, $docs:tt) => {
|
||||
#[doc = $docs]
|
||||
fn $name(
|
||||
&mut self,
|
||||
context: &InterceptorContext<TxReq, TxRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// An interceptor allows injecting code into the SDK ’s request execution pipeline.
|
||||
///
|
||||
/// ## Terminology:
|
||||
/// - An execution is one end-to-end invocation against an SDK client.
|
||||
/// - An attempt is an attempt at performing an execution. By default executions are retried multiple
|
||||
/// times based on the client ’s retry strategy.
|
||||
/// - A hook is a single method on the interceptor, allowing injection of code into a specific part
|
||||
/// of the SDK ’s request execution pipeline. Hooks are either "read" hooks, which make it possible
|
||||
/// to read in-flight request or response messages, or "read/write" hooks, which make it possible
|
||||
/// to modify in-flight request or output messages.
|
||||
pub trait Interceptor<TxReq, TxRes> {
|
||||
interceptor_trait_fn!(
|
||||
read_before_execution,
|
||||
"
|
||||
A hook called at the start of an execution, before the SDK
|
||||
does anything else.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per execution. The duration
|
||||
between invocation of this hook and `after_execution` is very close
|
||||
to full duration of the execution.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()] is
|
||||
**ALWAYS** available. Other information **WILL NOT** be available.
|
||||
|
||||
**Error Behavior:** Errors raised by this hook will be stored
|
||||
until all interceptors have had their `before_execution` invoked.
|
||||
Other hooks will then be skipped and execution will jump to
|
||||
`modify_before_completion` with the raised error as the
|
||||
[InterceptorContext::output_or_error()]. If multiple
|
||||
`before_execution` methods raise errors, the latest
|
||||
will be used and earlier ones will be logged and dropped.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
modify_before_serialization,
|
||||
"
|
||||
A hook called before the input message is marshalled into a
|
||||
transport message.
|
||||
This method has the ability to modify and return a new
|
||||
request message of the same type.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per execution, except when a
|
||||
failure occurs earlier in the request pipeline.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()] is
|
||||
**ALWAYS** available. This request may have been modified by earlier
|
||||
`modify_before_serialization` hooks, and may be modified further by
|
||||
later hooks. Other information **WILL NOT** be available.
|
||||
|
||||
**Error Behavior:** If errors are raised by this hook,
|
||||
|
||||
execution will jump to `modify_before_completion` with the raised
|
||||
error as the [InterceptorContext::output_or_error()].
|
||||
|
||||
**Return Constraints:** The input message returned by this hook
|
||||
MUST be the same type of input message passed into this hook.
|
||||
If not, an error will immediately be raised.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_before_serialization,
|
||||
"
|
||||
A hook called before the input message is marshalled
|
||||
into a transport
|
||||
message.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per execution, except when a
|
||||
failure occurs earlier in the request pipeline. The
|
||||
duration between invocation of this hook and `after_serialization` is
|
||||
very close to the amount of time spent marshalling the request.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()] is
|
||||
**ALWAYS** available. Other information **WILL NOT** be available.
|
||||
|
||||
**Error Behavior:** If errors are raised by this hook,
|
||||
execution will jump to `modify_before_completion` with the raised
|
||||
error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_after_serialization,
|
||||
"
|
||||
/// A hook called after the input message is marshalled into
|
||||
/// a transport message.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per execution, except when a
|
||||
/// failure occurs earlier in the request pipeline. The duration
|
||||
/// between invocation of this hook and `before_serialization` is very
|
||||
/// close to the amount of time spent marshalling the request.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::input()]
|
||||
/// and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available.
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this hook,
|
||||
/// execution will jump to `modify_before_completion` with the raised
|
||||
/// error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
modify_before_retry_loop,
|
||||
"
|
||||
A hook called before the retry loop is entered. This method
|
||||
has the ability to modify and return a new transport request
|
||||
message of the same type, except when a failure occurs earlier in the request pipeline.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
Other information **WILL NOT** be available.
|
||||
|
||||
**Error Behavior:** If errors are raised by this hook,
|
||||
execution will jump to `modify_before_completion` with the raised
|
||||
error as the [InterceptorContext::output_or_error()].
|
||||
|
||||
**Return Constraints:** The transport request message returned by this
|
||||
hook MUST be the same type of request message passed into this hook
|
||||
If not, an error will immediately be raised.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_before_attempt,
|
||||
"
|
||||
A hook called before each attempt at sending the transmission
|
||||
request message to the service.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method will be
|
||||
called multiple times in the event of retries.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
Other information **WILL NOT** be available. In the event of retries,
|
||||
the `InterceptorContext` will not include changes made in previous
|
||||
attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** Errors raised by this hook will be stored
|
||||
until all interceptors have had their `before_attempt` invoked.
|
||||
Other hooks will then be skipped and execution will jump to
|
||||
`modify_before_attempt_completion` with the raised error as the
|
||||
[InterceptorContext::output_or_error()]. If multiple
|
||||
`before_attempt` methods raise errors, the latest will be used
|
||||
and earlier ones will be logged and dropped.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
modify_before_signing,
|
||||
"
|
||||
A hook called before the transport request message is signed.
|
||||
This method has the ability to modify and return a new transport
|
||||
request message of the same type.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method may be
|
||||
called multiple times in the event of retries.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
The `http::Request` may have been modified by earlier
|
||||
`modify_before_signing` hooks, and may be modified further by later
|
||||
hooks. Other information **WILL NOT** be available. In the event of
|
||||
retries, the `InterceptorContext` will not include changes made
|
||||
in previous attempts
|
||||
(e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
|
||||
**Return Constraints:** The transport request message returned by this
|
||||
hook MUST be the same type of request message passed into this hook
|
||||
|
||||
If not, an error will immediately be raised.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_before_signing,
|
||||
"
|
||||
A hook called before the transport request message is signed.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method may be
|
||||
called multiple times in the event of retries. The duration between
|
||||
invocation of this hook and `after_signing` is very close to
|
||||
the amount of time spent signing the request.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
Other information **WILL NOT** be available. In the event of retries,
|
||||
the `InterceptorContext` will not include changes made in previous
|
||||
attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_after_signing,
|
||||
"
|
||||
A hook called after the transport request message is signed.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method may be
|
||||
called multiple times in the event of retries. The duration between
|
||||
invocation of this hook and `before_signing` is very close to
|
||||
the amount of time spent signing the request.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
Other information **WILL NOT** be available. In the event of retries,
|
||||
the `InterceptorContext` will not include changes made in previous
|
||||
attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
modify_before_transmit,
|
||||
"
|
||||
/// A hook called before the transport request message is sent to the
|
||||
/// service. This method has the ability to modify and return
|
||||
/// a new transport request message of the same type.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::input()]
|
||||
/// and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
/// The `http::Request` may have been modified by earlier
|
||||
/// `modify_before_transmit` hooks, and may be modified further by later
|
||||
/// hooks. Other information **WILL NOT** be available.
|
||||
/// In the event of retries, the `InterceptorContext` will not include
|
||||
/// changes made in previous attempts (e.g. by request signers or
|
||||
other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
|
||||
**Return Constraints:** The transport request message returned by this
|
||||
hook MUST be the same type of request message passed into this hook
|
||||
|
||||
If not, an error will immediately be raised.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_before_transmit,
|
||||
"
|
||||
A hook called before the transport request message is sent to the
|
||||
service.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method may be
|
||||
called multiple times in the event of retries. The duration between
|
||||
invocation of this hook and `after_transmit` is very close to
|
||||
the amount of time spent communicating with the service.
|
||||
Depending on the protocol, the duration may not include the
|
||||
time spent reading the response data.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::request()] are **ALWAYS** available.
|
||||
Other information **WILL NOT** be available. In the event of retries,
|
||||
the `InterceptorContext` will not include changes made in previous
|
||||
attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_after_transmit,
|
||||
"
|
||||
A hook called after the transport request message is sent to the
|
||||
service and a transport response message is received.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method may be
|
||||
called multiple times in the event of retries. The duration between
|
||||
invocation of this hook and `before_transmit` is very close to
|
||||
the amount of time spent communicating with the service.
|
||||
Depending on the protocol, the duration may not include the time
|
||||
spent reading the response data.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()],
|
||||
[InterceptorContext::request()] and
|
||||
[InterceptorContext::response()] are **ALWAYS** available.
|
||||
Other information **WILL NOT** be available. In the event of retries,
|
||||
the `InterceptorContext` will not include changes made in previous
|
||||
attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
modify_before_deserialization,
|
||||
"
|
||||
A hook called before the transport response message is unmarshalled.
|
||||
This method has the ability to modify and return a new transport
|
||||
response message of the same type.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method may be
|
||||
called multiple times in the event of retries.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()],
|
||||
[InterceptorContext::request()] and
|
||||
[InterceptorContext::response()] are **ALWAYS** available.
|
||||
The transmit_response may have been modified by earlier
|
||||
`modify_before_deserialization` hooks, and may be modified further by
|
||||
later hooks. Other information **WILL NOT** be available. In the event of
|
||||
retries, the `InterceptorContext` will not include changes made in
|
||||
previous attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the
|
||||
[InterceptorContext::output_or_error()].
|
||||
|
||||
**Return Constraints:** The transport response message returned by this
|
||||
hook MUST be the same type of response message passed into
|
||||
this hook. If not, an error will immediately be raised.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_before_deserialization,
|
||||
"
|
||||
A hook called before the transport response message is unmarshalled
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. This method may be
|
||||
called multiple times in the event of retries. The duration between
|
||||
invocation of this hook and `after_deserialization` is very close
|
||||
to the amount of time spent unmarshalling the service response.
|
||||
Depending on the protocol and operation, the duration may include
|
||||
the time spent downloading the response data.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()],
|
||||
[InterceptorContext::request()] and
|
||||
[InterceptorContext::response()] are **ALWAYS** available.
|
||||
Other information **WILL NOT** be available. In the event of retries,
|
||||
the `InterceptorContext` will not include changes made in previous
|
||||
attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion`
|
||||
with the raised error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_after_deserialization,
|
||||
"
|
||||
A hook called after the transport response message is unmarshalled.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs earlier in the request pipeline. The duration
|
||||
between invocation of this hook and `before_deserialization` is
|
||||
very close to the amount of time spent unmarshalling the
|
||||
service response. Depending on the protocol and operation,
|
||||
the duration may include the time spent downloading
|
||||
the response data.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()],
|
||||
[InterceptorContext::request()],
|
||||
[InterceptorContext::response()] and
|
||||
[InterceptorContext::output_or_error()] are **ALWAYS** available. In the event
|
||||
of retries, the `InterceptorContext` will not include changes made
|
||||
in previous attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `modify_before_attempt_completion` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
modify_before_attempt_completion,
|
||||
"
|
||||
A hook called when an attempt is completed. This method has the
|
||||
ability to modify and return a new output message or error
|
||||
matching the currently-executing operation.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
failure occurs before `before_attempt`. This method may
|
||||
be called multiple times in the event of retries.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()],
|
||||
[InterceptorContext::request()],
|
||||
[InterceptorContext::response()] and
|
||||
[InterceptorContext::output_or_error()] are **ALWAYS** available. In the event
|
||||
of retries, the `InterceptorContext` will not include changes made
|
||||
in previous attempts (e.g. by request signers or other interceptors).
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook, execution will jump to `after_attempt` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
|
||||
**Return Constraints:** Any output message returned by this
|
||||
hook MUST match the operation being invoked. Any error type can be
|
||||
returned, replacing the response currently in the context.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_after_attempt,
|
||||
"
|
||||
A hook called when an attempt is completed.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per attempt, as long as
|
||||
`before_attempt` has been executed.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()],
|
||||
[InterceptorContext::request()] and
|
||||
[InterceptorContext::output_or_error()] are **ALWAYS** available.
|
||||
The [InterceptorContext::response()] is available if a
|
||||
response was received by the service for this attempt.
|
||||
In the event of retries, the `InterceptorContext` will not include
|
||||
changes made in previous attempts (e.g. by request signers or other
|
||||
interceptors).
|
||||
|
||||
**Error Behavior:** Errors raised by this hook will be stored
|
||||
until all interceptors have had their `after_attempt` invoked.
|
||||
If multiple `after_execution` methods raise errors, the latest
|
||||
will be used and earlier ones will be logged and dropped. If the
|
||||
retry strategy determines that the execution is retryable,
|
||||
execution will then jump to `before_attempt`. Otherwise,
|
||||
execution will jump to `modify_before_attempt_completion` with the
|
||||
raised error as the [InterceptorContext::output_or_error()].
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
modify_before_completion,
|
||||
"
|
||||
A hook called when an execution is completed.
|
||||
This method has the ability to modify and return a new
|
||||
output message or error matching the currently - executing
|
||||
operation.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per execution.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::output_or_error()] are **ALWAYS** available. The
|
||||
[InterceptorContext::request()]
|
||||
and [InterceptorContext::response()] are available if the
|
||||
execution proceeded far enough for them to be generated.
|
||||
|
||||
**Error Behavior:** If errors are raised by this
|
||||
hook , execution will jump to `after_attempt` with
|
||||
the raised error as the [InterceptorContext::output_or_error()].
|
||||
|
||||
**Return Constraints:** Any output message returned by this
|
||||
hook MUST match the operation being invoked. Any error type can be
|
||||
returned , replacing the response currently in the context.
|
||||
"
|
||||
);
|
||||
|
||||
interceptor_trait_fn!(
|
||||
read_after_execution,
|
||||
"
|
||||
A hook called when an execution is completed.
|
||||
|
||||
**When:** This will **ALWAYS** be called once per execution. The duration
|
||||
between invocation of this hook and `before_execution` is very
|
||||
close to the full duration of the execution.
|
||||
|
||||
**Available Information:** The [InterceptorContext::input()]
|
||||
and [InterceptorContext::output_or_error()] are **ALWAYS** available. The
|
||||
[InterceptorContext::request()] and
|
||||
[InterceptorContext::response()] are available if the
|
||||
execution proceeded far enough for them to be generated.
|
||||
|
||||
**Error Behavior:** Errors raised by this hook will be stored
|
||||
until all interceptors have had their `after_execution` invoked.
|
||||
The error will then be treated as the
|
||||
[InterceptorContext::output_or_error()] to the customer. If multiple
|
||||
`after_execution` methods raise errors , the latest will be
|
||||
used and earlier ones will be logged and dropped.
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
pub struct Interceptors<TxReq, TxRes> {
|
||||
client_interceptors: Vec<Box<dyn Interceptor<TxReq, TxRes>>>,
|
||||
operation_interceptors: Vec<Box<dyn Interceptor<TxReq, TxRes>>>,
|
||||
}
|
||||
|
||||
impl<TxReq, TxRes> Default for Interceptors<TxReq, TxRes> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
client_interceptors: Vec::new(),
|
||||
operation_interceptors: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! interceptor_impl_fn {
|
||||
(context, $name:ident) => {
|
||||
interceptor_impl_fn!(context, $name, $name);
|
||||
};
|
||||
(mut context, $name:ident) => {
|
||||
interceptor_impl_fn!(mut context, $name, $name);
|
||||
};
|
||||
(context, $outer_name:ident, $inner_name:ident) => {
|
||||
pub fn $outer_name(
|
||||
&mut self,
|
||||
context: &InterceptorContext<TxReq, TxRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.client_interceptors.iter_mut() {
|
||||
interceptor.$inner_name(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
(mut context, $outer_name:ident, $inner_name:ident) => {
|
||||
pub fn $outer_name(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<TxReq, TxRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.client_interceptors.iter_mut() {
|
||||
interceptor.$inner_name(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<TxReq, TxRes> Interceptors<TxReq, TxRes> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_client_interceptor(
|
||||
&mut self,
|
||||
interceptor: impl Interceptor<TxReq, TxRes> + 'static,
|
||||
) -> &mut Self {
|
||||
self.client_interceptors.push(Box::new(interceptor));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_operation_interceptor(
|
||||
&mut self,
|
||||
interceptor: impl Interceptor<TxReq, TxRes> + 'static,
|
||||
) -> &mut Self {
|
||||
self.operation_interceptors.push(Box::new(interceptor));
|
||||
self
|
||||
}
|
||||
|
||||
interceptor_impl_fn!(context, client_read_before_execution, read_before_execution);
|
||||
interceptor_impl_fn!(
|
||||
context,
|
||||
operation_read_before_execution,
|
||||
read_before_execution
|
||||
);
|
||||
interceptor_impl_fn!(mut context, modify_before_serialization);
|
||||
interceptor_impl_fn!(context, read_before_serialization);
|
||||
interceptor_impl_fn!(context, read_after_serialization);
|
||||
interceptor_impl_fn!(mut context, modify_before_retry_loop);
|
||||
interceptor_impl_fn!(context, read_before_attempt);
|
||||
interceptor_impl_fn!(mut context, modify_before_signing);
|
||||
interceptor_impl_fn!(context, read_before_signing);
|
||||
interceptor_impl_fn!(context, read_after_signing);
|
||||
interceptor_impl_fn!(mut context, modify_before_transmit);
|
||||
interceptor_impl_fn!(context, read_before_transmit);
|
||||
interceptor_impl_fn!(context, read_after_transmit);
|
||||
interceptor_impl_fn!(mut context, modify_before_deserialization);
|
||||
interceptor_impl_fn!(context, read_before_deserialization);
|
||||
interceptor_impl_fn!(context, read_after_deserialization);
|
||||
interceptor_impl_fn!(mut context, modify_before_attempt_completion);
|
||||
interceptor_impl_fn!(context, read_after_attempt);
|
||||
interceptor_impl_fn!(mut context, modify_before_completion);
|
||||
interceptor_impl_fn!(context, read_after_execution);
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use super::InterceptorError;
|
||||
use crate::type_erasure::TypeErasedBox;
|
||||
|
||||
pub type Input = TypeErasedBox;
|
||||
pub type Output = TypeErasedBox;
|
||||
pub type Error = TypeErasedBox;
|
||||
pub type OutputOrError = Result<Output, Error>;
|
||||
|
||||
/// A container for the data currently available to an interceptor.
|
||||
pub struct InterceptorContext<Request, Response> {
|
||||
input: Input,
|
||||
output_or_error: Option<OutputOrError>,
|
||||
request: Option<Request>,
|
||||
response: Option<Response>,
|
||||
}
|
||||
|
||||
// TODO(interceptors) we could use types to ensure that people calling methods on interceptor context can't access
|
||||
// field that haven't been set yet.
|
||||
impl<Request, Response> InterceptorContext<Request, Response> {
|
||||
pub fn new(input: Input) -> Self {
|
||||
Self {
|
||||
input,
|
||||
output_or_error: None,
|
||||
request: None,
|
||||
response: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the modeled request for the operation being invoked.
|
||||
pub fn input(&self) -> &Input {
|
||||
&self.input
|
||||
}
|
||||
|
||||
/// Retrieve the modeled request for the operation being invoked.
|
||||
pub fn input_mut(&mut self) -> &mut Input {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
/// Retrieve the transmittable request for the operation being invoked.
|
||||
/// This will only be available once request marshalling has completed.
|
||||
pub fn request(&self) -> Result<&Request, InterceptorError> {
|
||||
self.request
|
||||
.as_ref()
|
||||
.ok_or_else(InterceptorError::invalid_request_access)
|
||||
}
|
||||
|
||||
/// Retrieve the transmittable request for the operation being invoked.
|
||||
/// This will only be available once request marshalling has completed.
|
||||
pub fn request_mut(&mut self) -> Result<&mut Request, InterceptorError> {
|
||||
self.request
|
||||
.as_mut()
|
||||
.ok_or_else(InterceptorError::invalid_request_access)
|
||||
}
|
||||
|
||||
/// Retrieve the response to the transmittable response for the operation
|
||||
/// being invoked. This will only be available once transmission has
|
||||
/// completed.
|
||||
pub fn response(&self) -> Result<&Response, InterceptorError> {
|
||||
self.response
|
||||
.as_ref()
|
||||
.ok_or_else(InterceptorError::invalid_response_access)
|
||||
}
|
||||
|
||||
/// Retrieve the response to the transmittable response for the operation
|
||||
/// being invoked. This will only be available once transmission has
|
||||
/// completed.
|
||||
pub fn response_mut(&mut self) -> Result<&mut Response, InterceptorError> {
|
||||
self.response
|
||||
.as_mut()
|
||||
.ok_or_else(InterceptorError::invalid_response_access)
|
||||
}
|
||||
|
||||
/// Retrieve the response to the customer. This will only be available
|
||||
/// once the `response` has been unmarshalled or the attempt/execution has failed.
|
||||
pub fn output_or_error(&self) -> Result<Result<&Output, &Error>, InterceptorError> {
|
||||
self.output_or_error
|
||||
.as_ref()
|
||||
.ok_or_else(InterceptorError::invalid_modeled_response_access)
|
||||
.map(|res| res.as_ref())
|
||||
}
|
||||
|
||||
/// Retrieve the response to the customer. This will only be available
|
||||
/// once the `response` has been unmarshalled or the
|
||||
/// attempt/execution has failed.
|
||||
pub fn output_or_error_mut(&mut self) -> Result<&mut Result<Output, Error>, InterceptorError> {
|
||||
self.output_or_error
|
||||
.as_mut()
|
||||
.ok_or_else(InterceptorError::invalid_modeled_response_access)
|
||||
}
|
||||
|
||||
// There is no set_modeled_request method because that can only be set once, during context construction
|
||||
|
||||
pub fn set_request(&mut self, request: Request) {
|
||||
if self.request.is_some() {
|
||||
panic!("Called set_request but a request was already set. This is a bug. Please report it.");
|
||||
}
|
||||
|
||||
self.request = Some(request);
|
||||
}
|
||||
|
||||
pub fn set_response(&mut self, response: Response) {
|
||||
if self.response.is_some() {
|
||||
panic!("Called set_response but a transmit_response was already set. This is a bug. Please report it.");
|
||||
}
|
||||
|
||||
self.response = Some(response);
|
||||
}
|
||||
|
||||
pub fn set_output_or_error(&mut self, output: Result<Output, Error>) {
|
||||
if self.output_or_error.is_some() {
|
||||
panic!(
|
||||
"Called set_output but an output was already set. This is a bug. Please report it."
|
||||
);
|
||||
}
|
||||
|
||||
self.output_or_error = Some(output);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn into_parts(
|
||||
self,
|
||||
) -> (
|
||||
Input,
|
||||
Option<OutputOrError>,
|
||||
Option<Request>,
|
||||
Option<Response>,
|
||||
) {
|
||||
(
|
||||
self.input,
|
||||
self.output_or_error,
|
||||
self.request,
|
||||
self.response,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -190,16 +190,16 @@ impl InterceptorError {
|
|||
}
|
||||
}
|
||||
/// Create a new error indicating that an interceptor tried to access the tx_request out of turn
|
||||
pub fn invalid_tx_request_access() -> Self {
|
||||
pub fn invalid_request_access() -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::InvalidTxRequestAccess,
|
||||
kind: ErrorKind::InvalidRequestAccess,
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
/// Create a new error indicating that an interceptor tried to access the tx_response out of turn
|
||||
pub fn invalid_tx_response_access() -> Self {
|
||||
pub fn invalid_response_access() -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::InvalidTxResponseAccess,
|
||||
kind: ErrorKind::InvalidResponseAccess,
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
@ -253,10 +253,10 @@ enum ErrorKind {
|
|||
/// An error occurred within the read_after_execution interceptor
|
||||
ReadAfterExecution,
|
||||
// There is no InvalidModeledRequestAccess because it's always accessible
|
||||
/// An interceptor tried to access the tx_request out of turn
|
||||
InvalidTxRequestAccess,
|
||||
/// An interceptor tried to access the tx_response out of turn
|
||||
InvalidTxResponseAccess,
|
||||
/// An interceptor tried to access the request out of turn
|
||||
InvalidRequestAccess,
|
||||
/// An interceptor tried to access the response out of turn
|
||||
InvalidResponseAccess,
|
||||
/// An interceptor tried to access the modeled_response out of turn
|
||||
InvalidModeledResponseAccess,
|
||||
}
|
||||
|
@ -321,13 +321,12 @@ impl fmt::Display for InterceptorError {
|
|||
ReadAfterExecution => {
|
||||
write!(f, "read_after_execution interceptor encountered an error")
|
||||
}
|
||||
InvalidTxRequestAccess => {
|
||||
write!(f, "tried to access tx_request before request serialization")
|
||||
InvalidRequestAccess => {
|
||||
write!(f, "tried to access request before request serialization")
|
||||
}
|
||||
InvalidResponseAccess => {
|
||||
write!(f, "tried to access response before transmitting a request")
|
||||
}
|
||||
InvalidTxResponseAccess => write!(
|
||||
f,
|
||||
"tried to access tx_response before transmitting a request"
|
||||
),
|
||||
InvalidModeledResponseAccess => write!(
|
||||
f,
|
||||
"tried to access modeled_response before response deserialization"
|
|
@ -0,0 +1,374 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use crate::client::identity::Identity;
|
||||
use crate::client::interceptors::context::{Input, OutputOrError};
|
||||
use crate::client::interceptors::InterceptorContext;
|
||||
use crate::config_bag::ConfigBag;
|
||||
use crate::type_erasure::{TypeErasedBox, TypedBox};
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_http::property_bag::PropertyBag;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type HttpRequest = http::Request<SdkBody>;
|
||||
pub type HttpResponse = http::Response<SdkBody>;
|
||||
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
pub type BoxFallibleFut<T> = Pin<Box<dyn Future<Output = Result<T, BoxError>>>>;
|
||||
|
||||
pub trait TraceProbe: Send + Sync + Debug {
|
||||
fn dispatch_events(&self, cfg: &ConfigBag) -> BoxFallibleFut<()>;
|
||||
}
|
||||
|
||||
pub trait RequestSerializer: Send + Sync + Debug {
|
||||
fn serialize_input(&self, input: &Input, cfg: &ConfigBag) -> Result<HttpRequest, BoxError>;
|
||||
}
|
||||
|
||||
pub trait ResponseDeserializer: Send + Sync + Debug {
|
||||
fn deserialize_streaming(&self, response: &mut HttpResponse) -> Option<OutputOrError> {
|
||||
let _ = response;
|
||||
None
|
||||
}
|
||||
|
||||
fn deserialize_nonstreaming(&self, response: &HttpResponse) -> OutputOrError;
|
||||
}
|
||||
|
||||
pub trait Connection: Send + Sync + Debug {
|
||||
fn call(&self, request: &mut HttpRequest, cfg: &ConfigBag) -> BoxFallibleFut<HttpResponse>;
|
||||
}
|
||||
|
||||
pub trait RetryStrategy: Send + Sync + Debug {
|
||||
fn should_attempt_initial_request(&self, cfg: &ConfigBag) -> Result<(), BoxError>;
|
||||
|
||||
fn should_attempt_retry(
|
||||
&self,
|
||||
context: &InterceptorContext<HttpRequest, HttpResponse>,
|
||||
cfg: &ConfigBag,
|
||||
) -> Result<bool, BoxError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuthOptionResolverParams(TypeErasedBox);
|
||||
|
||||
impl AuthOptionResolverParams {
|
||||
pub fn new<T: Any + Send + Sync + 'static>(params: T) -> Self {
|
||||
Self(TypedBox::new(params).erase())
|
||||
}
|
||||
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.0.downcast_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AuthOptionResolver: Send + Sync + Debug {
|
||||
fn resolve_auth_options(
|
||||
&self,
|
||||
params: &AuthOptionResolverParams,
|
||||
) -> Result<Vec<HttpAuthOption>, BoxError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HttpAuthOption {
|
||||
scheme_id: &'static str,
|
||||
properties: Arc<PropertyBag>,
|
||||
}
|
||||
|
||||
impl HttpAuthOption {
|
||||
pub fn new(scheme_id: &'static str, properties: Arc<PropertyBag>) -> Self {
|
||||
Self {
|
||||
scheme_id,
|
||||
properties,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scheme_id(&self) -> &'static str {
|
||||
self.scheme_id
|
||||
}
|
||||
|
||||
pub fn properties(&self) -> &PropertyBag {
|
||||
&self.properties
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IdentityResolver: Send + Sync + Debug {
|
||||
fn resolve_identity(&self, cfg: &ConfigBag) -> Result<Identity, BoxError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IdentityResolvers {
|
||||
identity_resolvers: Vec<(&'static str, Box<dyn IdentityResolver>)>,
|
||||
}
|
||||
|
||||
impl IdentityResolvers {
|
||||
pub fn builder() -> builders::IdentityResolversBuilder {
|
||||
builders::IdentityResolversBuilder::new()
|
||||
}
|
||||
|
||||
pub fn identity_resolver(&self, identity_type: &'static str) -> Option<&dyn IdentityResolver> {
|
||||
self.identity_resolvers
|
||||
.iter()
|
||||
.find(|resolver| resolver.0 == identity_type)
|
||||
.map(|resolver| &*resolver.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HttpAuthSchemesInner {
|
||||
schemes: Vec<(&'static str, Box<dyn HttpAuthScheme>)>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct HttpAuthSchemes {
|
||||
inner: Arc<HttpAuthSchemesInner>,
|
||||
}
|
||||
|
||||
impl HttpAuthSchemes {
|
||||
pub fn builder() -> builders::HttpAuthSchemesBuilder {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn scheme(&self, name: &'static str) -> Option<&dyn HttpAuthScheme> {
|
||||
self.inner
|
||||
.schemes
|
||||
.iter()
|
||||
.find(|scheme| scheme.0 == name)
|
||||
.map(|scheme| &*scheme.1)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HttpAuthScheme: Send + Sync + Debug {
|
||||
fn scheme_id(&self) -> &'static str;
|
||||
|
||||
fn identity_resolver(&self, identity_resolvers: &IdentityResolvers) -> &dyn IdentityResolver;
|
||||
|
||||
fn request_signer(&self) -> &dyn HttpRequestSigner;
|
||||
}
|
||||
|
||||
pub trait HttpRequestSigner: 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(
|
||||
&self,
|
||||
request: &HttpRequest,
|
||||
identity: &Identity,
|
||||
cfg: &ConfigBag,
|
||||
) -> Result<HttpRequest, BoxError>;
|
||||
}
|
||||
|
||||
pub trait EndpointResolver: Send + Sync + Debug {
|
||||
fn resolve_and_apply_endpoint(
|
||||
&self,
|
||||
request: &mut HttpRequest,
|
||||
cfg: &ConfigBag,
|
||||
) -> Result<(), BoxError>;
|
||||
}
|
||||
|
||||
pub trait ConfigBagAccessors {
|
||||
fn auth_option_resolver_params(&self) -> &AuthOptionResolverParams;
|
||||
fn set_auth_option_resolver_params(
|
||||
&mut self,
|
||||
auth_option_resolver_params: AuthOptionResolverParams,
|
||||
);
|
||||
|
||||
fn auth_option_resolver(&self) -> &dyn AuthOptionResolver;
|
||||
fn set_auth_option_resolver(&mut self, auth_option_resolver: impl AuthOptionResolver + 'static);
|
||||
|
||||
fn endpoint_resolver(&self) -> &dyn EndpointResolver;
|
||||
fn set_endpoint_resolver(&mut self, endpoint_resolver: impl EndpointResolver + 'static);
|
||||
|
||||
fn identity_resolvers(&self) -> &IdentityResolvers;
|
||||
fn set_identity_resolvers(&mut self, identity_resolvers: IdentityResolvers);
|
||||
|
||||
fn connection(&self) -> &dyn Connection;
|
||||
fn set_connection(&mut self, connection: impl Connection + 'static);
|
||||
|
||||
fn http_auth_schemes(&self) -> &HttpAuthSchemes;
|
||||
fn set_http_auth_schemes(&mut self, http_auth_schemes: HttpAuthSchemes);
|
||||
|
||||
fn request_serializer(&self) -> &dyn RequestSerializer;
|
||||
fn set_request_serializer(&mut self, request_serializer: impl RequestSerializer + 'static);
|
||||
|
||||
fn response_deserializer(&self) -> &dyn ResponseDeserializer;
|
||||
fn set_response_deserializer(
|
||||
&mut self,
|
||||
response_serializer: impl ResponseDeserializer + 'static,
|
||||
);
|
||||
|
||||
fn retry_strategy(&self) -> &dyn RetryStrategy;
|
||||
fn set_retry_strategy(&mut self, retry_strategy: impl RetryStrategy + 'static);
|
||||
|
||||
fn trace_probe(&self) -> &dyn TraceProbe;
|
||||
fn set_trace_probe(&mut self, trace_probe: impl TraceProbe + 'static);
|
||||
}
|
||||
|
||||
impl ConfigBagAccessors for ConfigBag {
|
||||
fn auth_option_resolver_params(&self) -> &AuthOptionResolverParams {
|
||||
self.get::<AuthOptionResolverParams>()
|
||||
.expect("auth option resolver params must be set")
|
||||
}
|
||||
|
||||
fn set_auth_option_resolver_params(
|
||||
&mut self,
|
||||
auth_option_resolver_params: AuthOptionResolverParams,
|
||||
) {
|
||||
self.put::<AuthOptionResolverParams>(auth_option_resolver_params);
|
||||
}
|
||||
|
||||
fn auth_option_resolver(&self) -> &dyn AuthOptionResolver {
|
||||
&**self
|
||||
.get::<Box<dyn AuthOptionResolver>>()
|
||||
.expect("an auth option resolver must be set")
|
||||
}
|
||||
|
||||
fn set_auth_option_resolver(
|
||||
&mut self,
|
||||
auth_option_resolver: impl AuthOptionResolver + 'static,
|
||||
) {
|
||||
self.put::<Box<dyn AuthOptionResolver>>(Box::new(auth_option_resolver));
|
||||
}
|
||||
|
||||
fn http_auth_schemes(&self) -> &HttpAuthSchemes {
|
||||
self.get::<HttpAuthSchemes>()
|
||||
.expect("auth schemes must be set")
|
||||
}
|
||||
|
||||
fn set_http_auth_schemes(&mut self, http_auth_schemes: HttpAuthSchemes) {
|
||||
self.put::<HttpAuthSchemes>(http_auth_schemes);
|
||||
}
|
||||
|
||||
fn retry_strategy(&self) -> &dyn RetryStrategy {
|
||||
&**self
|
||||
.get::<Box<dyn RetryStrategy>>()
|
||||
.expect("a retry strategy must be set")
|
||||
}
|
||||
|
||||
fn set_retry_strategy(&mut self, retry_strategy: impl RetryStrategy + 'static) {
|
||||
self.put::<Box<dyn RetryStrategy>>(Box::new(retry_strategy));
|
||||
}
|
||||
|
||||
fn endpoint_resolver(&self) -> &dyn EndpointResolver {
|
||||
&**self
|
||||
.get::<Box<dyn EndpointResolver>>()
|
||||
.expect("an endpoint resolver must be set")
|
||||
}
|
||||
|
||||
fn set_endpoint_resolver(&mut self, endpoint_resolver: impl EndpointResolver + 'static) {
|
||||
self.put::<Box<dyn EndpointResolver>>(Box::new(endpoint_resolver));
|
||||
}
|
||||
|
||||
fn identity_resolvers(&self) -> &IdentityResolvers {
|
||||
self.get::<IdentityResolvers>()
|
||||
.expect("identity resolvers must be configured")
|
||||
}
|
||||
|
||||
fn set_identity_resolvers(&mut self, identity_resolvers: IdentityResolvers) {
|
||||
self.put::<IdentityResolvers>(identity_resolvers);
|
||||
}
|
||||
|
||||
fn connection(&self) -> &dyn Connection {
|
||||
&**self
|
||||
.get::<Box<dyn Connection>>()
|
||||
.expect("missing connector")
|
||||
}
|
||||
|
||||
fn set_connection(&mut self, connection: impl Connection + 'static) {
|
||||
self.put::<Box<dyn Connection>>(Box::new(connection));
|
||||
}
|
||||
|
||||
fn request_serializer(&self) -> &dyn RequestSerializer {
|
||||
&**self
|
||||
.get::<Box<dyn RequestSerializer>>()
|
||||
.expect("missing request serializer")
|
||||
}
|
||||
|
||||
fn set_request_serializer(&mut self, request_serializer: impl RequestSerializer + 'static) {
|
||||
self.put::<Box<dyn RequestSerializer>>(Box::new(request_serializer));
|
||||
}
|
||||
|
||||
fn response_deserializer(&self) -> &dyn ResponseDeserializer {
|
||||
&**self
|
||||
.get::<Box<dyn ResponseDeserializer>>()
|
||||
.expect("missing response deserializer")
|
||||
}
|
||||
|
||||
fn set_response_deserializer(
|
||||
&mut self,
|
||||
response_deserializer: impl ResponseDeserializer + 'static,
|
||||
) {
|
||||
self.put::<Box<dyn ResponseDeserializer>>(Box::new(response_deserializer));
|
||||
}
|
||||
|
||||
fn trace_probe(&self) -> &dyn TraceProbe {
|
||||
&**self
|
||||
.get::<Box<dyn TraceProbe>>()
|
||||
.expect("missing trace probe")
|
||||
}
|
||||
|
||||
fn set_trace_probe(&mut self, trace_probe: impl TraceProbe + 'static) {
|
||||
self.put::<Box<dyn TraceProbe>>(Box::new(trace_probe));
|
||||
}
|
||||
}
|
||||
|
||||
pub mod builders {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IdentityResolversBuilder {
|
||||
identity_resolvers: Vec<(&'static str, Box<dyn IdentityResolver>)>,
|
||||
}
|
||||
|
||||
impl IdentityResolversBuilder {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn identity_resolver(
|
||||
mut self,
|
||||
name: &'static str,
|
||||
resolver: impl IdentityResolver + 'static,
|
||||
) -> Self {
|
||||
self.identity_resolvers
|
||||
.push((name, Box::new(resolver) as _));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> IdentityResolvers {
|
||||
IdentityResolvers {
|
||||
identity_resolvers: self.identity_resolvers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HttpAuthSchemesBuilder {
|
||||
schemes: Vec<(&'static str, Box<dyn HttpAuthScheme>)>,
|
||||
}
|
||||
|
||||
impl HttpAuthSchemesBuilder {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn auth_scheme(
|
||||
mut self,
|
||||
name: &'static str,
|
||||
auth_scheme: impl HttpAuthScheme + 'static,
|
||||
) -> Self {
|
||||
self.schemes.push((name, Box::new(auth_scheme) as _));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> HttpAuthSchemes {
|
||||
HttpAuthSchemes {
|
||||
inner: Arc::new(HttpAuthSchemesInner {
|
||||
schemes: self.schemes,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,11 +55,11 @@ impl Token for Standard {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Standard as Token;
|
||||
use crate::retries::rate_limiting::token_bucket::Standard as TokenBucket;
|
||||
use crate::client::retries::rate_limiting::token_bucket::Standard as TokenBucket;
|
||||
|
||||
#[test]
|
||||
fn token_bucket_trait_is_dyn_safe() {
|
||||
let _tb: Box<dyn crate::retries::rate_limiting::TokenBucket<Token = Token>> =
|
||||
let _tb: Box<dyn crate::client::retries::rate_limiting::TokenBucket<Token = Token>> =
|
||||
Box::new(TokenBucket::builder().build());
|
||||
}
|
||||
}
|
|
@ -1,861 +0,0 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
|
||||
use crate::config_bag::ConfigBag;
|
||||
pub use context::InterceptorContext;
|
||||
pub use error::InterceptorError;
|
||||
|
||||
/// An interceptor allows injecting code into the SDK ’s request execution pipeline.
|
||||
///
|
||||
/// ## Terminology:
|
||||
/// - An execution is one end-to-end invocation against an SDK client.
|
||||
/// - An attempt is an attempt at performing an execution. By default executions are retried multiple
|
||||
/// times based on the client ’s retry strategy.
|
||||
/// - A hook is a single method on the interceptor, allowing injection of code into a specific part
|
||||
/// of the SDK ’s request execution pipeline. Hooks are either "read" hooks, which make it possible
|
||||
/// to read in-flight request or response messages, or "read/write" hooks, which make it possible
|
||||
/// to modify in-flight request or output messages.
|
||||
pub trait Interceptor<ModReq, TxReq, TxRes, ModRes> {
|
||||
/// A hook called at the start of an execution, before the SDK
|
||||
/// does anything else.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per execution. The duration
|
||||
/// between invocation of this hook and `after_execution` is very close
|
||||
/// to full duration of the execution.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()] is
|
||||
/// **ALWAYS** available. Other information **WILL NOT** be available.
|
||||
///
|
||||
/// **Error Behavior:** Errors raised by this hook will be stored
|
||||
/// until all interceptors have had their `before_execution` invoked.
|
||||
/// Other hooks will then be skipped and execution will jump to
|
||||
/// `modify_before_completion` with the raised error as the
|
||||
/// [InterceptorContext::modeled_response()]. If multiple
|
||||
/// `before_execution` methods raise errors, the latest
|
||||
/// will be used and earlier ones will be logged and dropped.
|
||||
fn read_before_execution(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the input message is marshalled into a
|
||||
/// transport message.
|
||||
/// This method has the ability to modify and return a new
|
||||
/// request message of the same type.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per execution, except when a
|
||||
/// failure occurs earlier in the request pipeline.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()] is
|
||||
/// **ALWAYS** available. This request may have been modified by earlier
|
||||
/// `modify_before_serialization` hooks, and may be modified further by
|
||||
/// later hooks. Other information **WILL NOT** be available.
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this hook,
|
||||
///
|
||||
/// execution will jump to `modify_before_completion` with the raised
|
||||
/// error as the [InterceptorContext::modeled_response()].
|
||||
///
|
||||
/// **Return Constraints:** The input message returned by this hook
|
||||
/// MUST be the same type of input message passed into this hook.
|
||||
/// If not, an error will immediately be raised.
|
||||
fn modify_before_serialization(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the input message is marshalled
|
||||
/// into a transport
|
||||
/// message.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per execution, except when a
|
||||
/// failure occurs earlier in the request pipeline. The
|
||||
/// duration between invocation of this hook and `after_serialization` is
|
||||
/// very close to the amount of time spent marshalling the request.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()] is
|
||||
/// **ALWAYS** available. Other information **WILL NOT** be available.
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this hook,
|
||||
/// execution will jump to `modify_before_completion` with the raised
|
||||
/// error as the [InterceptorContext::modeled_response()].
|
||||
fn read_before_serialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called after the input message is marshalled into
|
||||
/// a transport message.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per execution, except when a
|
||||
/// failure occurs earlier in the request pipeline. The duration
|
||||
/// between invocation of this hook and `before_serialization` is very
|
||||
/// close to the amount of time spent marshalling the request.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available.
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this hook,
|
||||
/// execution will jump to `modify_before_completion` with the raised
|
||||
/// error as the [InterceptorContext::modeled_response()].
|
||||
fn read_after_serialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the retry loop is entered. This method
|
||||
/// has the ability to modify and return a new transport request
|
||||
/// message of the same type, except when a failure occurs earlier in the request pipeline.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available.
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this hook,
|
||||
/// execution will jump to `modify_before_completion` with the raised
|
||||
/// error as the [InterceptorContext::modeled_response()].
|
||||
///
|
||||
/// **Return Constraints:** The transport request message returned by this
|
||||
/// hook MUST be the same type of request message passed into this hook
|
||||
/// If not, an error will immediately be raised.
|
||||
fn modify_before_retry_loop(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before each attempt at sending the transmission
|
||||
/// request message to the service.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method will be
|
||||
/// called multiple times in the event of retries.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available. In the event of retries,
|
||||
/// the `InterceptorContext` will not include changes made in previous
|
||||
/// attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** Errors raised by this hook will be stored
|
||||
/// until all interceptors have had their `before_attempt` invoked.
|
||||
/// Other hooks will then be skipped and execution will jump to
|
||||
/// `modify_before_attempt_completion` with the raised error as the
|
||||
/// [InterceptorContext::modeled_response()]. If multiple
|
||||
/// `before_attempt` methods raise errors, the latest will be used
|
||||
/// and earlier ones will be logged and dropped.
|
||||
fn read_before_attempt(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the transport request message is signed.
|
||||
/// This method has the ability to modify and return a new transport
|
||||
/// request message of the same type.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// The `http::Request` may have been modified by earlier
|
||||
/// `modify_before_signing` hooks, and may be modified further by later
|
||||
/// hooks. Other information **WILL NOT** be available. In the event of
|
||||
/// retries, the `InterceptorContext` will not include changes made
|
||||
/// in previous attempts
|
||||
/// (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
///
|
||||
/// **Return Constraints:** The transport request message returned by this
|
||||
/// hook MUST be the same type of request message passed into this hook
|
||||
///
|
||||
/// If not, an error will immediately be raised.
|
||||
fn modify_before_signing(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the transport request message is signed.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries. The duration between
|
||||
/// invocation of this hook and `after_signing` is very close to
|
||||
/// the amount of time spent signing the request.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available. In the event of retries,
|
||||
/// the `InterceptorContext` will not include changes made in previous
|
||||
/// attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
fn read_before_signing(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called after the transport request message is signed.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries. The duration between
|
||||
/// invocation of this hook and `before_signing` is very close to
|
||||
/// the amount of time spent signing the request.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available. In the event of retries,
|
||||
/// the `InterceptorContext` will not include changes made in previous
|
||||
/// attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
fn read_after_signing(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the transport request message is sent to the
|
||||
/// service. This method has the ability to modify and return
|
||||
/// a new transport request message of the same type.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// The `http::Request` may have been modified by earlier
|
||||
/// `modify_before_transmit` hooks, and may be modified further by later
|
||||
/// hooks. Other information **WILL NOT** be available.
|
||||
/// In the event of retries, the `InterceptorContext` will not include
|
||||
/// changes made in previous attempts (e.g. by request signers or
|
||||
/// other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
///
|
||||
/// **Return Constraints:** The transport request message returned by this
|
||||
/// hook MUST be the same type of request message passed into this hook
|
||||
///
|
||||
/// If not, an error will immediately be raised.
|
||||
fn modify_before_transmit(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the transport request message is sent to the
|
||||
/// service.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries. The duration between
|
||||
/// invocation of this hook and `after_transmit` is very close to
|
||||
/// the amount of time spent communicating with the service.
|
||||
/// Depending on the protocol, the duration may not include the
|
||||
/// time spent reading the response data.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::tx_request()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available. In the event of retries,
|
||||
/// the `InterceptorContext` will not include changes made in previous
|
||||
/// attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
fn read_before_transmit(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called after the transport request message is sent to the
|
||||
/// service and a transport response message is received.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries. The duration between
|
||||
/// invocation of this hook and `before_transmit` is very close to
|
||||
/// the amount of time spent communicating with the service.
|
||||
/// Depending on the protocol, the duration may not include the time
|
||||
/// spent reading the response data.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()],
|
||||
/// [InterceptorContext::tx_request()] and
|
||||
/// [InterceptorContext::tx_response()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available. In the event of retries,
|
||||
/// the `InterceptorContext` will not include changes made in previous
|
||||
/// attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
fn read_after_transmit(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the transport response message is unmarshalled.
|
||||
/// This method has the ability to modify and return a new transport
|
||||
/// response message of the same type.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()],
|
||||
/// [InterceptorContext::tx_request()] and
|
||||
/// [InterceptorContext::tx_response()] are **ALWAYS** available.
|
||||
/// The transmit_response may have been modified by earlier
|
||||
/// `modify_before_deserialization` hooks, and may be modified further by
|
||||
/// later hooks. Other information **WILL NOT** be available. In the event of
|
||||
/// retries, the `InterceptorContext` will not include changes made in
|
||||
/// previous attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the
|
||||
/// [InterceptorContext::modeled_response()].
|
||||
///
|
||||
/// **Return Constraints:** The transport response message returned by this
|
||||
/// hook MUST be the same type of response message passed into
|
||||
/// this hook. If not, an error will immediately be raised.
|
||||
fn modify_before_deserialization(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called before the transport response message is unmarshalled
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. This method may be
|
||||
/// called multiple times in the event of retries. The duration between
|
||||
/// invocation of this hook and `after_deserialization` is very close
|
||||
/// to the amount of time spent unmarshalling the service response.
|
||||
/// Depending on the protocol and operation, the duration may include
|
||||
/// the time spent downloading the response data.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()],
|
||||
/// [InterceptorContext::tx_request()] and
|
||||
/// [InterceptorContext::tx_response()] are **ALWAYS** available.
|
||||
/// Other information **WILL NOT** be available. In the event of retries,
|
||||
/// the `InterceptorContext` will not include changes made in previous
|
||||
/// attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion`
|
||||
/// with the raised error as the [InterceptorContext::modeled_response()].
|
||||
fn read_before_deserialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called after the transport response message is unmarshalled.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs earlier in the request pipeline. The duration
|
||||
/// between invocation of this hook and `before_deserialization` is
|
||||
/// very close to the amount of time spent unmarshalling the
|
||||
/// service response. Depending on the protocol and operation,
|
||||
/// the duration may include the time spent downloading
|
||||
/// the response data.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()],
|
||||
/// [InterceptorContext::tx_request()],
|
||||
/// [InterceptorContext::tx_response()] and
|
||||
/// [InterceptorContext::modeled_response()] are **ALWAYS** available. In the event
|
||||
/// of retries, the `InterceptorContext` will not include changes made
|
||||
/// in previous attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `modify_before_attempt_completion` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
fn read_after_deserialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called when an attempt is completed. This method has the
|
||||
/// ability to modify and return a new output message or error
|
||||
/// matching the currently-executing operation.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, except when a
|
||||
/// failure occurs before `before_attempt`. This method may
|
||||
/// be called multiple times in the event of retries.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()],
|
||||
/// [InterceptorContext::tx_request()],
|
||||
/// [InterceptorContext::tx_response()] and
|
||||
/// [InterceptorContext::modeled_response()] are **ALWAYS** available. In the event
|
||||
/// of retries, the `InterceptorContext` will not include changes made
|
||||
/// in previous attempts (e.g. by request signers or other interceptors).
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook, execution will jump to `after_attempt` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
///
|
||||
/// **Return Constraints:** Any output message returned by this
|
||||
/// hook MUST match the operation being invoked. Any error type can be
|
||||
/// returned, replacing the response currently in the context.
|
||||
fn modify_before_attempt_completion(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called when an attempt is completed.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per attempt, as long as
|
||||
/// `before_attempt` has been executed.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()],
|
||||
/// [InterceptorContext::tx_request()] and
|
||||
/// [InterceptorContext::modeled_response()] are **ALWAYS** available.
|
||||
/// The [InterceptorContext::tx_response()] is available if a
|
||||
/// response was received by the service for this attempt.
|
||||
/// In the event of retries, the `InterceptorContext` will not include
|
||||
/// changes made in previous attempts (e.g. by request signers or other
|
||||
/// interceptors).
|
||||
///
|
||||
/// **Error Behavior:** Errors raised by this hook will be stored
|
||||
/// until all interceptors have had their `after_attempt` invoked.
|
||||
/// If multiple `after_execution` methods raise errors, the latest
|
||||
/// will be used and earlier ones will be logged and dropped. If the
|
||||
/// retry strategy determines that the execution is retryable,
|
||||
/// execution will then jump to `before_attempt`. Otherwise,
|
||||
/// execution will jump to `modify_before_attempt_completion` with the
|
||||
/// raised error as the [InterceptorContext::modeled_response()].
|
||||
fn read_after_attempt(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called when an execution is completed.
|
||||
/// This method has the ability to modify and return a new
|
||||
/// output message or error matching the currently - executing
|
||||
/// operation.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per execution.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::modeled_response()] are **ALWAYS** available. The
|
||||
/// [InterceptorContext::tx_request()]
|
||||
/// and [InterceptorContext::tx_response()] are available if the
|
||||
/// execution proceeded far enough for them to be generated.
|
||||
///
|
||||
/// **Error Behavior:** If errors are raised by this
|
||||
/// hook , execution will jump to `after_attempt` with
|
||||
/// the raised error as the [InterceptorContext::modeled_response()].
|
||||
///
|
||||
/// **Return Constraints:** Any output message returned by this
|
||||
/// hook MUST match the operation being invoked. Any error type can be
|
||||
/// returned , replacing the response currently in the context.
|
||||
fn modify_before_completion(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hook called when an execution is completed.
|
||||
///
|
||||
/// **When:** This will **ALWAYS** be called once per execution. The duration
|
||||
/// between invocation of this hook and `before_execution` is very
|
||||
/// close to the full duration of the execution.
|
||||
///
|
||||
/// **Available Information:** The [InterceptorContext::modeled_request()]
|
||||
/// and [InterceptorContext::modeled_response()] are **ALWAYS** available. The
|
||||
/// [InterceptorContext::tx_request()] and
|
||||
/// [InterceptorContext::tx_response()] are available if the
|
||||
/// execution proceeded far enough for them to be generated.
|
||||
///
|
||||
/// **Error Behavior:** Errors raised by this hook will be stored
|
||||
/// until all interceptors have had their `after_execution` invoked.
|
||||
/// The error will then be treated as the
|
||||
/// [InterceptorContext::modeled_response()] to the customer. If multiple
|
||||
/// `after_execution` methods raise errors , the latest will be
|
||||
/// used and earlier ones will be logged and dropped.
|
||||
fn read_after_execution(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
let _ctx = context;
|
||||
let _cfg = cfg;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Interceptors<ModReq, TxReq, TxRes, ModRes> {
|
||||
client_interceptors: Vec<Box<dyn Interceptor<ModReq, TxReq, TxRes, ModRes>>>,
|
||||
operation_interceptors: Vec<Box<dyn Interceptor<ModReq, TxReq, TxRes, ModRes>>>,
|
||||
}
|
||||
|
||||
impl<ModReq, TxReq, TxRes, ModRes> Default for Interceptors<ModReq, TxReq, TxRes, ModRes> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
client_interceptors: Vec::new(),
|
||||
operation_interceptors: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ModReq, TxReq, TxRes, ModRes> Interceptors<ModReq, TxReq, TxRes, ModRes> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_client_interceptor(
|
||||
&mut self,
|
||||
interceptor: impl Interceptor<ModReq, TxReq, TxRes, ModRes> + 'static,
|
||||
) -> &mut Self {
|
||||
self.client_interceptors.push(Box::new(interceptor));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_operation_interceptor(
|
||||
&mut self,
|
||||
interceptor: impl Interceptor<ModReq, TxReq, TxRes, ModRes> + 'static,
|
||||
) -> &mut Self {
|
||||
self.operation_interceptors.push(Box::new(interceptor));
|
||||
self
|
||||
}
|
||||
|
||||
fn all_interceptors_mut(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut Box<dyn Interceptor<ModReq, TxReq, TxRes, ModRes>>> {
|
||||
self.client_interceptors
|
||||
.iter_mut()
|
||||
.chain(self.operation_interceptors.iter_mut())
|
||||
}
|
||||
|
||||
pub fn client_read_before_execution(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.client_interceptors.iter_mut() {
|
||||
interceptor.read_before_execution(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn operation_read_before_execution(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.operation_interceptors.iter_mut() {
|
||||
interceptor.read_before_execution(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn modify_before_serialization(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.modify_before_serialization(context, cfg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_before_serialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_before_serialization(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_after_serialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_after_serialization(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn modify_before_retry_loop(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.modify_before_retry_loop(context, cfg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_before_attempt(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_before_attempt(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn modify_before_signing(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.modify_before_signing(context, cfg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_before_signing(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_before_signing(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_after_signing(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_after_signing(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn modify_before_transmit(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.modify_before_transmit(context, cfg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_before_transmit(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_before_transmit(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_after_transmit(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_after_transmit(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn modify_before_deserialization(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.modify_before_deserialization(context, cfg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_before_deserialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_before_deserialization(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_after_deserialization(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_after_deserialization(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn modify_before_attempt_completion(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.modify_before_attempt_completion(context, cfg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_after_attempt(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_after_attempt(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn modify_before_completion(
|
||||
&mut self,
|
||||
context: &mut InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.modify_before_completion(context, cfg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_after_execution(
|
||||
&mut self,
|
||||
context: &InterceptorContext<ModReq, TxReq, TxRes, ModRes>,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<(), InterceptorError> {
|
||||
for interceptor in self.all_interceptors_mut() {
|
||||
interceptor.read_after_execution(context, cfg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use super::InterceptorError;
|
||||
|
||||
/// A container for the data currently available to an interceptor.
|
||||
pub struct InterceptorContext<ModReq, TxReq, TxRes, ModRes> {
|
||||
modeled_request: ModReq,
|
||||
tx_request: Option<TxReq>,
|
||||
modeled_response: Option<ModRes>,
|
||||
tx_response: Option<TxRes>,
|
||||
}
|
||||
|
||||
// TODO(interceptors) we could use types to ensure that people calling methods on interceptor context can't access
|
||||
// field that haven't been set yet.
|
||||
impl<ModReq, TxReq, TxRes, ModRes> InterceptorContext<ModReq, TxReq, TxRes, ModRes> {
|
||||
pub fn new(request: ModReq) -> Self {
|
||||
Self {
|
||||
modeled_request: request,
|
||||
tx_request: None,
|
||||
tx_response: None,
|
||||
modeled_response: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the modeled request for the operation being invoked.
|
||||
pub fn modeled_request(&self) -> &ModReq {
|
||||
&self.modeled_request
|
||||
}
|
||||
|
||||
/// Retrieve the modeled request for the operation being invoked.
|
||||
pub fn modeled_request_mut(&mut self) -> &mut ModReq {
|
||||
&mut self.modeled_request
|
||||
}
|
||||
|
||||
/// Retrieve the transmittable request for the operation being invoked.
|
||||
/// This will only be available once request marshalling has completed.
|
||||
pub fn tx_request(&self) -> Result<&TxReq, InterceptorError> {
|
||||
self.tx_request
|
||||
.as_ref()
|
||||
.ok_or_else(InterceptorError::invalid_tx_request_access)
|
||||
}
|
||||
|
||||
/// Retrieve the transmittable request for the operation being invoked.
|
||||
/// This will only be available once request marshalling has completed.
|
||||
pub fn tx_request_mut(&mut self) -> Result<&mut TxReq, InterceptorError> {
|
||||
self.tx_request
|
||||
.as_mut()
|
||||
.ok_or_else(InterceptorError::invalid_tx_request_access)
|
||||
}
|
||||
|
||||
/// Retrieve the response to the transmittable request for the operation
|
||||
/// being invoked. This will only be available once transmission has
|
||||
/// completed.
|
||||
pub fn tx_response(&self) -> Result<&TxRes, InterceptorError> {
|
||||
self.tx_response
|
||||
.as_ref()
|
||||
.ok_or_else(InterceptorError::invalid_tx_response_access)
|
||||
}
|
||||
|
||||
/// Retrieve the response to the transmittable request for the operation
|
||||
/// being invoked. This will only be available once transmission has
|
||||
/// completed.
|
||||
pub fn tx_response_mut(&mut self) -> Result<&mut TxRes, InterceptorError> {
|
||||
self.tx_response
|
||||
.as_mut()
|
||||
.ok_or_else(InterceptorError::invalid_tx_response_access)
|
||||
}
|
||||
|
||||
/// Retrieve the response to the customer. This will only be available
|
||||
/// once the `tx_response` has been unmarshalled or the
|
||||
/// attempt/execution has failed.
|
||||
pub fn modeled_response(&self) -> Result<&ModRes, InterceptorError> {
|
||||
self.modeled_response
|
||||
.as_ref()
|
||||
.ok_or_else(InterceptorError::invalid_modeled_response_access)
|
||||
}
|
||||
|
||||
/// Retrieve the response to the customer. This will only be available
|
||||
/// once the `tx_response` has been unmarshalled or the
|
||||
/// attempt/execution has failed.
|
||||
pub fn modeled_response_mut(&mut self) -> Result<&mut ModRes, InterceptorError> {
|
||||
self.modeled_response
|
||||
.as_mut()
|
||||
.ok_or_else(InterceptorError::invalid_modeled_response_access)
|
||||
}
|
||||
|
||||
// There is no set_modeled_request method because that can only be set once, during context construction
|
||||
|
||||
pub fn set_tx_request(&mut self, transmit_request: TxReq) {
|
||||
if self.tx_request.is_some() {
|
||||
panic!("Called set_tx_request but a transmit_request was already set. This is a bug, pleases report it.");
|
||||
}
|
||||
|
||||
self.tx_request = Some(transmit_request);
|
||||
}
|
||||
|
||||
pub fn set_tx_response(&mut self, transmit_response: TxRes) {
|
||||
if self.tx_response.is_some() {
|
||||
panic!("Called set_tx_response but a transmit_response was already set. This is a bug, pleases report it.");
|
||||
}
|
||||
|
||||
self.tx_response = Some(transmit_response);
|
||||
}
|
||||
|
||||
pub fn set_modeled_response(&mut self, modeled_response: ModRes) {
|
||||
if self.modeled_response.is_some() {
|
||||
panic!("Called set_modeled_response but a modeled_response was already set. This is a bug, pleases report it.");
|
||||
}
|
||||
|
||||
self.modeled_response = Some(modeled_response);
|
||||
}
|
||||
|
||||
pub fn into_responses(self) -> Result<(ModRes, TxRes), InterceptorError> {
|
||||
let mod_res = self
|
||||
.modeled_response
|
||||
.ok_or_else(InterceptorError::invalid_modeled_response_access)?;
|
||||
let tx_res = self
|
||||
.tx_response
|
||||
.ok_or_else(InterceptorError::invalid_tx_response_access)?;
|
||||
|
||||
Ok((mod_res, tx_res))
|
||||
}
|
||||
}
|
|
@ -12,16 +12,11 @@
|
|||
|
||||
//! Basic types for the new smithy client orchestrator.
|
||||
|
||||
/// Smithy runtime for client orchestration.
|
||||
pub mod client;
|
||||
|
||||
/// A typemap for storing configuration.
|
||||
pub mod config_bag;
|
||||
/// Smithy interceptors for smithy clients.
|
||||
///
|
||||
/// Interceptors are lifecycle hooks that can read/modify requests and responses.
|
||||
pub mod interceptors;
|
||||
/// 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_plugin;
|
||||
|
||||
/// Utilities for type erasure.
|
||||
pub mod type_erasure;
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use std::any::Any;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A [`TypeErasedBox`] with type information tracked via generics at compile-time
|
||||
///
|
||||
/// `TypedBox` is used to transition to/from a `TypeErasedBox`. A `TypedBox<T>` can only
|
||||
/// be created from a `T` or from a `TypeErasedBox` value that _is a_ `T`. Therefore, it can
|
||||
/// be assumed to be a `T` even though the underlying storage is still a `TypeErasedBox`.
|
||||
/// Since the `T` is only used in `PhantomData`, it gets compiled down to just a `TypeErasedBox`.
|
||||
///
|
||||
/// The orchestrator uses `TypeErasedBox` to avoid the complication of six or more generic parameters
|
||||
/// and to avoid the monomorphization that brings with it. This `TypedBox` will primarily be useful
|
||||
/// for operation-specific or service-specific interceptors that need to operate on the actual
|
||||
/// input/output/error types.
|
||||
#[derive(Debug)]
|
||||
pub struct TypedBox<T> {
|
||||
inner: TypeErasedBox,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> TypedBox<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
// Creates a new `TypedBox`.
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner: TypeErasedBox::new(Box::new(inner) as _),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to create a `TypedBox<T>` from a `TypeErasedBox`.
|
||||
//
|
||||
// If the `TypedBox<T>` can't be created due to the `TypeErasedBox`'s value consisting
|
||||
// of another type, then the original `TypeErasedBox` will be returned in the `Err` variant.
|
||||
pub fn assume_from(type_erased: TypeErasedBox) -> Result<TypedBox<T>, TypeErasedBox> {
|
||||
if type_erased.downcast_ref::<T>().is_some() {
|
||||
Ok(TypedBox {
|
||||
inner: type_erased,
|
||||
_phantom: Default::default(),
|
||||
})
|
||||
} else {
|
||||
Err(type_erased)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the `TypedBox<T>` back into `T`.
|
||||
pub fn unwrap(self) -> T {
|
||||
*self.inner.downcast::<T>().expect("type checked")
|
||||
}
|
||||
|
||||
/// Converts the `TypedBox<T>` into a `TypeErasedBox`.
|
||||
pub fn erase(self) -> TypeErasedBox {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Deref for TypedBox<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner.downcast_ref().expect("type checked")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> DerefMut for TypedBox<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.inner.downcast_mut().expect("type checked")
|
||||
}
|
||||
}
|
||||
|
||||
/// A new-type around `Box<dyn Any + Send + Sync>`
|
||||
#[derive(Debug)]
|
||||
pub struct TypeErasedBox {
|
||||
inner: Box<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
impl TypeErasedBox {
|
||||
// Creates a new `TypeErasedBox`.
|
||||
pub fn new(inner: Box<dyn Any + Send + Sync>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
// Downcast into a `Box<T>`, or return `Self` if it is not a `T`.
|
||||
pub fn downcast<T: 'static>(self) -> Result<Box<T>, Self> {
|
||||
match self.inner.downcast() {
|
||||
Ok(t) => Ok(t),
|
||||
Err(s) => Err(Self { inner: s }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcast as a `&T`, or return `None` if it is not a `T`.
|
||||
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
|
||||
self.inner.downcast_ref()
|
||||
}
|
||||
|
||||
/// Downcast as a `&mut T`, or return `None` if it is not a `T`.
|
||||
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.inner.downcast_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Foo(&'static str);
|
||||
#[derive(Debug)]
|
||||
struct Bar(isize);
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let foo = TypedBox::new(Foo("1"));
|
||||
let bar = TypedBox::new(Bar(2));
|
||||
|
||||
let mut foo_erased = foo.erase();
|
||||
foo_erased
|
||||
.downcast_mut::<Foo>()
|
||||
.expect("I know its a Foo")
|
||||
.0 = "3";
|
||||
|
||||
let bar_erased = bar.erase();
|
||||
|
||||
let bar_erased = TypedBox::<Foo>::assume_from(bar_erased).expect_err("it's not a Foo");
|
||||
let mut bar = TypedBox::<Bar>::assume_from(bar_erased).expect("it's a Bar");
|
||||
assert_eq!(2, bar.0);
|
||||
bar.0 += 1;
|
||||
|
||||
let bar = bar.unwrap();
|
||||
assert_eq!(3, bar.0);
|
||||
|
||||
assert!(foo_erased.downcast_ref::<Bar>().is_none());
|
||||
assert!(foo_erased.downcast_mut::<Bar>().is_none());
|
||||
let mut foo_erased = foo_erased.downcast::<Bar>().expect_err("it's not a Bar");
|
||||
|
||||
assert_eq!("3", foo_erased.downcast_ref::<Foo>().expect("it's a Foo").0);
|
||||
foo_erased.downcast_mut::<Foo>().expect("it's a Foo").0 = "4";
|
||||
let foo = *foo_erased.downcast::<Foo>().expect("it's a Foo");
|
||||
assert_eq!("4", foo.0);
|
||||
}
|
||||
}
|
|
@ -12,10 +12,13 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
aws-smithy-http = { path = "../aws-smithy-http" }
|
||||
aws-smithy-types = { path = "../aws-smithy-types" }
|
||||
aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api" }
|
||||
aws-smithy-types = { path = "../aws-smithy-types" }
|
||||
bytes = "1"
|
||||
http = "0.2.8"
|
||||
http-body = "0.4.5"
|
||||
pin-utils = "0.1.0"
|
||||
tracing = "0.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
pub mod orchestrator;
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use self::auth::orchestrate_auth;
|
||||
use crate::client::orchestrator::http::read_body;
|
||||
use crate::client::orchestrator::phase::Phase;
|
||||
use aws_smithy_http::result::SdkError;
|
||||
use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, Output};
|
||||
use aws_smithy_runtime_api::client::interceptors::{InterceptorContext, Interceptors};
|
||||
use aws_smithy_runtime_api::client::orchestrator::{
|
||||
BoxError, ConfigBagAccessors, HttpRequest, HttpResponse,
|
||||
};
|
||||
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins;
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use tracing::{debug_span, Instrument};
|
||||
|
||||
mod auth;
|
||||
mod http;
|
||||
pub(self) mod phase;
|
||||
|
||||
pub async fn invoke(
|
||||
input: Input,
|
||||
interceptors: &mut Interceptors<HttpRequest, HttpResponse>,
|
||||
runtime_plugins: &RuntimePlugins,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<Output, SdkError<Error, HttpResponse>> {
|
||||
let context = Phase::construction(InterceptorContext::new(input))
|
||||
// Client configuration
|
||||
.include(|_| runtime_plugins.apply_client_configuration(cfg))?
|
||||
.include(|ctx| interceptors.client_read_before_execution(ctx, cfg))?
|
||||
// Operation configuration
|
||||
.include(|_| runtime_plugins.apply_operation_configuration(cfg))?
|
||||
.include(|ctx| interceptors.operation_read_before_execution(ctx, cfg))?
|
||||
// Before serialization
|
||||
.include(|ctx| interceptors.read_before_serialization(ctx, cfg))?
|
||||
.include_mut(|ctx| interceptors.modify_before_serialization(ctx, cfg))?
|
||||
// Serialization
|
||||
.include_mut(|ctx| {
|
||||
let request_serializer = cfg.request_serializer();
|
||||
let request = request_serializer.serialize_input(ctx.input(), cfg)?;
|
||||
ctx.set_request(request);
|
||||
Result::<(), BoxError>::Ok(())
|
||||
})?
|
||||
// After serialization
|
||||
.include(|ctx| interceptors.read_after_serialization(ctx, cfg))?
|
||||
// Before retry loop
|
||||
.include_mut(|ctx| interceptors.modify_before_retry_loop(ctx, cfg))?
|
||||
.finish();
|
||||
|
||||
{
|
||||
let retry_strategy = cfg.retry_strategy();
|
||||
match retry_strategy.should_attempt_initial_request(cfg) {
|
||||
// Yes, let's make a request
|
||||
Ok(_) => {}
|
||||
// No, we shouldn't make a request because...
|
||||
Err(err) => return Err(Phase::dispatch(context).fail(err)),
|
||||
}
|
||||
}
|
||||
|
||||
let mut context = context;
|
||||
let handling_phase = loop {
|
||||
let dispatch_phase = Phase::dispatch(context);
|
||||
context = make_an_attempt(dispatch_phase, cfg, interceptors)
|
||||
.await?
|
||||
.include(|ctx| interceptors.read_after_attempt(ctx, cfg))?
|
||||
.include_mut(|ctx| interceptors.modify_before_attempt_completion(ctx, cfg))?
|
||||
.finish();
|
||||
|
||||
let retry_strategy = cfg.retry_strategy();
|
||||
match retry_strategy.should_attempt_retry(&context, cfg) {
|
||||
// Yes, let's retry the request
|
||||
Ok(true) => continue,
|
||||
// No, this request shouldn't be retried
|
||||
Ok(false) => {}
|
||||
// I couldn't determine if the request should be retried because an error occurred.
|
||||
Err(err) => {
|
||||
return Err(Phase::response_handling(context).fail(err));
|
||||
}
|
||||
}
|
||||
|
||||
let handling_phase = Phase::response_handling(context)
|
||||
.include_mut(|ctx| interceptors.modify_before_completion(ctx, cfg))?;
|
||||
let trace_probe = cfg.trace_probe();
|
||||
trace_probe.dispatch_events(cfg);
|
||||
|
||||
break handling_phase.include(|ctx| interceptors.read_after_execution(ctx, cfg))?;
|
||||
};
|
||||
|
||||
handling_phase.finalize()
|
||||
}
|
||||
|
||||
// Making an HTTP request can fail for several reasons, but we still need to
|
||||
// call lifecycle events when that happens. Therefore, we define this
|
||||
// `make_an_attempt` function to make error handling simpler.
|
||||
async fn make_an_attempt(
|
||||
dispatch_phase: Phase,
|
||||
cfg: &mut ConfigBag,
|
||||
interceptors: &mut Interceptors<HttpRequest, HttpResponse>,
|
||||
) -> Result<Phase, SdkError<Error, HttpResponse>> {
|
||||
let dispatch_phase = dispatch_phase
|
||||
.include(|ctx| interceptors.read_before_attempt(ctx, cfg))?
|
||||
.include_mut(|ctx| {
|
||||
let request = ctx.request_mut().expect("request has been set");
|
||||
|
||||
let endpoint_resolver = cfg.endpoint_resolver();
|
||||
endpoint_resolver.resolve_and_apply_endpoint(request, cfg)
|
||||
})?
|
||||
.include_mut(|ctx| interceptors.modify_before_signing(ctx, cfg))?
|
||||
.include(|ctx| interceptors.read_before_signing(ctx, cfg))?;
|
||||
|
||||
let dispatch_phase = orchestrate_auth(dispatch_phase, cfg).await?;
|
||||
|
||||
let mut context = dispatch_phase
|
||||
.include(|ctx| interceptors.read_after_signing(ctx, cfg))?
|
||||
.include_mut(|ctx| interceptors.modify_before_transmit(ctx, cfg))?
|
||||
.include(|ctx| interceptors.read_before_transmit(ctx, cfg))?
|
||||
.finish();
|
||||
|
||||
// The connection consumes the request but we need to keep a copy of it
|
||||
// within the interceptor context, so we clone it here.
|
||||
let call_result = {
|
||||
let tx_req = context.request_mut().expect("request has been set");
|
||||
let connection = cfg.connection();
|
||||
connection.call(tx_req, cfg).await
|
||||
};
|
||||
|
||||
let mut context = Phase::dispatch(context)
|
||||
.include_mut(move |ctx| {
|
||||
ctx.set_response(call_result?);
|
||||
Result::<(), BoxError>::Ok(())
|
||||
})?
|
||||
.include(|ctx| interceptors.read_after_transmit(ctx, cfg))?
|
||||
.include_mut(|ctx| interceptors.modify_before_deserialization(ctx, cfg))?
|
||||
.include(|ctx| interceptors.read_before_deserialization(ctx, cfg))?
|
||||
.finish();
|
||||
|
||||
let output_or_error = {
|
||||
let response = context.response_mut().expect("response has been set");
|
||||
let response_deserializer = cfg.response_deserializer();
|
||||
match response_deserializer.deserialize_streaming(response) {
|
||||
Some(output_or_error) => Ok(output_or_error),
|
||||
None => read_body(response)
|
||||
.instrument(debug_span!("read_body"))
|
||||
.await
|
||||
.map(|_| response_deserializer.deserialize_nonstreaming(response)),
|
||||
}
|
||||
};
|
||||
|
||||
Phase::response_handling(context)
|
||||
.include_mut(move |ctx| {
|
||||
ctx.set_output_or_error(output_or_error?);
|
||||
Result::<(), BoxError>::Ok(())
|
||||
})?
|
||||
.include(|ctx| interceptors.read_after_deserialization(ctx, cfg))
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use super::phase::Phase;
|
||||
use aws_smithy_http::result::SdkError;
|
||||
use aws_smithy_runtime_api::client::interceptors::context::Error;
|
||||
use aws_smithy_runtime_api::client::orchestrator::{BoxError, ConfigBagAccessors, HttpResponse};
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
|
||||
pub(super) async fn orchestrate_auth(
|
||||
dispatch_phase: Phase,
|
||||
cfg: &ConfigBag,
|
||||
) -> Result<Phase, SdkError<Error, HttpResponse>> {
|
||||
dispatch_phase.include_mut(|ctx| {
|
||||
let params = cfg.auth_option_resolver_params();
|
||||
let auth_options = cfg.auth_option_resolver().resolve_auth_options(params)?;
|
||||
let identity_resolvers = cfg.identity_resolvers();
|
||||
|
||||
for option in auth_options {
|
||||
let scheme_id = option.scheme_id();
|
||||
if let Some(auth_scheme) = cfg.http_auth_schemes().scheme(scheme_id) {
|
||||
let identity_resolver = auth_scheme.identity_resolver(identity_resolvers);
|
||||
let request_signer = auth_scheme.request_signer();
|
||||
|
||||
let identity = identity_resolver.resolve_identity(cfg)?;
|
||||
let request = ctx.request_mut()?;
|
||||
request_signer.sign_request(request, &identity, cfg)?;
|
||||
return Result::<_, BoxError>::Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err("No auth scheme matched auth options. This is a bug. Please file an issue.".into())
|
||||
})
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_smithy_runtime_api::client::orchestrator::HttpResponse;
|
||||
use bytes::{Buf, Bytes};
|
||||
use http_body::Body;
|
||||
use pin_utils::pin_mut;
|
||||
|
||||
async fn body_to_bytes(body: SdkBody) -> Result<Bytes, <SdkBody as Body>::Error> {
|
||||
let mut output = Vec::new();
|
||||
pin_mut!(body);
|
||||
while let Some(buf) = body.data().await {
|
||||
let mut buf = buf?;
|
||||
while buf.has_remaining() {
|
||||
output.extend_from_slice(buf.chunk());
|
||||
buf.advance(buf.chunk().len())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Bytes::from(output))
|
||||
}
|
||||
|
||||
pub(crate) async fn read_body(response: &mut HttpResponse) -> Result<(), <SdkBody as Body>::Error> {
|
||||
let mut body = SdkBody::taken();
|
||||
std::mem::swap(&mut body, response.body_mut());
|
||||
|
||||
let bytes = body_to_bytes(body).await?;
|
||||
let mut body = SdkBody::from(bytes);
|
||||
std::mem::swap(&mut body, response.body_mut());
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use aws_smithy_http::result::{ConnectorError, SdkError};
|
||||
use aws_smithy_runtime_api::client::interceptors::context::{Error, Output};
|
||||
use aws_smithy_runtime_api::client::interceptors::InterceptorContext;
|
||||
use aws_smithy_runtime_api::client::orchestrator::{BoxError, HttpRequest, HttpResponse};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
enum OrchestrationPhase {
|
||||
Construction,
|
||||
Dispatch,
|
||||
ResponseHandling,
|
||||
}
|
||||
|
||||
pub(super) struct Phase {
|
||||
phase: OrchestrationPhase,
|
||||
context: InterceptorContext<HttpRequest, HttpResponse>,
|
||||
}
|
||||
|
||||
impl Phase {
|
||||
pub(crate) fn construction(context: InterceptorContext<HttpRequest, HttpResponse>) -> Self {
|
||||
Self::start(OrchestrationPhase::Construction, context)
|
||||
}
|
||||
pub(crate) fn dispatch(context: InterceptorContext<HttpRequest, HttpResponse>) -> Self {
|
||||
Self::start(OrchestrationPhase::Dispatch, context)
|
||||
}
|
||||
pub(crate) fn response_handling(
|
||||
context: InterceptorContext<HttpRequest, HttpResponse>,
|
||||
) -> Self {
|
||||
Self::start(OrchestrationPhase::ResponseHandling, context)
|
||||
}
|
||||
|
||||
fn start(
|
||||
phase: OrchestrationPhase,
|
||||
context: InterceptorContext<HttpRequest, HttpResponse>,
|
||||
) -> Self {
|
||||
match phase {
|
||||
OrchestrationPhase::Construction => {}
|
||||
OrchestrationPhase::Dispatch => debug_assert!(context.request().is_ok()),
|
||||
OrchestrationPhase::ResponseHandling => debug_assert!(context.response().is_ok()),
|
||||
}
|
||||
Self { phase, context }
|
||||
}
|
||||
|
||||
pub(crate) fn include_mut<E: Into<BoxError>>(
|
||||
mut self,
|
||||
c: impl FnOnce(&mut InterceptorContext<HttpRequest, HttpResponse>) -> Result<(), E>,
|
||||
) -> Result<Self, SdkError<Error, HttpResponse>> {
|
||||
match c(&mut self.context) {
|
||||
Ok(_) => Ok(self),
|
||||
Err(e) => Err(self.fail(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn include<E: Into<BoxError>>(
|
||||
self,
|
||||
c: impl FnOnce(&InterceptorContext<HttpRequest, HttpResponse>) -> Result<(), E>,
|
||||
) -> Result<Self, SdkError<Error, HttpResponse>> {
|
||||
match c(&self.context) {
|
||||
Ok(_) => Ok(self),
|
||||
Err(e) => Err(self.fail(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fail(self, e: impl Into<BoxError>) -> SdkError<Error, HttpResponse> {
|
||||
self.into_sdk_error(e.into())
|
||||
}
|
||||
|
||||
pub(crate) fn finalize(self) -> Result<Output, SdkError<Error, HttpResponse>> {
|
||||
debug_assert!(self.phase == OrchestrationPhase::ResponseHandling);
|
||||
let (_input, output_or_error, _request, response) = self.context.into_parts();
|
||||
match output_or_error {
|
||||
Some(output_or_error) => match output_or_error {
|
||||
Ok(output) => Ok(output),
|
||||
Err(error) => Err(SdkError::service_error(
|
||||
error,
|
||||
response.expect("response must be set by this point"),
|
||||
)),
|
||||
},
|
||||
None => unreachable!("phase can't get this far without bubbling up a failure"),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_sdk_error(self, e: BoxError) -> SdkError<Error, HttpResponse> {
|
||||
let e = match e.downcast::<ConnectorError>() {
|
||||
Ok(connector_error) => {
|
||||
debug_assert!(
|
||||
self.phase == OrchestrationPhase::Dispatch,
|
||||
"connector errors should only occur during the dispatch phase"
|
||||
);
|
||||
return SdkError::dispatch_failure(*connector_error);
|
||||
}
|
||||
Err(e) => e,
|
||||
};
|
||||
let (_input, output_or_error, _request, response) = self.context.into_parts();
|
||||
match self.phase {
|
||||
OrchestrationPhase::Construction => SdkError::construction_failure(e),
|
||||
OrchestrationPhase::Dispatch => {
|
||||
if let Some(response) = response {
|
||||
SdkError::response_error(e, response)
|
||||
} else {
|
||||
SdkError::dispatch_failure(ConnectorError::other(e, None))
|
||||
}
|
||||
}
|
||||
OrchestrationPhase::ResponseHandling => match (response, output_or_error) {
|
||||
(Some(response), Some(Err(error))) => SdkError::service_error(error, response),
|
||||
(Some(response), _) => SdkError::response_error(e, response),
|
||||
_ => unreachable!("response handling phase at least has a response"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finish(self) -> InterceptorContext<HttpRequest, HttpResponse> {
|
||||
self.context
|
||||
}
|
||||
}
|
|
@ -10,172 +10,4 @@
|
|||
rust_2018_idioms
|
||||
)]
|
||||
|
||||
use aws_smithy_runtime_api::config_bag::ConfigBag;
|
||||
use aws_smithy_runtime_api::interceptors::{InterceptorContext, Interceptors};
|
||||
use aws_smithy_runtime_api::runtime_plugin::RuntimePlugins;
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
pub type BoxFallibleFut<T> = Pin<Box<dyn Future<Output = Result<T, BoxError>>>>;
|
||||
|
||||
pub trait TraceProbe: Send + Sync + Debug {
|
||||
fn dispatch_events(&self, cfg: &ConfigBag) -> BoxFallibleFut<()>;
|
||||
}
|
||||
|
||||
pub trait RequestSerializer<In, TxReq>: Send + Sync + Debug {
|
||||
fn serialize_request(&self, req: &mut In, cfg: &ConfigBag) -> Result<TxReq, BoxError>;
|
||||
}
|
||||
|
||||
pub trait ResponseDeserializer<TxRes, Out>: Send + Sync + Debug {
|
||||
fn deserialize_response(&self, res: &mut TxRes, cfg: &ConfigBag) -> Result<Out, BoxError>;
|
||||
}
|
||||
|
||||
pub trait Connection<TxReq, TxRes>: Send + Sync + Debug {
|
||||
fn call(&self, req: &mut TxReq, cfg: &ConfigBag) -> BoxFallibleFut<TxRes>;
|
||||
}
|
||||
|
||||
pub trait RetryStrategy<Out>: Send + Sync + Debug {
|
||||
fn should_retry(&self, res: &Out, cfg: &ConfigBag) -> Result<bool, BoxError>;
|
||||
}
|
||||
|
||||
pub trait AuthOrchestrator<Req>: Send + Sync + Debug {
|
||||
fn auth_request(&self, req: &mut Req, cfg: &ConfigBag) -> Result<(), BoxError>;
|
||||
}
|
||||
|
||||
pub trait EndpointOrchestrator<Req>: Send + Sync + Debug {
|
||||
fn resolve_and_apply_endpoint(&self, req: &mut Req, cfg: &ConfigBag) -> Result<(), BoxError>;
|
||||
// TODO(jdisanti) The EP Orc and Auth Orc need to share info on auth schemes but I'm not sure how that should happen
|
||||
fn resolve_auth_schemes(&self) -> Result<Vec<String>, BoxError>;
|
||||
}
|
||||
|
||||
/// `In`: The input message e.g. `ListObjectsRequest`
|
||||
/// `Req`: The transport request message e.g. `http::Request<SmithyBody>`
|
||||
/// `Res`: The transport response message e.g. `http::Response<SmithyBody>`
|
||||
/// `Out`: The output message. A `Result` containing either:
|
||||
/// - The 'success' output message e.g. `ListObjectsResponse`
|
||||
/// - The 'failure' output message e.g. `NoSuchBucketException`
|
||||
pub async fn invoke<In, Req, Res, T>(
|
||||
input: In,
|
||||
interceptors: &mut Interceptors<In, Req, Res, Result<T, BoxError>>,
|
||||
runtime_plugins: &RuntimePlugins,
|
||||
cfg: &mut ConfigBag,
|
||||
) -> Result<T, BoxError>
|
||||
where
|
||||
// The input must be Clone in case of retries
|
||||
In: Clone + 'static,
|
||||
Req: 'static,
|
||||
Res: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let mut ctx: InterceptorContext<In, Req, Res, Result<T, BoxError>> =
|
||||
InterceptorContext::new(input);
|
||||
|
||||
runtime_plugins.apply_client_configuration(cfg)?;
|
||||
interceptors.client_read_before_execution(&ctx, cfg)?;
|
||||
|
||||
runtime_plugins.apply_operation_configuration(cfg)?;
|
||||
interceptors.operation_read_before_execution(&ctx, cfg)?;
|
||||
|
||||
interceptors.read_before_serialization(&ctx, cfg)?;
|
||||
interceptors.modify_before_serialization(&mut ctx, cfg)?;
|
||||
|
||||
let request_serializer = cfg
|
||||
.get::<Box<dyn RequestSerializer<In, Req>>>()
|
||||
.ok_or("missing serializer")?;
|
||||
let req = request_serializer.serialize_request(ctx.modeled_request_mut(), cfg)?;
|
||||
ctx.set_tx_request(req);
|
||||
|
||||
interceptors.read_after_serialization(&ctx, cfg)?;
|
||||
interceptors.modify_before_retry_loop(&mut ctx, cfg)?;
|
||||
|
||||
loop {
|
||||
make_an_attempt(&mut ctx, cfg, interceptors).await?;
|
||||
interceptors.read_after_attempt(&ctx, cfg)?;
|
||||
interceptors.modify_before_attempt_completion(&mut ctx, cfg)?;
|
||||
|
||||
let retry_strategy = cfg
|
||||
.get::<Box<dyn RetryStrategy<Result<T, BoxError>>>>()
|
||||
.ok_or("missing retry strategy")?;
|
||||
let mod_res = ctx
|
||||
.modeled_response()
|
||||
.expect("it's set during 'make_an_attempt'");
|
||||
if retry_strategy.should_retry(mod_res, cfg)? {
|
||||
continue;
|
||||
}
|
||||
|
||||
interceptors.modify_before_completion(&mut ctx, cfg)?;
|
||||
let trace_probe = cfg
|
||||
.get::<Box<dyn TraceProbe>>()
|
||||
.ok_or("missing trace probes")?;
|
||||
trace_probe.dispatch_events(cfg);
|
||||
interceptors.read_after_execution(&ctx, cfg)?;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
let (modeled_response, _) = ctx.into_responses()?;
|
||||
modeled_response
|
||||
}
|
||||
|
||||
// Making an HTTP request can fail for several reasons, but we still need to
|
||||
// call lifecycle events when that happens. Therefore, we define this
|
||||
// `make_an_attempt` function to make error handling simpler.
|
||||
async fn make_an_attempt<In, Req, Res, T>(
|
||||
ctx: &mut InterceptorContext<In, Req, Res, Result<T, BoxError>>,
|
||||
cfg: &mut ConfigBag,
|
||||
interceptors: &mut Interceptors<In, Req, Res, Result<T, BoxError>>,
|
||||
) -> Result<(), BoxError>
|
||||
where
|
||||
In: Clone + 'static,
|
||||
Req: 'static,
|
||||
Res: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
interceptors.read_before_attempt(ctx, cfg)?;
|
||||
|
||||
let tx_req_mut = ctx.tx_request_mut().expect("tx_request has been set");
|
||||
let endpoint_orchestrator = cfg
|
||||
.get::<Box<dyn EndpointOrchestrator<Req>>>()
|
||||
.ok_or("missing endpoint orchestrator")?;
|
||||
endpoint_orchestrator.resolve_and_apply_endpoint(tx_req_mut, cfg)?;
|
||||
|
||||
interceptors.modify_before_signing(ctx, cfg)?;
|
||||
interceptors.read_before_signing(ctx, cfg)?;
|
||||
|
||||
let tx_req_mut = ctx.tx_request_mut().expect("tx_request has been set");
|
||||
let auth_orchestrator = cfg
|
||||
.get::<Box<dyn AuthOrchestrator<Req>>>()
|
||||
.ok_or("missing auth orchestrator")?;
|
||||
auth_orchestrator.auth_request(tx_req_mut, cfg)?;
|
||||
|
||||
interceptors.read_after_signing(ctx, cfg)?;
|
||||
interceptors.modify_before_transmit(ctx, cfg)?;
|
||||
interceptors.read_before_transmit(ctx, cfg)?;
|
||||
|
||||
// The connection consumes the request but we need to keep a copy of it
|
||||
// within the interceptor context, so we clone it here.
|
||||
let res = {
|
||||
let tx_req = ctx.tx_request_mut().expect("tx_request has been set");
|
||||
let connection = cfg
|
||||
.get::<Box<dyn Connection<Req, Res>>>()
|
||||
.ok_or("missing connector")?;
|
||||
connection.call(tx_req, cfg).await?
|
||||
};
|
||||
ctx.set_tx_response(res);
|
||||
|
||||
interceptors.read_after_transmit(ctx, cfg)?;
|
||||
interceptors.modify_before_deserialization(ctx, cfg)?;
|
||||
interceptors.read_before_deserialization(ctx, cfg)?;
|
||||
let tx_res = ctx.tx_response_mut().expect("tx_response has been set");
|
||||
let response_deserializer = cfg
|
||||
.get::<Box<dyn ResponseDeserializer<Res, Result<T, BoxError>>>>()
|
||||
.ok_or("missing response deserializer")?;
|
||||
let res = response_deserializer.deserialize_response(tx_res, cfg)?;
|
||||
ctx.set_modeled_response(res);
|
||||
|
||||
interceptors.read_after_deserialization(ctx, cfg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub mod client;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
use aws_smithy_json::deserialize::token::skip_value;
|
||||
use aws_smithy_json::deserialize::{error::DeserializeError, json_token_iter, Token};
|
||||
use aws_smithy_types::error::metadata::{Builder as ErrorMetadataBuilder, ErrorMetadata};
|
||||
use bytes::Bytes;
|
||||
use http::header::ToStrError;
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use std::borrow::Cow;
|
||||
|
@ -83,10 +82,10 @@ fn error_type_from_header(headers: &HeaderMap<HeaderValue>) -> Result<Option<&st
|
|||
}
|
||||
|
||||
pub fn parse_error_metadata(
|
||||
payload: &Bytes,
|
||||
payload: &[u8],
|
||||
headers: &HeaderMap<HeaderValue>,
|
||||
) -> Result<ErrorMetadataBuilder, DeserializeError> {
|
||||
let ErrorBody { code, message } = parse_error_body(payload.as_ref())?;
|
||||
let ErrorBody { code, message } = parse_error_body(payload)?;
|
||||
|
||||
let mut err_builder = ErrorMetadata::builder();
|
||||
if let Some(code) = error_type_from_header(headers)
|
||||
|
|
|
@ -13,10 +13,11 @@ include(":codegen-server:python")
|
|||
include(":codegen-server-test")
|
||||
include(":codegen-server-test:python")
|
||||
include(":rust-runtime")
|
||||
include(":aws:sdk-codegen")
|
||||
include(":aws:sdk-adhoc-test")
|
||||
include(":aws:sdk")
|
||||
include(":aws:rust-runtime")
|
||||
include(":aws:sdk")
|
||||
include(":aws:sdk-adhoc-test")
|
||||
include(":aws:sdk-codegen")
|
||||
include(":aws:sra-test")
|
||||
|
||||
pluginManagement {
|
||||
val smithyGradlePluginVersion: String by settings
|
||||
|
|
Loading…
Reference in New Issue