diff --git a/aws/sdk/integration-tests/Cargo.toml b/aws/sdk/integration-tests/Cargo.toml index 406b718a94b..5a803583c73 100644 --- a/aws/sdk/integration-tests/Cargo.toml +++ b/aws/sdk/integration-tests/Cargo.toml @@ -15,4 +15,5 @@ members = [ "s3control", "sts", "transcribestreaming", + "smithy_orchestrator" ] diff --git a/aws/sdk/integration-tests/s3/tests/interceptors/mod.rs b/aws/sdk/integration-tests/s3/tests/interceptors/mod.rs deleted file mode 100644 index a770d18c092..00000000000 --- a/aws/sdk/integration-tests/s3/tests/interceptors/mod.rs +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -use aws_credential_types::cache::SharedCredentialsCache; -use aws_credential_types::provider::error::CredentialsError; -use aws_sdk_s3::model::ChecksumMode; -use aws_sig_auth::signer::OperationSigningConfig; -use aws_smithy_http::body::SdkBody; -use aws_smithy_interceptor::{Interceptor, InterceptorContext}; -use aws_smithy_types::error::InterceptorError; -use futures_util::FutureExt; - -type TxReq = http::Request; -type TxRes = http::Response; - -pub struct SigV4SigningConfigInterceptor { - pub signing_service: &'static str, - pub signing_region: Option, -} - -impl Interceptor for SigV4SigningConfigInterceptor { - fn modify_before_signing( - &mut self, - context: &mut InterceptorContext, - ) -> Result<(), InterceptorError> { - let mut props = context.properties_mut(); - - let mut signing_config = OperationSigningConfig::default_config(); - signing_config.signing_options.content_sha256_header = true; - signing_config.signing_options.double_uri_encode = false; - signing_config.signing_options.normalize_uri_path = false; - props.insert(signing_config); - props.insert(aws_types::SigningService::from_static(self.signing_service)); - - if let Some(signing_region) = self.signing_region.as_ref() { - props.insert(aws_types::region::SigningRegion::from( - signing_region.clone(), - )); - } - - Ok(()) - } -} - -pub struct CredentialsCacheInterceptor { - pub shared_credentials_cache: SharedCredentialsCache, -} - -impl Interceptor for CredentialsCacheInterceptor { - fn modify_before_signing( - &mut self, - context: &mut InterceptorContext, - ) -> Result<(), InterceptorError> { - match self - .shared_credentials_cache - .as_ref() - .provide_cached_credentials() - .now_or_never() - { - Some(Ok(creds)) => { - context.properties_mut().insert(creds); - } - // ignore the case where there is no credentials cache wired up - Some(Err(CredentialsError::CredentialsNotLoaded { .. })) => { - tracing::info!("credentials cache returned CredentialsNotLoaded, ignoring") - } - // if we get another error class, there is probably something actually wrong that the user will - // want to know about - Some(Err(other)) => return Err(InterceptorError::ModifyBeforeSigning(other.into())), - None => unreachable!("fingers crossed that creds are always available"), - } - - Ok(()) - } -} - -pub struct ChecksumInterceptor { - pub checksum_mode: Option, -} - -impl Interceptor for ChecksumInterceptor { - fn modify_before_serialization( - &mut self, - context: &mut InterceptorContext, - ) -> Result<(), InterceptorError> { - let mut props = context.properties_mut(); - props.insert(self.checksum_mode.clone()); - - Ok(()) - } -} diff --git a/aws/sdk/integration-tests/s3/tests/operation_v2.rs b/aws/sdk/integration-tests/s3/tests/operation_v2.rs deleted file mode 100644 index 593184351fd..00000000000 --- a/aws/sdk/integration-tests/s3/tests/operation_v2.rs +++ /dev/null @@ -1,608 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -mod interceptors; - -use crate::interceptors::{ - ChecksumInterceptor, CredentialsCacheInterceptor, SigV4SigningConfigInterceptor, -}; -use aws_http::retry::AwsResponseRetryClassifier; -use aws_sdk_s3::config::Config; -use aws_sdk_s3::endpoint; -use aws_sdk_s3::error::GetObjectError; -use aws_sdk_s3::input::GetObjectInput; -use aws_sdk_s3::model::ChecksumMode; -use aws_sdk_s3::output::GetObjectOutput; -use aws_sig_auth::signer::{OperationSigningConfig, SigV4Signer, SigningRequirements}; -use aws_smithy_client::hyper_ext::Adapter; -use aws_smithy_http::body::SdkBody; -use aws_smithy_http::operation; -use aws_smithy_http::operation::error::BuildError; -use aws_smithy_http::property_bag::PropertyBag; -use aws_smithy_http::result::{SdkError, SdkSuccess}; -use aws_smithy_http::retry::ClassifyRetry; -use aws_smithy_interceptor::Interceptors; -use aws_smithy_orchestrator::{invoke, Metadata, OperationV2, Parts}; -use aws_smithy_token_bucket::standard; -use aws_smithy_types::config::SmithyConfig; -use aws_smithy_types::retry::RetryKind; -use hyper::service::Service; -use std::str::from_utf8; -use std::sync::Arc; -use tracing::info; - -#[tokio::test] -async fn test_get_object_with_operation_v2() { - tracing_subscriber::fmt::init(); - - // Create the config we'll need to send the request + the request itself - let sdk_config = aws_config::load_from_env().await; - let service_config = Config::from(&sdk_config); - let config = SmithyConfig { - retry: service_config.retry_config().unwrap().clone(), - timeout: service_config.timeout_config().unwrap().clone(), - }; - let metadata = Some(Metadata::new("GetObject", service_config.signing_service())); - let input = GetObjectInput::builder() - .bucket("zhessler-test-bucket") - .key("1000-lines.txt") - .checksum_mode(ChecksumMode::Enabled) - .build() - .unwrap(); - - // Create the endpoint parameters - let endpoint_parameters = endpoint::Params::builder() - .set_region( - service_config - .region - .as_ref() - .map(|r| r.as_ref().to_owned()), - ) - .set_use_fips(service_config.use_fips) - .set_use_dual_stack(service_config.use_dual_stack) - .set_endpoint(service_config.endpoint_url.clone()) - .set_force_path_style(service_config.force_path_style) - .set_use_arn_region(service_config.use_arn_region) - .set_disable_multi_region_access_points(service_config.disable_multi_region_access_points) - .set_accelerate(service_config.accelerate) - .set_bucket(input.bucket.clone()) - .build() - .expect("params are valid"); - - // Mount the interceptors - let mut interceptors = Interceptors::new(); - let sig_v4_signing_config_interceptor = SigV4SigningConfigInterceptor { - signing_region: service_config.region.clone(), - signing_service: service_config.signing_service(), - }; - let credentials_cache_interceptor = CredentialsCacheInterceptor { - shared_credentials_cache: service_config.credentials_cache.clone(), - }; - let checksum_interceptor = ChecksumInterceptor { - checksum_mode: input.checksum_mode().cloned(), - }; - interceptors - .with_interceptor(sig_v4_signing_config_interceptor) - .with_interceptor(credentials_cache_interceptor) - .with_interceptor(checksum_interceptor); - - let token_bucket = Box::new(standard::TokenBucket::builder().max_tokens(500).build()); - - // Assemble the parts together - let parts = Parts { - signer: Arc::new(|req: &mut http::Request, props: &PropertyBag| { - use aws_smithy_orchestrator::auth::error::Error; - - let signer = SigV4Signer::new(); - let operation_config = props - .get::() - .ok_or(Error::SignRequest("missing signing config".into()))?; - - 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(|err| Error::SignRequest(Box::new(err)))?, - }; - - let _signature = signer - .sign(&operation_config, &request_config, &creds, req) - .expect("signing goes just fine"); - - Ok(()) - }), - interceptors, - metadata, - response_deserializer: Arc::new(|res: &mut http::Response| { - deserialize_response(res) - }), - token_bucket, - request_serializer: Arc::new(|req| serialize_request(req)), - endpoint_resolver: service_config.endpoint_resolver(), - config, - retry_classifier: Arc::new( - |res: Result<&SdkSuccess, &SdkError>| -> RetryKind { - let classifier = AwsResponseRetryClassifier::new(); - classifier.classify_retry(res) - }, - ), - endpoint_parameters, - }; - - // Now put it all together https://youtu.be/UfSGusoftv8?t=27 - let op = OperationV2::reconstitute(input, parts); - let connection = Arc::new(|req| { - let mut adapter = Adapter::builder().build(aws_smithy_client::conns::https()); - adapter.call(req) - }); - - // Did it work? - let res = invoke(op, connection) - .await - .expect("request should succeed"); - - info!("{:#?}", res.parsed); - - let body = res - .parsed - .body - .collect() - .await - .expect("body is OK") - .to_vec(); - let body_string = from_utf8(&body).expect("file is UTF-8"); - - info!("{body_string}"); -} - -fn serialize_request(input: GetObjectInput) -> Result, BuildError> { - 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 { - 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")) -} - -fn deserialize_response( - response: &mut http::Response, -) -> Result { - // TODO make prop bag accessible here - let properties = PropertyBag::new(); - - Ok({ - #[allow(unused_mut)] - let mut output = aws_sdk_s3::output::get_object_output::Builder::default(); - let _ = response; - output = output.set_accept_ranges( - aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_accept_ranges( - response.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( - response.body_mut(), - )?, - )); - output = output.set_bucket_key_enabled( - aws_sdk_s3::http_serde::deser_header_get_object_get_object_output_bucket_key_enabled(response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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( - response.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(response.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( - response.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(response.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( - response.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( - response.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( - response.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( - response.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(response.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(response.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(response.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(response.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( - response.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( - response.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( - response.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(response.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(response) - .map(str::to_string), - ); - output._set_request_id( - aws_http::request_id::RequestId::request_id(response).map(str::to_string), - ); - let response_algorithms = ["crc32", "crc32c", "sha256", "sha1"].as_slice(); - let checksum_mode = properties.get::(); - // 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( - response.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() - }) -} diff --git a/aws/sdk/integration-tests/smithy_orchestrator/Cargo.toml b/aws/sdk/integration-tests/smithy_orchestrator/Cargo.toml new file mode 100644 index 00000000000..b91c5fadeb0 --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "smithy_orchestrator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } +aws-config = { path = "../../build/aws-sdk/sdk/aws-config" } +aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } +aws-sigv4 = { path = "../../build/aws-sdk/sdk/aws-sigv4" } +aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3" } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["rt-tokio"] } +aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client" } +aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } +aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-interceptor = { path = "../../build/aws-sdk/sdk/aws-smithy-interceptor" } +aws-smithy-orchestrator = { path = "../../build/aws-sdk/sdk/aws-smithy-orchestrator" } +aws-smithy-token-bucket = { path = "../../build/aws-sdk/sdk/aws-smithy-token-bucket" } +aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } +tokio = { version = "1.8.4", features = ["macros", "test-util", "rt-multi-thread"] } +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.15", features = ["env-filter", "json"] } +http = "0.2.3" +http-body = "0.4.5" diff --git a/aws/sdk/integration-tests/smithy_orchestrator/src/auth.rs b/aws/sdk/integration-tests/smithy_orchestrator/src/auth.rs new file mode 100644 index 00000000000..5eb98e29689 --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/src/auth.rs @@ -0,0 +1,54 @@ +/* + * 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_orchestrator::{AuthOrchestrator, BoxErr, ConfigBag}; + +pub struct GetObjectAuthOrc {} + +impl GetObjectAuthOrc { + pub fn new() -> Self { + Self {} + } +} + +impl AuthOrchestrator> for GetObjectAuthOrc { + fn auth_request( + &self, + req: &mut http::Request, + cfg: &ConfigBag, + ) -> Result<(), BoxErr> { + todo!() + } +} + +// signer: Arc::new(|req: &mut http::Request, props: &PropertyBag| { +// use aws_smithy_orchestrator::auth::error::Error; +// +// let signer = SigV4Signer::new(); +// let operation_config = props +// .get::() +// .ok_or(Error::SignRequest("missing signing config".into()))?; +// +// 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(|err| Error::SignRequest(Box::new(err)))?, +// }; +// +// let _signature = signer +// .sign(&operation_config, &request_config, &creds, req) +// .expect("signing goes just fine"); +// +// Ok(()) +// }), diff --git a/aws/sdk/integration-tests/smithy_orchestrator/src/conn.rs b/aws/sdk/integration-tests/smithy_orchestrator/src/conn.rs new file mode 100644 index 00000000000..53b7102a05e --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/src/conn.rs @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_smithy_client::conns::Https; +use aws_smithy_client::hyper_ext::Adapter; +use aws_smithy_http::body::SdkBody; +use aws_smithy_orchestrator::{BoxFallibleFut, ConfigBag, Connection}; + +pub struct HyperConnection { + adapter: Adapter, +} + +impl HyperConnection { + pub fn new() -> Self { + Self { + adapter: Adapter::builder().build(aws_smithy_client::conns::https()), + } + } +} + +impl Connection, http::Response> for HyperConnection { + fn call( + &self, + req: &mut http::Request, + cfg: &ConfigBag, + ) -> BoxFallibleFut> { + todo!("hyper's connector wants to take ownership of req"); + // self.adapter.call(req) + } +} diff --git a/aws/sdk/integration-tests/smithy_orchestrator/src/de.rs b/aws/sdk/integration-tests/smithy_orchestrator/src/de.rs new file mode 100644 index 00000000000..afb7cf13c74 --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/src/de.rs @@ -0,0 +1,366 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_sdk_s3::output::GetObjectOutput; +use aws_smithy_http::body::SdkBody; +use aws_smithy_orchestrator::{BoxErr, ConfigBag, ResponseDeserializer}; + +pub struct GetObjectResponseDeserializer {} + +impl GetObjectResponseDeserializer { + pub fn new() -> Self { + Self {} + } +} + +impl ResponseDeserializer, GetObjectOutput> + for GetObjectResponseDeserializer +{ + fn deserialize_response( + &self, + res: &mut http::Response, + cfg: &ConfigBag, + ) -> Result { + 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::(); + // // 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() + // }) + } +} diff --git a/aws/sdk/integration-tests/smithy_orchestrator/src/interceptors.rs b/aws/sdk/integration-tests/smithy_orchestrator/src/interceptors.rs new file mode 100644 index 00000000000..fd4c8649c04 --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/src/interceptors.rs @@ -0,0 +1,103 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// type TxReq = http::Request; +// type TxRes = http::Response; +// +// pub struct SigV4SigningConfigInterceptor { +// pub signing_service: &'static str, +// pub signing_region: Option, +// } + +// // Mount the interceptors +// let mut interceptors = Interceptors::new(); +// let sig_v4_signing_config_interceptor = SigV4SigningConfigInterceptor { +// signing_region: service_config.region.clone(), +// signing_service: service_config.signing_service(), +// }; +// let credentials_cache_interceptor = CredentialsCacheInterceptor { +// shared_credentials_cache: service_config.credentials_cache.clone(), +// }; +// let checksum_interceptor = ChecksumInterceptor { +// checksum_mode: input.checksum_mode().cloned(), +// }; +// interceptors +// .with_interceptor(sig_v4_signing_config_interceptor) +// .with_interceptor(credentials_cache_interceptor) +// .with_interceptor(checksum_interceptor); + +// let token_bucket = Box::new(standard::TokenBucket::builder().max_tokens(500).build()); +// +// impl Interceptor for SigV4SigningConfigInterceptor { +// fn modify_before_signing( +// &mut self, +// context: &mut InterceptorContext, +// ) -> Result<(), InterceptorError> { +// let mut props = context.properties_mut(); +// +// let mut signing_config = OperationSigningConfig::default_config(); +// signing_config.signing_options.content_sha256_header = true; +// signing_config.signing_options.double_uri_encode = false; +// signing_config.signing_options.normalize_uri_path = false; +// props.insert(signing_config); +// props.insert(aws_types::SigningService::from_static(self.signing_service)); +// +// if let Some(signing_region) = self.signing_region.as_ref() { +// props.insert(aws_types::region::SigningRegion::from( +// signing_region.clone(), +// )); +// } +// +// Ok(()) +// } +// } +// +// pub struct CredentialsCacheInterceptor { +// pub shared_credentials_cache: SharedCredentialsCache, +// } +// +// impl Interceptor for CredentialsCacheInterceptor { +// fn modify_before_signing( +// &mut self, +// context: &mut InterceptorContext, +// ) -> Result<(), InterceptorError> { +// match self +// .shared_credentials_cache +// .as_ref() +// .provide_cached_credentials() +// .now_or_never() +// { +// Some(Ok(creds)) => { +// context.properties_mut().insert(creds); +// } +// // ignore the case where there is no credentials cache wired up +// Some(Err(CredentialsError::CredentialsNotLoaded { .. })) => { +// tracing::info!("credentials cache returned CredentialsNotLoaded, ignoring") +// } +// // if we get another error class, there is probably something actually wrong that the user will +// // want to know about +// Some(Err(other)) => return Err(InterceptorError::ModifyBeforeSigning(other.into())), +// None => unreachable!("fingers crossed that creds are always available"), +// } +// +// Ok(()) +// } +// } +// +// pub struct ChecksumInterceptor { +// pub checksum_mode: Option, +// } +// +// impl Interceptor for ChecksumInterceptor { +// fn modify_before_serialization( +// &mut self, +// context: &mut InterceptorContext, +// ) -> Result<(), InterceptorError> { +// let mut props = context.properties_mut(); +// props.insert(self.checksum_mode.clone()); +// +// Ok(()) +// } +// } diff --git a/aws/sdk/integration-tests/smithy_orchestrator/src/main.rs b/aws/sdk/integration-tests/smithy_orchestrator/src/main.rs new file mode 100644 index 00000000000..39c1ada52ed --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/src/main.rs @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +mod auth; +mod conn; +mod de; +mod interceptors; +mod retry; +mod ser; + +use aws_sdk_s3::input::GetObjectInput; +use aws_sdk_s3::model::ChecksumMode; +use aws_sdk_s3::output::GetObjectOutput; +use aws_smithy_http::body::SdkBody; +use aws_smithy_interceptor::Interceptors; +use aws_smithy_orchestrator::{invoke, BoxErr, ConfigBag}; +use std::str::from_utf8; +use tracing::info; + +#[tokio::main] +async fn main() -> Result<(), BoxErr> { + tracing_subscriber::fmt::init(); + + // Create the config we'll need to send the request + the request itself + let sdk_config = aws_config::load_from_env().await; + let service_config = aws_sdk_s3::Config::from(&sdk_config); + // TODO Make it so these are added by default for S3 + // .with_runtime_plugin(auth::GetObjectAuthOrc::new()) + // .with_runtime_plugin(conn::HyperConnection::new()); + + let input = GetObjectInput::builder() + .bucket("zhessler-test-bucket") + .key("1000-lines.txt") + .checksum_mode(ChecksumMode::Enabled) + // TODO Make it so these are added by default for this S3 operation + // .with_runtime_plugin(retry::GetObjectRetryStrategy::new()) + // .with_runtime_plugin(de::GetObjectResponseDeserializer::new()) + // .with_runtime_plugin(ser::GetObjectInputSerializer::new()) + .build()?; + + let mut cfg = ConfigBag::new(); + let mut interceptors: Interceptors< + GetObjectInput, + http::Request, + http::Response, + Result, + > = Interceptors::new(); + let res = invoke(input, &mut interceptors, &mut cfg).await?; + + let body = res.body.collect().await?.to_vec(); + let body_string = from_utf8(&body)?; + + info!("{body_string}"); + + Ok(()) +} diff --git a/aws/sdk/integration-tests/smithy_orchestrator/src/retry.rs b/aws/sdk/integration-tests/smithy_orchestrator/src/retry.rs new file mode 100644 index 00000000000..af4312e818e --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/src/retry.rs @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_sdk_s3::error::GetObjectError; +use aws_sdk_s3::output::GetObjectOutput; +use aws_smithy_orchestrator::{BoxErr, ConfigBag, RetryStrategy}; + +// retry_classifier: Arc::new( +// |res: Result<&SdkSuccess, &SdkError>| -> RetryKind { +// let classifier = AwsResponseRetryClassifier::new(); +// classifier.classify_retry(res) +// }, +// ), + +pub struct GetObjectRetryStrategy {} + +impl GetObjectRetryStrategy { + pub fn new() -> Self { + Self {} + } +} + +impl RetryStrategy> for GetObjectRetryStrategy { + fn should_retry( + &self, + res: &Result, + cfg: &ConfigBag, + ) -> Result { + todo!() + } +} diff --git a/aws/sdk/integration-tests/smithy_orchestrator/src/ser.rs b/aws/sdk/integration-tests/smithy_orchestrator/src/ser.rs new file mode 100644 index 00000000000..74213f308bc --- /dev/null +++ b/aws/sdk/integration-tests/smithy_orchestrator/src/ser.rs @@ -0,0 +1,135 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_sdk_s3::input::GetObjectInput; +use aws_smithy_http::body::SdkBody; +use aws_smithy_orchestrator::{BoxErr, ConfigBag, RequestSerializer}; + +pub struct GetObjectInputSerializer {} + +impl GetObjectInputSerializer { + pub fn new() -> Self { + Self {} + } +} + +impl RequestSerializer> for GetObjectInputSerializer { + fn serialize_request( + &self, + input: &mut GetObjectInput, + cfg: &ConfigBag, + ) -> Result, BoxErr> { + 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 { + // 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")) + } +} diff --git a/rust-runtime/aws-smithy-interceptor/src/context.rs b/rust-runtime/aws-smithy-interceptor/src/context.rs index d99b50195bb..c484f4c5e03 100644 --- a/rust-runtime/aws-smithy-interceptor/src/context.rs +++ b/rust-runtime/aws-smithy-interceptor/src/context.rs @@ -76,32 +76,18 @@ impl InterceptorContext Result<&TxRes, InterceptorError> { - match ( - &self.tx_response, - self.inner_context - .as_ref() - .and_then(|inner| inner.tx_response.as_ref()), - ) { - (Some(req), _) => Ok(req), - (None, Some(req)) => Ok(req), - (None, _) => Err(InterceptorError::InvalidTxResponseAccess), - } + self.tx_response + .as_ref() + .ok_or(InterceptorError::InvalidTxResponseAccess) } /// 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> { - match ( - &mut self.tx_response, - self.inner_context - .as_mut() - .and_then(|inner| inner.tx_response.as_mut()), - ) { - (Some(res), _) => Ok(res), - (None, Some(res)) => Ok(res), - (None, _) => Err(InterceptorError::InvalidTxResponseAccess), - } + self.tx_response + .as_mut() + .ok_or(InterceptorError::InvalidTxResponseAccess) } /// Retrieve the response to the customer. This will only be available diff --git a/rust-runtime/aws-smithy-orchestrator/src/lib.rs b/rust-runtime/aws-smithy-orchestrator/src/lib.rs index fe51039ecc7..5e0faa795b9 100644 --- a/rust-runtime/aws-smithy-orchestrator/src/lib.rs +++ b/rust-runtime/aws-smithy-orchestrator/src/lib.rs @@ -8,79 +8,39 @@ pub mod config_bag; mod metadata; use crate::auth::AuthSchemeOptions; -use crate::config_bag::ConfigBag; +pub use crate::config_bag::ConfigBag; use aws_smithy_http::body::SdkBody; use aws_smithy_interceptor::{InterceptorContext, Interceptors}; pub use metadata::Metadata; use std::future::Future; use std::pin::Pin; -type BoxErr = Box; -type BoxFallibleFut = Pin>>>; +pub type BoxErr = Box; +pub type BoxFallibleFut = Pin>>>; -trait TraceProbe: Send + Sync { +pub trait TraceProbe: Send + Sync { fn dispatch_events(&self, cfg: &ConfigBag) -> BoxFallibleFut<()>; } -trait RequestSerializer: Send + Sync { - fn serialize_request(&self, req: &In, cfg: &ConfigBag) -> BoxFallibleFut; +pub trait RequestSerializer: Send + Sync { + fn serialize_request(&self, req: &mut In, cfg: &ConfigBag) -> Result; } -trait Signer: Send + Sync { - fn sign(&self, req: &mut TxReq, cfg: &ConfigBag) -> BoxFallibleFut<()>; +pub trait ResponseDeserializer: Send + Sync { + fn deserialize_response(&self, res: &mut TxRes, cfg: &ConfigBag) -> Result; } -trait ResponseDeserializer: Send + Sync { - fn deserialize_response(&self, res: &TxRes, cfg: &ConfigBag) -> BoxFallibleFut; -} - -trait Connection: Send + Sync { +pub trait Connection: Send + Sync { fn call(&self, req: &mut TxReq, cfg: &ConfigBag) -> BoxFallibleFut; } -trait RetryStrategy: Send + Sync { +pub trait RetryStrategy: Send + Sync { fn should_retry(&self, res: &Out, cfg: &ConfigBag) -> Result; } -// fn _resolve_auth_schemes( -// endpoint_resolver: &dyn ResolveEndpoint, -// params: &Ep, -// ) -> Result, SdkError> { -// 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::>(); -// Ok(auth_schemes) -// } +pub trait AuthOrchestrator: Send + Sync { + fn auth_request(&self, req: &mut Req, cfg: &ConfigBag) -> Result<(), BoxErr>; +} /// `In`: The input message e.g. `ListObjectsRequest` /// `Req`: The transport request message e.g. `http::Request` @@ -110,18 +70,17 @@ where interceptors.modify_before_serialization(&mut ctx)?; interceptors.read_before_serialization(&ctx)?; - let mod_req = ctx.modeled_request().clone(); let request_serializer = cfg .get::>>() .ok_or("missing serializer")?; - let req = request_serializer.serialize_request(&mod_req, cfg).await?; + let req = request_serializer.serialize_request(ctx.modeled_request_mut(), cfg)?; ctx.set_tx_request(req); interceptors.read_after_serialization(&ctx)?; interceptors.modify_before_retry_loop(&mut ctx)?; loop { - make_an_attempt(cfg, &mut ctx, interceptors).await?; + make_an_attempt(&mut ctx, cfg, interceptors).await?; interceptors.read_after_attempt(&ctx)?; interceptors.modify_before_attempt_completion(&mut ctx)?; @@ -179,16 +138,12 @@ pub fn try_clone_http_response(res: &http::Response) -> Option( - cfg: &mut ConfigBag, ctx: &mut InterceptorContext>, + cfg: &mut ConfigBag, interceptors: &mut Interceptors>, ) -> Result<(), BoxErr> where @@ -198,18 +153,18 @@ where T: 'static, { interceptors.read_before_attempt(ctx)?; - // // TODO make all this work with the ConfigBag - // // let auth_schemes = resolve_auth_schemes(ctx, cfg)?; - // // let signer = get_signer_for_first_supported_auth_scheme(&auth_schemes)?; - // // let identity = auth_scheme.resolve_identity(cfg)?; - // // resolve_and_apply_endpoint(ctx, cfg)?; - // + + resolve_and_apply_endpoint(ctx, cfg)?; + interceptors.modify_before_signing(ctx)?; interceptors.read_before_signing(ctx)?; - // // { - // // let (tx_req_mut, props) = ctx.tx_request_mut().expect("tx_request has been set"); - // // signer(tx_req_mut, &props)?; - // // } + + let tx_req_mut = ctx.tx_request_mut().expect("tx_request has been set"); + let auth_orchestrator = cfg + .get::>>() + .ok_or("missing auth orchestrator")?; + auth_orchestrator.auth_request(tx_req_mut, cfg)?; + interceptors.read_after_signing(ctx)?; interceptors.modify_before_transmit(ctx)?; interceptors.read_before_transmit(ctx)?; @@ -229,13 +184,11 @@ where interceptors.read_after_transmit(ctx)?; interceptors.modify_before_deserialization(ctx)?; interceptors.read_before_deserialization(ctx)?; - let tx_res = ctx.tx_response().expect("tx_response has been set"); + let tx_res = ctx.tx_response_mut().expect("tx_response has been set"); let response_deserializer = cfg .get::>>>() .ok_or("missing response deserializer")?; - let res = response_deserializer - .deserialize_response(tx_res, cfg) - .await?; + let res = response_deserializer.deserialize_response(tx_res, cfg)?; ctx.set_modeled_response(res); interceptors.read_after_deserialization(ctx)?; @@ -243,43 +196,83 @@ where Ok(()) } -// fn resolve_and_apply_endpoint( -// ctx: &mut OperationV2InterceptorCtx, -// endpoint_resolver: &dyn ResolveEndpoint, -// endpoint_parameters: &Ep, -// ) -> Result<(), ResolveEndpointError> -// { -// 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::()).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())) -// })?, -// ); -// } -// } -// -// Ok(()) -// } +fn resolve_and_apply_endpoint( + _ctx: &mut InterceptorContext>, + _cfg: &mut ConfigBag, +) -> Result<(), BoxErr> { + // 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::()).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())) + // })?, + // ); + // } + // } + + Ok(()) +} + +fn _resolve_auth_schemes( + _ctx: &mut InterceptorContext>, + _cfg: &mut ConfigBag, +) -> Result, BoxErr> { + 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::>(); + // Ok(auth_schemes) +}