AWS SDK

AWS SDK

rev. 3c756f73b1f83a0eed4275d9d1e22df0b10b66fb (ignoring whitespace)

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-legacy-http/src/event_stream/receiver.rs

@@ -1,1 +39,39 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
    6      6   
use aws_smithy_eventstream::frame::{
    7      7   
    DecodedFrame, MessageFrameDecoder, UnmarshallMessage, UnmarshalledMessage,
    8      8   
};
    9         -
use aws_smithy_runtime_api::client::result::{ConnectorError, SdkError};
           9  +
use aws_smithy_runtime_api::client::result::{ConnectorError, ResponseError, SdkError};
   10     10   
use aws_smithy_types::body::SdkBody;
   11     11   
use aws_smithy_types::event_stream::{Message, RawMessage};
   12     12   
use bytes::Buf;
   13     13   
use bytes::Bytes;
   14     14   
use bytes_utils::SegmentedBuf;
   15     15   
use std::error::Error as StdError;
   16     16   
use std::fmt;
   17     17   
use std::marker::PhantomData;
   18     18   
use std::mem;
   19     19   
use tracing::trace;
@@ -202,202 +277,300 @@
  222    222   
        Ok(None)
  223    223   
    }
  224    224   
  225    225   
    /// Tries to receive the initial response message that has `:event-type` of a given `message_type`.
  226    226   
    /// If a different event type is received, then it is buffered and `Ok(None)` is returned.
  227    227   
    #[doc(hidden)]
  228    228   
    pub async fn try_recv_initial(
  229    229   
        &mut self,
  230    230   
        message_type: InitialMessageType,
  231    231   
    ) -> Result<Option<Message>, SdkError<E, RawMessage>> {
         232  +
        self.try_recv_initial_with_preprocessor(message_type, |msg| Ok((msg, ())))
         233  +
            .await
         234  +
            .map(|opt| opt.map(|(msg, _)| msg))
         235  +
    }
         236  +
         237  +
    /// Tries to receive the initial response message with preprocessing support.
         238  +
    ///
         239  +
    /// The preprocessor function can transform the raw message (e.g., unwrap envelopes)
         240  +
    /// and return metadata alongside the transformed message. If the transformed message
         241  +
    /// matches the expected `message_type`, both the message and metadata are returned.
         242  +
    /// Otherwise, the transformed message is buffered and `Ok(None)` is returned.
         243  +
    #[doc(hidden)]
         244  +
    pub async fn try_recv_initial_with_preprocessor<F, M>(
         245  +
        &mut self,
         246  +
        message_type: InitialMessageType,
         247  +
        preprocessor: F,
         248  +
    ) -> Result<Option<(Message, M)>, SdkError<E, RawMessage>>
         249  +
    where
         250  +
        F: FnOnce(Message) -> Result<(Message, M), ResponseError<RawMessage>>,
         251  +
    {
  232    252   
        if let Some(message) = self.next_message().await? {
  233         -
            if let Some(event_type) = message
         253  +
            let (processed_message, metadata) =
         254  +
                preprocessor(message.clone()).map_err(|err| SdkError::ResponseError(err))?;
         255  +
         256  +
            if let Some(event_type) = processed_message
  234    257   
                .headers()
  235    258   
                .iter()
  236    259   
                .find(|h| h.name().as_str() == ":event-type")
  237    260   
            {
  238    261   
                if event_type
  239    262   
                    .value()
  240    263   
                    .as_string()
  241    264   
                    .map(|s| s.as_str() == message_type.as_str())
  242    265   
                    .unwrap_or(false)
  243    266   
                {
  244         -
                    return Ok(Some(message));
         267  +
                    return Ok(Some((processed_message, metadata)));
  245    268   
                }
  246    269   
            }
  247         -
            // Buffer the message so that it can be returned by the next call to `recv()`
         270  +
            // Buffer the processed message so that it can be returned by the next call to `recv()`
  248    271   
            self.buffered_message = Some(message);
  249    272   
        }
  250    273   
        Ok(None)
  251    274   
    }
  252    275   
  253    276   
    /// Asynchronously tries to receive a message from the stream. If the stream has ended,
  254    277   
    /// it returns an `Ok(None)`. If there is a transport layer error, it will return
  255    278   
    /// `Err(SdkError::DispatchFailure)`. Service-modeled errors will be a part of the returned
  256    279   
    /// messages.
  257    280   
    pub async fn recv(&mut self) -> Result<Option<T>, SdkError<E, RawMessage>> {
@@ -429,452 +489,512 @@
  449    472   
                encode_message("eight"),
  450    473   
            ].concat());
  451    474   
  452    475   
            let midpoint = combined.len() / 2;
  453    476   
            let (start, boundary1, boundary2, end) = (
  454    477   
                0,
  455    478   
                b1 % midpoint,
  456    479   
                midpoint + b2 % midpoint,
  457    480   
                combined.len()
  458    481   
            );
  459         -
            println!("[{}, {}], [{}, {}], [{}, {}]", start, boundary1, boundary1, boundary2, boundary2, end);
         482  +
            println!("[{start}, {boundary1}], [{boundary1}, {boundary2}], [{boundary2}, {end}]");
  460    483   
  461    484   
            let rt = tokio::runtime::Runtime::new().unwrap();
  462    485   
            rt.block_on(async move {
  463    486   
                let chunks: Vec<Result<_, IOError>> = vec![
  464    487   
                    Ok(Bytes::copy_from_slice(&combined[start..boundary1])),
  465    488   
                    Ok(Bytes::copy_from_slice(&combined[boundary1..boundary2])),
  466    489   
                    Ok(Bytes::copy_from_slice(&combined[boundary2..end])),
  467    490   
                ];
  468    491   
  469    492   
                let chunk_stream = futures_util::stream::iter(chunks);

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-mocks/Cargo.toml

@@ -1,1 +0,45 @@
    1      1   
# Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
    2      2   
[package]
    3      3   
name = "aws-smithy-mocks"
    4         -
version = "0.2.3"
           4  +
version = "0.2.4"
    5      5   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
    6      6   
description = "Testing utilities for smithy-rs generated clients"
    7      7   
edition = "2021"
    8      8   
license = "Apache-2.0"
    9      9   
repository = "https://github.com/smithy-lang/smithy-rs"
   10     10   
rust-version = "1.88"
   11     11   
[package.metadata.docs.rs]
   12     12   
all-features = true
   13     13   
targets = ["x86_64-unknown-linux-gnu"]
   14     14   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
   15     15   
rustdoc-args = ["--cfg", "docsrs"]
   16     16   
   17     17   
[dependencies]
   18         -
http = "1"
          18  +
http = "1.3.1"
   19     19   
   20     20   
[dependencies.aws-smithy-types]
   21     21   
path = "../aws-smithy-types"
   22         -
version = "1.3.6"
          22  +
version = "1.4.0"
   23     23   
   24     24   
[dependencies.aws-smithy-runtime-api]
   25     25   
path = "../aws-smithy-runtime-api"
   26     26   
features = ["client", "http-1x", "test-util"]
   27         -
version = "1.10.0"
          27  +
version = "1.11.0"
   28     28   
   29     29   
[dependencies.aws-smithy-http-client]
   30     30   
path = "../aws-smithy-http-client"
   31     31   
features = ["test-util"]
   32     32   
version = "1.1.6"
   33     33   
[dev-dependencies.tokio]
   34     34   
version = "1"
   35     35   
features = ["full"]
   36     36   
   37     37   
[dev-dependencies.aws-smithy-async]
   38     38   
path = "../aws-smithy-async"
   39     39   
features = ["rt-tokio"]
   40         -
version = "1.2.7"
          40  +
version = "1.2.8"
   41     41   
   42     42   
[dev-dependencies.aws-smithy-runtime]
   43     43   
path = "../aws-smithy-runtime"
   44     44   
features = ["client"]
   45         -
version = "1.9.8"
          45  +
version = "1.10.0"

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-observability-otel/Cargo.toml

@@ -1,1 +49,49 @@
    2      2   
[[bench]]
    3      3   
name = "sync_instruments"
    4      4   
harness = false
    5      5   
    6      6   
[[bench]]
    7      7   
name = "async_instruments"
    8      8   
harness = false
    9      9   
   10     10   
[package]
   11     11   
name = "aws-smithy-observability-otel"
   12         -
version = "0.1.4"
          12  +
version = "0.1.5"
   13     13   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
   14     14   
description = "Smithy OpenTelemetry observability implementation."
   15     15   
edition = "2021"
   16     16   
license = "Apache-2.0"
   17     17   
repository = "https://github.com/awslabs/smithy-rs"
   18     18   
rust-version = "1.88"
   19     19   
[package.metadata.docs.rs]
   20     20   
all-features = true
   21     21   
targets = ["x86_64-unknown-linux-gnu"]
   22     22   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
   23     23   
rustdoc-args = ["--cfg", "docsrs"]
   24     24   
   25     25   
[dependencies]
   26     26   
value-bag = "1.10.0"
   27     27   
async-global-executor = "2.4.1"
   28     28   
async-task = "=4.7.1"
   29     29   
   30     30   
[dependencies.aws-smithy-observability]
   31     31   
path = "../aws-smithy-observability"
   32         -
version = "0.2.0"
          32  +
version = "0.2.1"
   33     33   
   34     34   
[dependencies.opentelemetry]
   35     35   
version = "0.26.0"
   36     36   
features = ["metrics"]
   37     37   
[target."cfg(not(target_arch = \"powerpc\"))".dependencies.opentelemetry_sdk]
   38     38   
version = "0.26.0"
   39     39   
features = ["metrics", "testing"]
   40     40   
   41     41   
[dev-dependencies]
   42     42   
stats_alloc = "0.1.10"

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-observability/Cargo.toml

@@ -1,1 +21,21 @@
    1      1   
# Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
    2      2   
[package]
    3      3   
name = "aws-smithy-observability"
    4         -
version = "0.2.0"
           4  +
version = "0.2.1"
    5      5   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
    6      6   
description = "Smithy observability implementation."
    7      7   
edition = "2021"
    8      8   
license = "Apache-2.0"
    9      9   
repository = "https://github.com/awslabs/smithy-rs"
   10     10   
rust-version = "1.88"
   11     11   
[package.metadata.docs.rs]
   12     12   
all-features = true
   13     13   
targets = ["x86_64-unknown-linux-gnu"]
   14     14   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
   15     15   
rustdoc-args = ["--cfg", "docsrs"]
   16     16   
[dependencies.aws-smithy-runtime-api]
   17     17   
path = "../aws-smithy-runtime-api"
   18         -
version = "1.10.0"
          18  +
version = "1.11.0"
   19     19   
   20     20   
[dev-dependencies]
   21     21   
serial_test = "3.1.1"

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-protocol-test/Cargo.toml

@@ -1,1 +0,46 @@
    1      1   
# Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
    2      2   
[package]
    3      3   
name = "aws-smithy-protocol-test"
    4         -
version = "0.63.7"
           4  +
version = "0.63.8"
    5      5   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Russell Cohen <rcoh@amazon.com>"]
    6      6   
description = "A collection of library functions to validate HTTP requests against Smithy protocol tests."
    7      7   
edition = "2021"
    8      8   
license = "Apache-2.0"
    9      9   
repository = "https://github.com/smithy-lang/smithy-rs"
   10     10   
rust-version = "1.88"
   11     11   
[package.metadata.docs.rs]
   12     12   
all-features = true
   13     13   
targets = ["x86_64-unknown-linux-gnu"]
   14     14   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
   15     15   
rustdoc-args = ["--cfg", "docsrs"]
   16     16   
          17  +
[features]
          18  +
default = ["http-02x"]
          19  +
http-02x = ["dep:http-0x"]
          20  +
http-1x = ["dep:http-1x"]
          21  +
   17     22   
[dependencies]
   18     23   
assert-json-diff = "2"
   19     24   
base64-simd = "0.8"
   20     25   
cbor-diag = "0.1.12"
   21     26   
ciborium = "0.2"
   22         -
http = "0.2.9"
   23     27   
pretty_assertions = "1.3"
   24     28   
regex-lite = "0.1.5"
   25     29   
roxmltree = "0.14.1"
   26     30   
serde_json = "1.0.128"
   27     31   
thiserror = "2"
   28     32   
   29     33   
[dependencies.aws-smithy-runtime-api]
   30     34   
path = "../aws-smithy-runtime-api"
   31     35   
features = ["client"]
   32         -
version = "1.10.0"
          36  +
version = "1.11.0"
          37  +
          38  +
[dependencies.http-0x]
          39  +
package = "http"
          40  +
version = "0.2.12"
          41  +
optional = true
          42  +
          43  +
[dependencies.http-1x]
          44  +
package = "http"
          45  +
version = "1.3.1"
          46  +
optional = true

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-protocol-test/src/lib.rs

@@ -1,1 +54,53 @@
   14     14   
)]
   15     15   
   16     16   
mod urlencoded;
   17     17   
mod xml;
   18     18   
   19     19   
use crate::sealed::GetNormalizedHeader;
   20     20   
use crate::xml::try_xml_equivalent;
   21     21   
use assert_json_diff::assert_json_matches_no_panic;
   22     22   
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
   23     23   
use aws_smithy_runtime_api::http::Headers;
   24         -
use http::{HeaderMap, Uri};
   25     24   
use pretty_assertions::Comparison;
   26     25   
use std::borrow::Cow;
   27     26   
use std::collections::HashSet;
   28     27   
use std::fmt::{self, Debug};
   29     28   
use thiserror::Error;
   30     29   
use urlencoded::try_url_encoded_form_equivalent;
   31     30   
   32     31   
/// Helper trait for tests for float comparisons
   33     32   
///
   34     33   
/// This trait differs in float's default `PartialEq` implementation by considering all `NaN` values to
@@ -119,118 +183,195 @@
  139    138   
    let left = left.as_ref();
  140    139   
    let right = right.as_ref();
  141    140   
    if left == right {
  142    141   
        return;
  143    142   
    }
  144    143   
    assert_eq!(
  145    144   
        extract_params(left),
  146    145   
        extract_params(right),
  147    146   
        "Query parameters did not match. left: {left}, right: {right}"
  148    147   
    );
  149         -
    let left: Uri = left.parse().expect("left is not a valid URI");
  150         -
    let right: Uri = right.parse().expect("left is not a valid URI");
         148  +
         149  +
    // When both features are enabled, prefer http-1x version
         150  +
    #[cfg(feature = "http-1x")]
         151  +
    {
         152  +
        let left: http_1x::Uri = left.parse().expect("left is not a valid URI");
         153  +
        let right: http_1x::Uri = right.parse().expect("right is not a valid URI");
         154  +
        assert_eq!(left.authority(), right.authority());
         155  +
        assert_eq!(left.scheme(), right.scheme());
         156  +
        assert_eq!(left.path(), right.path());
         157  +
    }
         158  +
    #[cfg(all(feature = "http-02x", not(feature = "http-1x")))]
         159  +
    {
         160  +
        let left: http_0x::Uri = left.parse().expect("left is not a valid URI");
         161  +
        let right: http_0x::Uri = right.parse().expect("right is not a valid URI");
  151    162   
        assert_eq!(left.authority(), right.authority());
  152    163   
        assert_eq!(left.scheme(), right.scheme());
  153    164   
        assert_eq!(left.path(), right.path());
         165  +
    }
  154    166   
}
  155    167   
  156    168   
pub fn validate_query_string(
  157    169   
    request: &HttpRequest,
  158    170   
    expected_params: &[&str],
  159    171   
) -> Result<(), ProtocolTestFailure> {
  160    172   
    let actual_params = extract_params(request.uri());
  161    173   
    for param in expected_params {
  162    174   
        if !actual_params.contains(param) {
  163    175   
            return Err(ProtocolTestFailure::MissingQueryParam {
@@ -203,215 +263,295 @@
  223    235   
impl GetNormalizedHeader for &Headers {
  224    236   
    fn get_header(&self, key: &str) -> Option<String> {
  225    237   
        if !self.contains_key(key) {
  226    238   
            None
  227    239   
        } else {
  228    240   
            Some(self.get_all(key).collect::<Vec<_>>().join(", "))
  229    241   
        }
  230    242   
    }
  231    243   
}
  232    244   
  233         -
impl GetNormalizedHeader for &HeaderMap {
         245  +
// HTTP 0.2.x HeaderMap implementation
         246  +
#[cfg(feature = "http-02x")]
         247  +
impl GetNormalizedHeader for &http_0x::HeaderMap {
         248  +
    fn get_header(&self, key: &str) -> Option<String> {
         249  +
        if !self.contains_key(key) {
         250  +
            None
         251  +
        } else {
         252  +
            Some(
         253  +
                self.get_all(key)
         254  +
                    .iter()
         255  +
                    .map(|value| std::str::from_utf8(value.as_bytes()).expect("invalid utf-8"))
         256  +
                    .collect::<Vec<_>>()
         257  +
                    .join(", "),
         258  +
            )
         259  +
        }
         260  +
    }
         261  +
}
         262  +
         263  +
// HTTP 1.x HeaderMap implementation
         264  +
#[cfg(feature = "http-1x")]
         265  +
impl GetNormalizedHeader for &http_1x::HeaderMap {
  234    266   
    fn get_header(&self, key: &str) -> Option<String> {
  235    267   
        if !self.contains_key(key) {
  236    268   
            None
  237    269   
        } else {
  238    270   
            Some(
  239    271   
                self.get_all(key)
  240    272   
                    .iter()
  241    273   
                    .map(|value| std::str::from_utf8(value.as_bytes()).expect("invalid utf-8"))
  242    274   
                    .collect::<Vec<_>>()
  243    275   
                    .join(", "),
@@ -714,746 +764,800 @@
  734    766   
        let expected = r#"asdf"#;
  735    767   
        let actual = r#"asdf "#;
  736    768   
        validate_body(actual, expected, MediaType::from("something/else"))
  737    769   
            .expect_err("bodies do not match");
  738    770   
  739    771   
        validate_body(expected, expected, MediaType::from("something/else"))
  740    772   
            .expect("inputs matched exactly")
  741    773   
    }
  742    774   
  743    775   
    #[test]
         776  +
    #[cfg(feature = "http-02x")]
  744    777   
    fn test_validate_headers_http0x() {
  745         -
        let request = http::Request::builder().header("a", "b").body(()).unwrap();
         778  +
        let request = http_0x::Request::builder()
         779  +
            .header("a", "b")
         780  +
            .body(())
         781  +
            .unwrap();
  746    782   
        validate_headers(request.headers(), [("a", "b")]).unwrap()
  747    783   
    }
  748    784   
  749    785   
    #[test]
  750    786   
    fn test_float_equals() {
  751    787   
        let a = f64::NAN;
  752    788   
        let b = f64::NAN;
  753    789   
        assert_ne!(a, b);
  754    790   
        assert!(a.float_equals(&b));
  755    791   
        assert!(!a.float_equals(&5_f64));

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-query/Cargo.toml

@@ -1,1 +0,23 @@
    1      1   
# Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
    2      2   
[package]
    3      3   
name = "aws-smithy-query"
    4         -
version = "0.60.9"
           4  +
version = "0.60.10"
    5      5   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "John DiSanti <jdisanti@amazon.com>"]
    6      6   
description = "AWSQuery and EC2Query Smithy protocol logic for smithy-rs."
    7      7   
edition = "2021"
    8      8   
license = "Apache-2.0"
    9      9   
repository = "https://github.com/smithy-lang/smithy-rs"
   10     10   
rust-version = "1.88"
   11     11   
[package.metadata.docs.rs]
   12     12   
all-features = true
   13     13   
targets = ["x86_64-unknown-linux-gnu"]
   14     14   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
   15     15   
rustdoc-args = ["--cfg", "docsrs"]
   16     16   
   17     17   
[dependencies]
   18     18   
urlencoding = "2.1"
   19     19   
   20     20   
[dependencies.aws-smithy-types]
   21     21   
path = "../aws-smithy-types"
   22         -
version = "1.3.6"
          22  +
features = ["http-body-1-x"]
          23  +
version = "1.4.0"

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime-api/Cargo.toml

@@ -1,1 +62,63 @@
    1      1   
# Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
    2      2   
[package]
    3      3   
name = "aws-smithy-runtime-api"
    4         -
version = "1.10.0"
           4  +
version = "1.11.0"
    5      5   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Zelda Hessler <zhessler@amazon.com>"]
    6      6   
description = "Smithy runtime types."
    7      7   
edition = "2021"
    8      8   
license = "Apache-2.0"
    9      9   
repository = "https://github.com/smithy-lang/smithy-rs"
   10     10   
rust-version = "1.88"
   11     11   
[package.metadata.docs.rs]
   12     12   
all-features = true
   13     13   
targets = ["x86_64-unknown-linux-gnu"]
   14     14   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
   15     15   
rustdoc-args = ["--cfg", "docsrs"]
   16     16   
   17     17   
[package.metadata.smithy-rs-release-tooling]
   18     18   
stable = true
   19     19   
   20     20   
[features]
   21     21   
default = []
   22     22   
client = []
   23     23   
http-auth = ["dep:zeroize"]
   24     24   
test-util = ["aws-smithy-types/test-util", "http-1x"]
   25     25   
http-02x = []
   26     26   
http-1x = []
   27     27   
   28     28   
[dependencies]
   29     29   
bytes = "1.10.0"
   30     30   
pin-project-lite = "0.2.14"
   31     31   
tracing = "0.1.40"
   32     32   
   33     33   
[dependencies.aws-smithy-async]
   34     34   
path = "../aws-smithy-async"
   35         -
version = "1.2.7"
          35  +
version = "1.2.8"
   36     36   
   37     37   
[dependencies.aws-smithy-types]
   38     38   
path = "../aws-smithy-types"
   39         -
version = "1.3.6"
          39  +
features = ["http-body-1-x"]
          40  +
version = "1.4.0"
   40     41   
   41     42   
[dependencies.http-02x]
   42     43   
package = "http"
   43         -
version = "0.2.9"
          44  +
version = "0.2.12"
   44     45   
   45     46   
[dependencies.http-1x]
   46     47   
package = "http"
   47         -
version = "1"
          48  +
version = "1.3.1"
   48     49   
   49     50   
[dependencies.tokio]
   50         -
version = "1.40.0"
          51  +
version = "1.46.0"
   51     52   
features = ["sync"]
   52     53   
   53     54   
[dependencies.zeroize]
   54     55   
version = "1.7.0"
   55     56   
optional = true
   56     57   
   57     58   
[dev-dependencies]
   58     59   
proptest = "1"
   59     60   
   60     61   
[dev-dependencies.tokio]

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime-api/src/client/waiters.rs

@@ -1,1 +54,55 @@
   15     15   
        ErrorMetadata,
   16     16   
    };
   17     17   
    use std::{fmt, time::Duration};
   18     18   
   19     19   
    /// An error occurred while waiting.
   20     20   
    ///
   21     21   
    /// This error type is useful for distinguishing between the max wait
   22     22   
    /// time being exceeded, or some other failure occurring.
   23     23   
    #[derive(Debug)]
   24     24   
    #[non_exhaustive]
          25  +
    #[allow(clippy::large_enum_variant)] // For `OperationFailed` variant
   25     26   
    pub enum WaiterError<O, E> {
   26     27   
        /// An error occurred during waiter initialization.
   27     28   
        ///
   28     29   
        /// This can happen if the input/config is invalid.
   29     30   
        ConstructionFailure(ConstructionFailure),
   30     31   
   31     32   
        /// The maximum wait time was exceeded without completion.
   32     33   
        ExceededMaxWait(ExceededMaxWait),
   33     34   
   34     35   
        /// Waiting ended in a failure state.

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime-api/src/http/request.rs

@@ -159,159 +218,225 @@
  179    179   
  180    180   
#[cfg(feature = "http-02x")]
  181    181   
impl<B> TryInto<http_02x::Request<B>> for Request<B> {
  182    182   
    type Error = HttpError;
  183    183   
  184    184   
    fn try_into(self) -> Result<http_02x::Request<B>, Self::Error> {
  185    185   
        self.try_into_http02x()
  186    186   
    }
  187    187   
}
  188    188   
         189  +
#[cfg(feature = "http-1x")]
         190  +
impl From<http_1x::Uri> for Uri {
         191  +
    fn from(value: http_1x::Uri) -> Self {
         192  +
        Uri::from_http1x_uri(value)
         193  +
    }
         194  +
}
         195  +
  189    196   
#[cfg(feature = "http-1x")]
  190    197   
impl<B> TryInto<http_1x::Request<B>> for Request<B> {
  191    198   
    type Error = HttpError;
  192    199   
  193    200   
    fn try_into(self) -> Result<http_1x::Request<B>, Self::Error> {
  194    201   
        self.try_into_http1x()
  195    202   
    }
  196    203   
}
  197    204   
  198    205   
impl<B> Request<B> {

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime/Cargo.toml

@@ -1,1 +124,125 @@
    1      1   
# Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
    2      2   
[package]
    3      3   
name = "aws-smithy-runtime"
    4         -
version = "1.9.8"
           4  +
version = "1.10.0"
    5      5   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Zelda Hessler <zhessler@amazon.com>"]
    6      6   
description = "The new smithy runtime crate"
    7      7   
edition = "2021"
    8      8   
license = "Apache-2.0"
    9      9   
repository = "https://github.com/smithy-lang/smithy-rs"
   10     10   
rust-version = "1.88"
   11     11   
[package.metadata.docs.rs]
   12     12   
all-features = true
   13     13   
targets = ["x86_64-unknown-linux-gnu"]
   14     14   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
   15     15   
rustdoc-args = ["--cfg", "docsrs"]
   16     16   
   17     17   
[package.metadata.smithy-rs-release-tooling]
   18     18   
stable = true
   19     19   
[package.metadata.cargo-udeps.ignore]
   20     20   
normal = ["aws-smithy-http"]
   21     21   
   22     22   
[features]
   23     23   
client = ["aws-smithy-runtime-api/client", "aws-smithy-types/http-body-1-x"]
   24     24   
http-auth = ["aws-smithy-runtime-api/http-auth"]
   25     25   
connector-hyper-0-14-x = ["dep:aws-smithy-http-client", "aws-smithy-http-client?/hyper-014"]
   26     26   
tls-rustls = ["dep:aws-smithy-http-client", "aws-smithy-http-client?/legacy-rustls-ring", "connector-hyper-0-14-x"]
   27     27   
default-https-client = ["dep:aws-smithy-http-client", "aws-smithy-http-client?/rustls-aws-lc"]
   28     28   
rt-tokio = ["tokio/rt"]
   29     29   
test-util = ["aws-smithy-runtime-api/test-util", "dep:tracing-subscriber", "aws-smithy-http-client/test-util", "legacy-test-util"]
   30     30   
legacy-test-util = ["aws-smithy-runtime-api/test-util", "dep:tracing-subscriber", "aws-smithy-http-client/test-util", "connector-hyper-0-14-x", "aws-smithy-http-client/legacy-test-util"]
   31     31   
wire-mock = ["legacy-test-util", "aws-smithy-http-client/wire-mock"]
   32     32   
   33     33   
[dependencies]
   34     34   
bytes = "1.10.0"
   35     35   
fastrand = "2.3.0"
          36  +
http-body-util = "0.1.3"
   36     37   
pin-project-lite = "0.2.14"
   37     38   
pin-utils = "0.1.0"
   38     39   
tracing = "0.1.40"
   39     40   
   40     41   
[dependencies.aws-smithy-async]
   41     42   
path = "../aws-smithy-async"
   42         -
version = "1.2.7"
          43  +
version = "1.2.8"
   43     44   
   44     45   
[dependencies.aws-smithy-http]
   45     46   
path = "../aws-smithy-http"
   46         -
version = "0.62.6"
          47  +
version = "0.63.0"
   47     48   
   48     49   
[dependencies.aws-smithy-observability]
   49     50   
path = "../aws-smithy-observability"
   50         -
version = "0.2.0"
          51  +
version = "0.2.1"
   51     52   
   52     53   
[dependencies.aws-smithy-runtime-api]
   53     54   
path = "../aws-smithy-runtime-api"
   54         -
version = "1.10.0"
          55  +
version = "1.11.0"
   55     56   
   56     57   
[dependencies.aws-smithy-types]
   57     58   
path = "../aws-smithy-types"
   58     59   
features = ["http-body-0-4-x"]
   59         -
version = "1.3.6"
          60  +
version = "1.4.0"
   60     61   
   61     62   
[dependencies.aws-smithy-http-client]
   62     63   
path = "../aws-smithy-http-client"
   63     64   
optional = true
   64     65   
version = "1.1.6"
   65     66   
   66     67   
[dependencies.http-02x]
   67     68   
package = "http"
   68         -
version = "0.2.9"
          69  +
version = "0.2.12"
   69     70   
   70     71   
[dependencies.http-1x]
   71     72   
package = "http"
   72         -
version = "1"
          73  +
version = "1.3.1"
   73     74   
   74     75   
[dependencies.http-body-04x]
   75     76   
package = "http-body"
   76         -
version = "0.4.5"
          77  +
version = "0.4.6"
   77     78   
   78     79   
[dependencies.http-body-1x]
   79     80   
package = "http-body"
   80         -
version = "1"
          81  +
version = "1.0.1"
   81     82   
   82     83   
[dependencies.tokio]
   83         -
version = "1.40.0"
          84  +
version = "1.46.0"
   84     85   
features = []
   85     86   
   86     87   
[dependencies.tracing-subscriber]
   87     88   
version = "0.3.16"
   88     89   
optional = true
   89     90   
features = ["env-filter", "fmt", "json"]
   90     91   
   91     92   
[dev-dependencies]
   92     93   
approx = "0.5.1"
   93     94   
fastrand = "2.3.0"
   94     95   
futures-util = "0.3.29"
   95     96   
pretty_assertions = "1.4.0"
   96     97   
tracing-test = "0.2.1"
   97     98   
   98     99   
[dev-dependencies.aws-smithy-async]
   99    100   
path = "../aws-smithy-async"
  100    101   
features = ["rt-tokio", "test-util"]
  101         -
version = "1.2.7"
         102  +
version = "1.2.8"
  102    103   
  103    104   
[dev-dependencies.aws-smithy-runtime-api]
  104    105   
path = "../aws-smithy-runtime-api"
  105    106   
features = ["test-util"]
  106         -
version = "1.10.0"
         107  +
version = "1.11.0"
  107    108   
  108    109   
[dev-dependencies.aws-smithy-types]
  109    110   
path = "../aws-smithy-types"
  110    111   
features = ["test-util"]
  111         -
version = "1.3.6"
         112  +
version = "1.4.0"
  112    113   
  113    114   
[dev-dependencies.tokio]
  114    115   
version = "1.25"
  115    116   
features = ["macros", "rt", "rt-multi-thread", "test-util", "full"]
  116    117   
  117    118   
[dev-dependencies.tracing-subscriber]
  118    119   
version = "0.3.16"
  119    120   
features = ["env-filter"]
  120    121   
  121    122   
[dev-dependencies.hyper_0_14]

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime/src/client/auth/http.rs

@@ -78,78 +257,257 @@
   98     98   
                        format!("{} {}", self.scheme, api_key.token()),
   99     99   
                    )
  100    100   
                    .map_err(|_| {
  101    101   
                        "API key contains characters that can't be included in a HTTP header"
  102    102   
                    })?;
  103    103   
            }
  104    104   
            ApiKeyLocation::Query => {
  105    105   
                let mut query = QueryWriter::new_from_string(request.uri())?;
  106    106   
                query.insert(&self.name, api_key.token());
  107    107   
                request
  108         -
                    .set_uri(query.build_uri())
         108  +
                    .set_uri(query.build_uri().to_string())
  109    109   
                    .expect("query writer returns a valid URI")
  110    110   
            }
  111    111   
        }
  112    112   
  113    113   
        Ok(())
  114    114   
    }
  115    115   
}
  116    116   
  117    117   
/// Auth implementation for Smithy's `@httpBasicAuth` auth scheme
  118    118   
#[derive(Debug, Default)]
  119    119   
pub struct BasicAuthScheme {
  120    120   
    signer: BasicAuthSigner,
  121    121   
}
  122    122   
  123    123   
impl BasicAuthScheme {
  124    124   
    /// Creates a new `BasicAuthScheme`.
  125    125   
    pub fn new() -> Self {
  126    126   
        Self {
  127    127   
            signer: BasicAuthSigner,
  128    128   
        }
  129    129   
    }
  130    130   
}
  131    131   
  132    132   
impl AuthScheme for BasicAuthScheme {
  133    133   
    fn scheme_id(&self) -> AuthSchemeId {
  134    134   
        HTTP_BASIC_AUTH_SCHEME_ID
  135    135   
    }
  136    136   
  137    137   
    fn identity_resolver(
  138    138   
        &self,
  139    139   
        identity_resolvers: &dyn GetIdentityResolver,
  140    140   
    ) -> Option<SharedIdentityResolver> {
  141    141   
        identity_resolvers.identity_resolver(self.scheme_id())
  142    142   
    }
  143    143   
  144    144   
    fn signer(&self) -> &dyn Sign {
  145    145   
        &self.signer
  146    146   
    }
  147    147   
}
  148    148   
  149    149   
#[derive(Debug, Default)]
  150    150   
struct BasicAuthSigner;
  151    151   
  152    152   
impl Sign for BasicAuthSigner {
  153    153   
    fn sign_http_request(
  154    154   
        &self,
  155    155   
        request: &mut HttpRequest,
  156    156   
        identity: &Identity,
  157    157   
        _auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
  158    158   
        _runtime_components: &RuntimeComponents,
  159    159   
        _config_bag: &ConfigBag,
  160    160   
    ) -> Result<(), BoxError> {
  161    161   
        let login = identity
  162    162   
            .data::<Login>()
  163    163   
            .ok_or("HTTP basic auth requires a `Login` identity")?;
  164    164   
        request.headers_mut().insert(
  165         -
            http_02x::header::AUTHORIZATION,
  166         -
            http_02x::HeaderValue::from_str(&format!(
         165  +
            http_1x::header::AUTHORIZATION,
         166  +
            http_1x::HeaderValue::from_str(&format!(
  167    167   
                "Basic {}",
  168    168   
                encode(format!("{}:{}", login.user(), login.password()))
  169    169   
            ))
  170    170   
            .expect("valid header value"),
  171    171   
        );
  172    172   
        Ok(())
  173    173   
    }
  174    174   
}
  175    175   
  176    176   
/// Auth implementation for Smithy's `@httpBearerAuth` auth scheme
  177    177   
#[derive(Debug, Default)]
  178    178   
pub struct BearerAuthScheme {
  179    179   
    signer: BearerAuthSigner,
  180    180   
}
  181    181   
  182    182   
impl BearerAuthScheme {
  183    183   
    /// Creates a new `BearerAuthScheme`.
  184    184   
    pub fn new() -> Self {
  185    185   
        Self {
  186    186   
            signer: BearerAuthSigner,
  187    187   
        }
  188    188   
    }
  189    189   
}
  190    190   
  191    191   
impl AuthScheme for BearerAuthScheme {
  192    192   
    fn scheme_id(&self) -> AuthSchemeId {
  193    193   
        HTTP_BEARER_AUTH_SCHEME_ID
  194    194   
    }
  195    195   
  196    196   
    fn identity_resolver(
  197    197   
        &self,
  198    198   
        identity_resolvers: &dyn GetIdentityResolver,
  199    199   
    ) -> Option<SharedIdentityResolver> {
  200    200   
        identity_resolvers.identity_resolver(self.scheme_id())
  201    201   
    }
  202    202   
  203    203   
    fn signer(&self) -> &dyn Sign {
  204    204   
        &self.signer
  205    205   
    }
  206    206   
}
  207    207   
  208    208   
#[derive(Debug, Default)]
  209    209   
struct BearerAuthSigner;
  210    210   
  211    211   
impl Sign for BearerAuthSigner {
  212    212   
    fn sign_http_request(
  213    213   
        &self,
  214    214   
        request: &mut HttpRequest,
  215    215   
        identity: &Identity,
  216    216   
        _auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
  217    217   
        _runtime_components: &RuntimeComponents,
  218    218   
        _config_bag: &ConfigBag,
  219    219   
    ) -> Result<(), BoxError> {
  220    220   
        let token = identity
  221    221   
            .data::<Token>()
  222    222   
            .ok_or("HTTP bearer auth requires a `Token` identity")?;
  223    223   
        request.headers_mut().insert(
  224         -
            http_02x::header::AUTHORIZATION,
  225         -
            http_02x::HeaderValue::from_str(&format!("Bearer {}", token.token())).map_err(
  226         -
                |_| "Bearer token contains characters that can't be included in a HTTP header",
  227         -
            )?,
         224  +
            http_1x::header::AUTHORIZATION,
         225  +
            http_1x::HeaderValue::from_str(&format!("Bearer {}", token.token())).map_err(|_| {
         226  +
                "Bearer token contains characters that can't be included in a HTTP header"
         227  +
            })?,
  228    228   
        );
  229    229   
        Ok(())
  230    230   
    }
  231    231   
}
  232    232   
  233    233   
/// Auth implementation for Smithy's `@httpDigestAuth` auth scheme
  234    234   
#[derive(Debug, Default)]
  235    235   
pub struct DigestAuthScheme {
  236    236   
    signer: DigestAuthSigner,
  237    237   
}
@@ -270,270 +435,435 @@
  290    290   
    #[test]
  291    291   
    fn test_api_key_signing_headers() {
  292    292   
        let signer = ApiKeySigner {
  293    293   
            scheme: "SomeSchemeName".into(),
  294    294   
            location: ApiKeyLocation::Header,
  295    295   
            name: "some-header-name".into(),
  296    296   
        };
  297    297   
        let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
  298    298   
        let config_bag = ConfigBag::base();
  299    299   
        let identity = Identity::new(Token::new("some-token", None), None);
  300         -
        let mut request: HttpRequest = http_02x::Request::builder()
         300  +
        let mut request: HttpRequest = http_1x::Request::builder()
  301    301   
            .uri("http://example.com/Foobaz")
  302    302   
            .body(SdkBody::empty())
  303    303   
            .unwrap()
  304    304   
            .try_into()
  305    305   
            .unwrap();
  306    306   
        signer
  307    307   
            .sign_http_request(
  308    308   
                &mut request,
  309    309   
                &identity,
  310    310   
                AuthSchemeEndpointConfig::empty(),
  311    311   
                &runtime_components,
  312    312   
                &config_bag,
  313    313   
            )
  314    314   
            .expect("success");
  315    315   
        assert_eq!(
  316    316   
            "SomeSchemeName some-token",
  317    317   
            request.headers().get("some-header-name").unwrap()
  318    318   
        );
  319    319   
        assert_eq!("http://example.com/Foobaz", request.uri().to_string());
  320    320   
    }
  321    321   
  322    322   
    #[test]
  323    323   
    fn test_api_key_signing_query() {
  324    324   
        let signer = ApiKeySigner {
  325    325   
            scheme: "".into(),
  326    326   
            location: ApiKeyLocation::Query,
  327    327   
            name: "some-query-name".into(),
  328    328   
        };
  329    329   
        let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
  330    330   
        let config_bag = ConfigBag::base();
  331    331   
        let identity = Identity::new(Token::new("some-token", None), None);
  332         -
        let mut request: HttpRequest = http_02x::Request::builder()
         332  +
        let mut request: HttpRequest = http_1x::Request::builder()
  333    333   
            .uri("http://example.com/Foobaz")
  334    334   
            .body(SdkBody::empty())
  335    335   
            .unwrap()
  336    336   
            .try_into()
  337    337   
            .unwrap();
  338    338   
        signer
  339    339   
            .sign_http_request(
  340    340   
                &mut request,
  341    341   
                &identity,
  342    342   
                AuthSchemeEndpointConfig::empty(),
  343    343   
                &runtime_components,
  344    344   
                &config_bag,
  345    345   
            )
  346    346   
            .expect("success");
  347    347   
        assert!(request.headers().get("some-query-name").is_none());
  348    348   
        assert_eq!(
  349    349   
            "http://example.com/Foobaz?some-query-name=some-token",
  350    350   
            request.uri().to_string()
  351    351   
        );
  352    352   
    }
  353    353   
  354    354   
    #[test]
  355    355   
    fn test_basic_auth() {
  356    356   
        let signer = BasicAuthSigner;
  357    357   
        let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
  358    358   
        let config_bag = ConfigBag::base();
  359    359   
        let identity = Identity::new(Login::new("Aladdin", "open sesame", None), None);
  360         -
        let mut request = http_02x::Request::builder()
         360  +
        let mut request = http_1x::Request::builder()
  361    361   
            .body(SdkBody::empty())
  362    362   
            .unwrap()
  363    363   
            .try_into()
  364    364   
            .unwrap();
  365    365   
  366    366   
        signer
  367    367   
            .sign_http_request(
  368    368   
                &mut request,
  369    369   
                &identity,
  370    370   
                AuthSchemeEndpointConfig::empty(),
  371    371   
                &runtime_components,
  372    372   
                &config_bag,
  373    373   
            )
  374    374   
            .expect("success");
  375    375   
        assert_eq!(
  376    376   
            "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
  377    377   
            request.headers().get("Authorization").unwrap()
  378    378   
        );
  379    379   
    }
  380    380   
  381    381   
    #[test]
  382    382   
    fn test_bearer_auth() {
  383    383   
        let signer = BearerAuthSigner;
  384    384   
  385    385   
        let config_bag = ConfigBag::base();
  386    386   
        let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
  387    387   
        let identity = Identity::new(Token::new("some-token", None), None);
  388         -
        let mut request = http_02x::Request::builder()
         388  +
        let mut request = http_1x::Request::builder()
  389    389   
            .body(SdkBody::empty())
  390    390   
            .unwrap()
  391    391   
            .try_into()
  392    392   
            .unwrap();
  393    393   
        signer
  394    394   
            .sign_http_request(
  395    395   
                &mut request,
  396    396   
                &identity,
  397    397   
                AuthSchemeEndpointConfig::empty(),
  398    398   
                &runtime_components,
  399    399   
                &config_bag,
  400    400   
            )
  401    401   
            .expect("success");
  402    402   
        assert_eq!(
  403    403   
            "Bearer some-token",
  404    404   
            request.headers().get("Authorization").unwrap()
  405    405   
        );
  406    406   
    }
  407    407   
  408    408   
    #[test]
  409    409   
    fn test_bearer_auth_overwrite_existing_header() {
  410    410   
        let signer = BearerAuthSigner;
  411    411   
  412    412   
        let config_bag = ConfigBag::base();
  413    413   
        let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
  414    414   
        let identity = Identity::new(Token::new("some-token", None), None);
  415         -
        let mut request = http_02x::Request::builder()
         415  +
        let mut request = http_1x::Request::builder()
  416    416   
            .header("Authorization", "wrong")
  417    417   
            .body(SdkBody::empty())
  418    418   
            .unwrap()
  419    419   
            .try_into()
  420    420   
            .unwrap();
  421    421   
        signer
  422    422   
            .sign_http_request(
  423    423   
                &mut request,
  424    424   
                &identity,
  425    425   
                AuthSchemeEndpointConfig::empty(),

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime/src/client/http/body/content_length_enforcement.rs

@@ -190,190 +309,309 @@
  210    210   
mod test {
  211    211   
    use crate::assert_str_contains;
  212    212   
    use crate::client::http::body::content_length_enforcement::{
  213    213   
        extract_content_length, ContentLengthEnforcingBody,
  214    214   
    };
  215    215   
    use aws_smithy_runtime_api::http::Response;
  216    216   
    use aws_smithy_types::body::SdkBody;
  217    217   
    use aws_smithy_types::byte_stream::ByteStream;
  218    218   
    use aws_smithy_types::error::display::DisplayErrorContext;
  219    219   
    use bytes::Bytes;
  220         -
    use http_02x::header::CONTENT_LENGTH;
  221         -
    use http_body_04x::Body;
         220  +
    use http_1x::header::CONTENT_LENGTH;
  222    221   
    use http_body_1x::Frame;
  223    222   
    use std::error::Error;
  224    223   
    use std::pin::Pin;
  225    224   
    use std::task::{Context, Poll};
  226    225   
  227    226   
    /// Body for tests so we ensure our code works on a body split across multiple frames
  228    227   
    struct ManyFrameBody {
  229    228   
        data: Vec<u8>,
  230    229   
    }
  231    230   
  232    231   
    impl ManyFrameBody {
  233    232   
        #[allow(clippy::new_ret_no_self)]
  234    233   
        fn new(input: impl Into<String>) -> SdkBody {
  235    234   
            let mut data = input.into().as_bytes().to_vec();
  236    235   
            data.reverse();
  237    236   
            SdkBody::from_body_1_x(Self { data })
  238    237   
        }
  239    238   
    }
  240    239   
  241    240   
    impl http_body_1x::Body for ManyFrameBody {
  242    241   
        type Data = Bytes;
  243         -
        type Error = <SdkBody as Body>::Error;
         242  +
        type Error = <SdkBody as http_body_1x::Body>::Error;
  244    243   
  245    244   
        fn poll_frame(
  246    245   
            mut self: Pin<&mut Self>,
  247    246   
            _cx: &mut Context<'_>,
  248    247   
        ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
  249    248   
            match self.data.pop() {
  250    249   
                Some(next) => Poll::Ready(Some(Ok(Frame::data(Bytes::from(vec![next]))))),
  251    250   
                None => Poll::Ready(None),
  252    251   
            }
  253    252   
        }
  254    253   
    }
  255    254   
  256    255   
    #[tokio::test]
  257    256   
    async fn stream_too_short() {
  258    257   
        let body = ManyFrameBody::new("123");
  259    258   
        let enforced = ContentLengthEnforcingBody::wrap(body, 10);
  260    259   
        let err = expect_body_error(enforced).await;
  261    260   
        assert_str_contains!(
  262    261   
            format!("{}", DisplayErrorContext(err)),
  263    262   
            "Expected 10 bytes but 3 bytes were received"
  264    263   
        );
  265    264   
    }
  266    265   
  267    266   
    #[tokio::test]
  268    267   
    async fn stream_too_long() {
  269    268   
        let body = ManyFrameBody::new("abcdefghijk");
  270    269   
        let enforced = ContentLengthEnforcingBody::wrap(body, 5);
  271    270   
        let err = expect_body_error(enforced).await;
  272    271   
        assert_str_contains!(
  273    272   
            format!("{}", DisplayErrorContext(err)),
  274    273   
            "Expected 5 bytes but 11 bytes were received"
  275    274   
        );
  276    275   
    }
  277    276   
  278    277   
    #[tokio::test]
  279    278   
    async fn stream_just_right() {
         279  +
        use http_body_util::BodyExt;
  280    280   
        let body = ManyFrameBody::new("abcdefghijk");
  281    281   
        let enforced = ContentLengthEnforcingBody::wrap(body, 11);
  282    282   
        let data = enforced.collect().await.unwrap().to_bytes();
  283    283   
        assert_eq!(b"abcdefghijk", data.as_ref());
  284    284   
    }
  285    285   
  286    286   
    async fn expect_body_error(body: SdkBody) -> impl Error {
  287    287   
        ByteStream::new(body)
  288    288   
            .collect()
  289    289   
            .await

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime/src/client/http/body/minimum_throughput.rs

@@ -1,1 +42,45 @@
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
    6      6   
//! A body-wrapping type that ensures data is being streamed faster than some lower limit.
    7      7   
//!
    8      8   
//! If data is being streamed too slowly, this body type will emit an error next time it's polled.
    9      9   
   10     10   
/// An implementation of v0.4 `http_body::Body` for `MinimumThroughputBody` and related code.
   11     11   
pub mod http_body_0_4_x;
   12     12   
          13  +
/// An implementation of v1.0 `http_body::Body` for `MinimumThroughputBody` and related code.
          14  +
pub mod http_body_1_x;
          15  +
   13     16   
/// Options for a [`MinimumThroughputBody`].
   14     17   
pub mod options;
   15     18   
pub use throughput::Throughput;
   16     19   
mod throughput;
   17     20   
   18     21   
use crate::client::http::body::minimum_throughput::throughput::ThroughputReport;
   19     22   
use aws_smithy_async::rt::sleep::Sleep;
   20     23   
use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
   21     24   
use aws_smithy_async::time::{SharedTimeSource, TimeSource};
   22     25   
use aws_smithy_runtime_api::{

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime/src/client/http/body/minimum_throughput/http_body_0_4_x.rs

@@ -1,1 +98,56 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
    6      6   
use super::{BoxError, Error, MinimumThroughputDownloadBody};
    7         -
use crate::client::http::body::minimum_throughput::{
    8         -
    throughput::ThroughputReport, Throughput, ThroughputReadingBody,
    9         -
};
           7  +
use crate::client::http::body::minimum_throughput::throughput::DownloadReport;
           8  +
use crate::client::http::body::minimum_throughput::ThroughputReadingBody;
   10      9   
use aws_smithy_async::rt::sleep::AsyncSleep;
   11         -
use http_body_04x::Body;
   12     10   
use std::future::Future;
   13     11   
use std::pin::{pin, Pin};
   14     12   
use std::task::{Context, Poll};
   15     13   
   16         -
const ZERO_THROUGHPUT: Throughput = Throughput::new_bytes_per_second(0);
   17         -
   18         -
// Helper trait for interpreting the throughput report.
   19         -
trait DownloadReport {
   20         -
    fn minimum_throughput_violated(self, minimum_throughput: Throughput) -> (bool, Throughput);
   21         -
}
   22         -
impl DownloadReport for ThroughputReport {
   23         -
    fn minimum_throughput_violated(self, minimum_throughput: Throughput) -> (bool, Throughput) {
   24         -
        let throughput = match self {
   25         -
            ThroughputReport::Complete => return (false, ZERO_THROUGHPUT),
   26         -
            // If the report is incomplete, then we don't have enough data yet to
   27         -
            // decide if minimum throughput was violated.
   28         -
            ThroughputReport::Incomplete => {
   29         -
                tracing::trace!(
   30         -
                    "not enough data to decide if minimum throughput has been violated"
   31         -
                );
   32         -
                return (false, ZERO_THROUGHPUT);
   33         -
            }
   34         -
            // If no polling is taking place, then the user has stalled.
   35         -
            // In this case, we don't want to say minimum throughput was violated.
   36         -
            ThroughputReport::NoPolling => {
   37         -
                tracing::debug!(
   38         -
                    "the user has stalled; this will not become a minimum throughput violation"
   39         -
                );
   40         -
                return (false, ZERO_THROUGHPUT);
   41         -
            }
   42         -
            // If we're stuck in Poll::Pending, then the server has stalled. Alternatively,
   43         -
            // if we're transferring data, but it's too slow, then we also want to say
   44         -
            // that the minimum throughput has been violated.
   45         -
            ThroughputReport::Pending => ZERO_THROUGHPUT,
   46         -
            ThroughputReport::Transferred(tp) => tp,
   47         -
        };
   48         -
        let violated = throughput < minimum_throughput;
   49         -
        if violated {
   50         -
            tracing::debug!(
   51         -
                "current throughput: {throughput} is below minimum: {minimum_throughput}"
   52         -
            );
   53         -
        }
   54         -
        (violated, throughput)
   55         -
    }
   56         -
}
   57         -
   58         -
impl<B> Body for MinimumThroughputDownloadBody<B>
          14  +
impl<B> http_body_04x::Body for MinimumThroughputDownloadBody<B>
   59     15   
where
   60         -
    B: Body<Data = bytes::Bytes, Error = BoxError>,
          16  +
    B: http_body_04x::Body<Data = bytes::Bytes, Error = BoxError>,
   61     17   
{
   62     18   
    type Data = bytes::Bytes;
   63     19   
    type Error = BoxError;
   64     20   
   65     21   
    fn poll_data(
   66     22   
        mut self: Pin<&mut Self>,
   67     23   
        cx: &mut Context<'_>,
   68     24   
    ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
          25  +
        #[allow(unused_imports)]
          26  +
        use crate::client::http::body::minimum_throughput::throughput::ThroughputReport;
   69     27   
        // this code is called quite frequently in production—one every millisecond or so when downloading
   70     28   
        // a stream. However, SystemTime::now is on the order of nanoseconds
   71     29   
        let now = self.time_source.now();
   72     30   
        // Attempt to read the data from the inner body, then update the
   73     31   
        // throughput logs.
   74     32   
        let mut this = self.as_mut().project();
   75     33   
        let poll_res = match this.inner.poll_data(cx) {
   76     34   
            Poll::Ready(Some(Ok(bytes))) => {
   77     35   
                tracing::trace!("received data: {}", bytes.len());
   78     36   
                this.throughput_logs
@@ -127,85 +189,147 @@
  147    105   
  148    106   
    fn is_end_stream(&self) -> bool {
  149    107   
        self.inner.is_end_stream()
  150    108   
    }
  151    109   
  152    110   
    fn size_hint(&self) -> http_body_04x::SizeHint {
  153    111   
        self.inner.size_hint()
  154    112   
    }
  155    113   
}
  156    114   
  157         -
impl<B> Body for ThroughputReadingBody<B>
         115  +
impl<B> http_body_04x::Body for ThroughputReadingBody<B>
  158    116   
where
  159         -
    B: Body<Data = bytes::Bytes, Error = BoxError>,
         117  +
    B: http_body_04x::Body<Data = bytes::Bytes, Error = BoxError>,
  160    118   
{
  161    119   
    type Data = bytes::Bytes;
  162    120   
    type Error = BoxError;
  163    121   
  164    122   
    fn poll_data(
  165    123   
        mut self: Pin<&mut Self>,
  166    124   
        cx: &mut Context<'_>,
  167    125   
    ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
  168    126   
        // this code is called quite frequently in production—one every millisecond or so when downloading
  169    127   
        // a stream. However, SystemTime::now is on the order of nanoseconds

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-runtime/src/client/http/body/minimum_throughput/http_body_1_x.rs

@@ -0,1 +0,177 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
use super::{BoxError, Error, MinimumThroughputDownloadBody};
           7  +
use crate::client::http::body::minimum_throughput::throughput::DownloadReport;
           8  +
use crate::client::http::body::minimum_throughput::ThroughputReadingBody;
           9  +
use aws_smithy_async::rt::sleep::AsyncSleep;
          10  +
use http_body_1x::Frame;
          11  +
use std::future::Future;
          12  +
use std::pin::{pin, Pin};
          13  +
use std::task::{Context, Poll};
          14  +
          15  +
impl<B> http_body_1x::Body for MinimumThroughputDownloadBody<B>
          16  +
where
          17  +
    B: http_body_1x::Body<Data = bytes::Bytes, Error = BoxError>,
          18  +
{
          19  +
    type Data = bytes::Bytes;
          20  +
    type Error = BoxError;
          21  +
          22  +
    fn poll_frame(
          23  +
        mut self: Pin<&mut Self>,
          24  +
        cx: &mut Context<'_>,
          25  +
    ) -> Poll<Option<Result<http_body_1x::Frame<Self::Data>, Self::Error>>> {
          26  +
        #[allow(unused_imports)]
          27  +
        use crate::client::http::body::minimum_throughput::throughput::ThroughputReport;
          28  +
        // this code is called quite frequently in production—one every millisecond or so when downloading
          29  +
        // a stream. However, SystemTime::now is on the order of nanoseconds
          30  +
        let now = self.time_source.now();
          31  +
        // Attempt to read the data from the inner body, then update the
          32  +
        // throughput logs.
          33  +
        let mut this = self.as_mut().project();
          34  +
        let poll_res = match this.inner.poll_frame(cx) {
          35  +
            Poll::Ready(Some(Ok(frame))) => {
          36  +
                if frame.is_data() {
          37  +
                    let bytes = frame.into_data().expect("Is data frame");
          38  +
                    tracing::trace!("received data: {}", bytes.len());
          39  +
                    this.throughput_logs
          40  +
                        .push_bytes_transferred(now, bytes.len() as u64);
          41  +
                    Poll::Ready(Some(Ok(Frame::data(bytes))))
          42  +
                } else {
          43  +
                    tracing::trace!("received trailer");
          44  +
                    Poll::Ready(Some(Ok(frame)))
          45  +
                }
          46  +
            }
          47  +
            Poll::Pending => {
          48  +
                tracing::trace!("received poll pending");
          49  +
                this.throughput_logs.push_pending(now);
          50  +
                Poll::Pending
          51  +
            }
          52  +
            // If we've read all the data or an error occurred, then return that result.
          53  +
            res => return res,
          54  +
        };
          55  +
          56  +
        // Check the sleep future to see if it needs refreshing.
          57  +
        let mut sleep_fut = this
          58  +
            .sleep_fut
          59  +
            .take()
          60  +
            .unwrap_or_else(|| this.async_sleep.sleep(*this.resolution));
          61  +
        if let Poll::Ready(()) = pin!(&mut sleep_fut).poll(cx) {
          62  +
            tracing::trace!("sleep future triggered—triggering a wakeup");
          63  +
            // Whenever the sleep future expires, we replace it.
          64  +
            sleep_fut = this.async_sleep.sleep(*this.resolution);
          65  +
          66  +
            // We also schedule a wake up for current task to ensure that
          67  +
            // it gets polled at least one more time.
          68  +
            cx.waker().wake_by_ref();
          69  +
        };
          70  +
        this.sleep_fut.replace(sleep_fut);
          71  +
          72  +
        // Calculate the current throughput and emit an error if it's too low and
          73  +
        // the grace period has elapsed.
          74  +
        let report = this.throughput_logs.report(now);
          75  +
        let (violated, current_throughput) =
          76  +
            report.minimum_throughput_violated(this.options.minimum_throughput());
          77  +
        if violated {
          78  +
            if this.grace_period_fut.is_none() {
          79  +
                tracing::debug!("entering minimum throughput grace period");
          80  +
            }
          81  +
            let mut grace_period_fut = this
          82  +
                .grace_period_fut
          83  +
                .take()
          84  +
                .unwrap_or_else(|| this.async_sleep.sleep(this.options.grace_period()));
          85  +
            if let Poll::Ready(()) = pin!(&mut grace_period_fut).poll(cx) {
          86  +
                // The grace period has ended!
          87  +
                return Poll::Ready(Some(Err(Box::new(Error::ThroughputBelowMinimum {
          88  +
                    expected: self.options.minimum_throughput(),
          89  +
                    actual: current_throughput,
          90  +
                }))));
          91  +
            };
          92  +
            this.grace_period_fut.replace(grace_period_fut);
          93  +
        } else {
          94  +
            // Ensure we don't have an active grace period future if we're not
          95  +
            // currently below the minimum throughput.
          96  +
            if this.grace_period_fut.is_some() {
          97  +
                tracing::debug!("throughput recovered; exiting grace period");
          98  +
            }
          99  +
            let _ = this.grace_period_fut.take();
         100  +
        }
         101  +
         102  +
        poll_res
         103  +
    }
         104  +
         105  +
    fn is_end_stream(&self) -> bool {
         106  +
        self.inner.is_end_stream()
         107  +
    }
         108  +
         109  +
    fn size_hint(&self) -> http_body_1x::SizeHint {
         110  +
        self.inner.size_hint()
         111  +
    }
         112  +
}
         113  +
         114  +
impl<B> http_body_1x::Body for ThroughputReadingBody<B>
         115  +
where
         116  +
    B: http_body_1x::Body<Data = bytes::Bytes, Error = BoxError>,
         117  +
{
         118  +
    type Data = bytes::Bytes;
         119  +
    type Error = BoxError;
         120  +
         121  +
    fn poll_frame(
         122  +
        mut self: Pin<&mut Self>,
         123  +
        cx: &mut Context<'_>,
         124  +
    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
         125  +
        // this code is called quite frequently in production—one every millisecond or so when downloading
         126  +
        // a stream. However, SystemTime::now is on the order of nanoseconds
         127  +
        let now = self.time_source.now();
         128  +
        // Attempt to read the data from the inner body, then update the
         129  +
        // throughput logs.
         130  +
        let this = self.as_mut().project();
         131  +
        match this.inner.poll_frame(cx) {
         132  +
            Poll::Ready(Some(Ok(frame))) => {
         133  +
                if frame.is_data() {
         134  +
                    let bytes = frame.into_data().expect("Is data frame");
         135  +
                    tracing::trace!("received data: {}", bytes.len());
         136  +
                    this.throughput
         137  +
                        .push_bytes_transferred(now, bytes.len() as u64);
         138  +
         139  +
                    // hyper will optimistically stop polling when end of stream is reported
         140  +
                    // (e.g. when content-length amount of data has been consumed) which means
         141  +
                    // we may never get to `Poll:Ready(None)`. Check for same condition and
         142  +
                    // attempt to stop checking throughput violations _now_ as we may never
         143  +
                    // get polled again. The caveat here is that it depends on `Body` implementations
         144  +
                    // implementing `is_end_stream()` correctly. Users can also disable SSP as an
         145  +
                    // alternative for such fringe use cases.
         146  +
                    if self.is_end_stream() {
         147  +
                        tracing::trace!("stream reported end of stream before Poll::Ready(None) reached; marking stream complete");
         148  +
                        self.throughput.mark_complete();
         149  +
                    }
         150  +
                    Poll::Ready(Some(Ok(Frame::data(bytes))))
         151  +
                } else {
         152  +
                    Poll::Ready(Some(Ok(frame)))
         153  +
                }
         154  +
            }
         155  +
            Poll::Pending => {
         156  +
                tracing::trace!("received poll pending");
         157  +
                this.throughput.push_pending(now);
         158  +
                Poll::Pending
         159  +
            }
         160  +
            // If we've read all the data or an error occurred, then return that result.
         161  +
            res => {
         162  +
                if this.throughput.mark_complete() {
         163  +
                    tracing::trace!("stream completed: {:?}", res);
         164  +
                }
         165  +
                res
         166  +
            }
         167  +
        }
         168  +
    }
         169  +
         170  +
    fn is_end_stream(&self) -> bool {
         171  +
        self.inner.is_end_stream()
         172  +
    }
         173  +
         174  +
    fn size_hint(&self) -> http_body_1x::SizeHint {
         175  +
        self.inner.size_hint()
         176  +
    }
         177  +
}