AWS SDK

AWS SDK

rev. a16ec9092803c091887fa244df7aa8e7df1c79c8

Files changed:

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

@@ -0,1 +0,328 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
//! # aws-smithy-mocks
           7  +
//!
           8  +
//! A flexible mocking framework for testing clients generated by smithy-rs, including all packages of the AWS SDK for Rust.
           9  +
//!
          10  +
//! This crate provides a simple yet powerful way to mock SDK client responses for testing purposes.
          11  +
//! It uses interceptors to return stub responses, allowing you to test both happy-path and error scenarios
          12  +
//! without mocking the entire client or using traits.
          13  +
//!
          14  +
//! ## Key Features
          15  +
//!
          16  +
//! - **Simple API**: Create mock rules with a fluent API using the [`mock!`] macro
          17  +
//! - **Flexible Response Types**: Return modeled outputs, errors, or raw HTTP responses
          18  +
//! - **Request Matching**: Match requests based on their properties
          19  +
//! - **Response Sequencing**: Define sequences of responses for testing retry behavior
          20  +
//! - **Rule Modes**: Control how rules are matched and applied
          21  +
//!
          22  +
//! ## Basic Usage
          23  +
//!
          24  +
//! ```rust,ignore
          25  +
//! use aws_sdk_s3::operation::get_object::GetObjectOutput;
          26  +
//! use aws_sdk_s3::Client;
          27  +
//! use aws_smithy_types::byte_stream::ByteStream;
          28  +
//! use aws_smithy_mocks::{mock, mock_client};
          29  +
//!
          30  +
//! #[tokio::test]
          31  +
//! async fn test_s3_get_object() {
          32  +
//!     // Create a rule that returns a successful response
          33  +
//!     let get_object_rule = mock!(Client::get_object)
          34  +
//!         .match_requests(|req| req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
          35  +
//!         .then_output(|| GetObjectOutput::builder()
          36  +
//!             .body(ByteStream::from_static(b"test-content"))
          37  +
//!             .build()
          38  +
//!          );
          39  +
//!
          40  +
//!     // Create a mocked client with the rule
          41  +
//!     let s3 = mock_client!(aws_sdk_s3, [&get_object_rule]);
          42  +
//!
          43  +
//!     // Use the client as you would normally
          44  +
//!     let result = s3.get_object()
          45  +
//!         .bucket("test-bucket")
          46  +
//!         .key("test-key")
          47  +
//!         .send()
          48  +
//!         .await
          49  +
//!         .expect("success response");
          50  +
//!
          51  +
//!     // Verify the response
          52  +
//!     let data = result.body.collect().await.expect("successful read").to_vec();
          53  +
//!     assert_eq!(data, b"test-content");
          54  +
//!
          55  +
//!     // Verify the rule was used
          56  +
//!     assert_eq!(get_object_rule.num_calls(), 1);
          57  +
//! }
          58  +
//! ```
          59  +
//!
          60  +
//! ## Creating Rules
          61  +
//!
          62  +
//! Rules are created using the [`mock!`] macro, which takes a client operation as an argument:
          63  +
//!
          64  +
//! ```rust,ignore
          65  +
//! let rule = mock!(Client::get_object)
          66  +
//!     // Optional: Add a matcher to filter requests
          67  +
//!     .match_requests(|req| req.bucket() == Some("test-bucket"))
          68  +
//!     // Add a response
          69  +
//!     .then_output(|| GetObjectOutput::builder().build());
          70  +
//! ```
          71  +
//!
          72  +
//! ### Response Types
          73  +
//!
          74  +
//! You can return different types of responses:
          75  +
//!
          76  +
//! ```rust,ignore
          77  +
//! // Return a modeled output
          78  +
//! let success_rule = mock!(Client::get_object)
          79  +
//!     .then_output(|| GetObjectOutput::builder().build());
          80  +
//!
          81  +
//! // Return a modeled error
          82  +
//! let error_rule = mock!(Client::get_object)
          83  +
//!     .then_error(|| GetObjectError::NoSuchKey(NoSuchKey::builder().build()));
          84  +
//!
          85  +
//! // Return an HTTP response
          86  +
//! let http_rule = mock!(Client::get_object)
          87  +
//!     .then_http_response(|| HttpResponse::new(
          88  +
//!         StatusCode::try_from(503).unwrap(),
          89  +
//!         SdkBody::from("service unavailable")
          90  +
//!     ));
          91  +
//! ```
          92  +
//!
          93  +
//! ### Response Sequences
          94  +
//!
          95  +
//! For testing retry behavior or complex scenarios, you can define sequences of responses using the sequence builder API:
          96  +
//!
          97  +
//! ```rust,ignore
          98  +
//! let retry_rule = mock!(Client::get_object)
          99  +
//!     .sequence()
         100  +
//!     .http_status(503, None)                          // First call returns 503
         101  +
//!     .http_status(503, None)                          // Second call returns 503
         102  +
//!     .output(|| GetObjectOutput::builder().build())   // Third call succeeds
         103  +
//!     .build();
         104  +
//!
         105  +
//! // With repetition using `times()`
         106  +
//! let retry_rule = mock!(Client::get_object)
         107  +
//!     .sequence()
         108  +
//!     .http_status(503, None)
         109  +
//!     .times(2)                                        // First two calls return 503
         110  +
//!     .output(|| GetObjectOutput::builder().build())   // Third call succeeds
         111  +
//!     .build();
         112  +
//! ```
         113  +
//!
         114  +
//! The sequence builder API provides a fluent interface for defining sequences of responses.
         115  +
//! After providing all responses in the sequence, the rule is considered exhausted.
         116  +
//!
         117  +
//! ## Creating Mocked Clients
         118  +
//!
         119  +
//! Use the [`mock_client!`] macro to create a client with your rules:
         120  +
//!
         121  +
//! ```rust,ignore
         122  +
//! // Create a client with a single rule
         123  +
//! let client = mock_client!(aws_sdk_s3, [&rule]);
         124  +
//!
         125  +
//! // Create a client with multiple rules and a specific rule mode
         126  +
//! let client = mock_client!(aws_sdk_s3, RuleMode::Sequential, [&rule1, &rule2]);
         127  +
//!
         128  +
//! // Create a client with additional configuration
         129  +
//! let client = mock_client!(
         130  +
//!     aws_sdk_s3,
         131  +
//!     RuleMode::Sequential,
         132  +
//!     [&rule],
         133  +
//!     |config| config.force_path_style(true)
         134  +
//! );
         135  +
//! ```
         136  +
//!
         137  +
//! ## Rule Modes
         138  +
//!
         139  +
//! The [`RuleMode`] enum controls how rules are matched and applied:
         140  +
//!
         141  +
//! - `RuleMode::Sequential`: Rules are tried in order. When a rule is exhausted, the next rule is used.
         142  +
//! - `RuleMode::MatchAny`: The first matching rule is used, regardless of order.
         143  +
//!
         144  +
//! ```rust,ignore
         145  +
//! let interceptor = MockResponseInterceptor::new()
         146  +
//!     .rule_mode(RuleMode::Sequential)
         147  +
//!     .with_rule(&rule1)
         148  +
//!     .with_rule(&rule2);
         149  +
//! ```
         150  +
//!
         151  +
//! ## Testing Retry Behavior
         152  +
//!
         153  +
//! The mocking framework supports testing retry behavior by allowing you to define sequences of responses:
         154  +
//!
         155  +
//! ```rust,ignore
         156  +
//! #[tokio::test]
         157  +
//! async fn test_retry() {
         158  +
//!     // Create a rule that returns errors for the first two attempts, then succeeds
         159  +
//!     let rule = mock!(Client::get_object)
         160  +
//!         .sequence()
         161  +
//!         .http_status(503, None)
         162  +
//!         .times(2)                                       // Service unavailable for first two calls
         163  +
//!         .output(|| GetObjectOutput::builder().build())  // Success on third call
         164  +
//!         .build();
         165  +
//!
         166  +
//!     // Create a client with retry enabled
         167  +
//!     let client = mock_client!(aws_sdk_s3, [&rule]);
         168  +
//!
         169  +
//!     // The operation should succeed after retries
         170  +
//!     let result = client.get_object()
         171  +
//!         .bucket("test-bucket")
         172  +
//!         .key("test-key")
         173  +
//!         .send()
         174  +
//!         .await;
         175  +
//!
         176  +
//!     assert!(result.is_ok());
         177  +
//!     assert_eq!(rule.num_calls(), 3);  // Called 3 times (2 failures + 1 success)
         178  +
//! }
         179  +
//! ```
         180  +
//!
         181  +
         182  +
/* Automatically managed default lints */
         183  +
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
         184  +
/* End of automatically managed default lints */
         185  +
#![warn(
         186  +
    missing_docs,
         187  +
    rustdoc::missing_crate_level_docs,
         188  +
    unreachable_pub,
         189  +
    rust_2018_idioms
         190  +
)]
         191  +
         192  +
mod interceptor;
         193  +
mod rule;
         194  +
         195  +
pub use interceptor::{create_mock_http_client, MockResponseInterceptor};
         196  +
pub(crate) use rule::MockResponse;
         197  +
pub use rule::{Rule, RuleBuilder, RuleMode};
         198  +
         199  +
// why do we need a macro for this?
         200  +
// We want customers to be able to provide an ergonomic way to say the method they're looking for,
         201  +
// `Client::list_buckets`, e.g. But there isn't enough information on that type to recover everything.
         202  +
// This macro commits a small amount of crimes to recover that type information so we can construct
         203  +
// a rule that can intercept these operations.
         204  +
         205  +
/// `mock!` macro that produces a [`RuleBuilder`] from a client invocation
         206  +
///
         207  +
/// See the `examples` folder of this crate for fully worked examples.
         208  +
///
         209  +
/// # Examples
         210  +
///
         211  +
/// **Mock and return a success response**:
         212  +
///
         213  +
/// ```rust,ignore
         214  +
/// use aws_sdk_s3::operation::get_object::GetObjectOutput;
         215  +
/// use aws_sdk_s3::Client;
         216  +
/// use aws_smithy_types::byte_stream::ByteStream;
         217  +
/// use aws_smithy_mocks::mock;
         218  +
/// let get_object_happy_path = mock!(Client::get_object)
         219  +
///   .match_requests(|req|req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
         220  +
///   .then_output(||GetObjectOutput::builder().body(ByteStream::from_static(b"12345-abcde")).build());
         221  +
/// ```
         222  +
///
         223  +
/// **Mock and return an error**:
         224  +
/// ```rust,ignore
         225  +
/// use aws_sdk_s3::operation::get_object::GetObjectError;
         226  +
/// use aws_sdk_s3::types::error::NoSuchKey;
         227  +
/// use aws_sdk_s3::Client;
         228  +
/// use aws_smithy_mocks::mock;
         229  +
/// let get_object_error_path = mock!(Client::get_object)
         230  +
///   .then_error(||GetObjectError::NoSuchKey(NoSuchKey::builder().build()));
         231  +
/// ```
         232  +
///
         233  +
#[macro_export]
         234  +
macro_rules! mock {
         235  +
    ($operation: expr) => {
         236  +
        #[allow(unreachable_code)]
         237  +
        {
         238  +
            $crate::RuleBuilder::new_from_mock(
         239  +
                // We don't actually want to run this code, so we put it in a closure. The closure
         240  +
                // has the types we want which makes this whole thing type-safe (and the IDE can even
         241  +
                // figure out the right input/output types in inference!)
         242  +
                // The code generated here is:
         243  +
                // `Client::list_buckets(todo!())`
         244  +
                || $operation(todo!()).as_input().clone().build().unwrap(),
         245  +
                || $operation(todo!()).send(),
         246  +
            )
         247  +
        }
         248  +
    };
         249  +
}
         250  +
         251  +
// This could be obviated by a reasonable trait, since you can express it with SdkConfig if clients implement From<&SdkConfig>.
         252  +
         253  +
/// `mock_client!` macro produces a Client configured with a number of Rules and appropriate test default configuration.
         254  +
///
         255  +
/// # Examples
         256  +
///
         257  +
/// **Create a client that uses a mock failure and then a success**:
         258  +
///
         259  +
/// ```rust,ignore
         260  +
/// use aws_sdk_s3::operation::get_object::{GetObjectOutput, GetObjectError};
         261  +
/// use aws_sdk_s3::types::error::NoSuchKey;
         262  +
/// use aws_sdk_s3::Client;
         263  +
/// use aws_smithy_types::byte_stream::ByteStream;
         264  +
/// use aws_smithy_mocks::{mock_client, mock, RuleMode};
         265  +
/// let get_object_error_path = mock!(Client::get_object)
         266  +
///   .then_error(||GetObjectError::NoSuchKey(NoSuchKey::builder().build()))
         267  +
///   .build();
         268  +
/// let get_object_happy_path = mock!(Client::get_object)
         269  +
///   .match_requests(|req|req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
         270  +
///   .then_output(||GetObjectOutput::builder().body(ByteStream::from_static(b"12345-abcde")).build())
         271  +
///   .build();
         272  +
/// let client = mock_client!(aws_sdk_s3, RuleMode::Sequential, &[&get_object_error_path, &get_object_happy_path]);
         273  +
///
         274  +
///
         275  +
/// **Create a client but customize a specific setting**:
         276  +
/// rust,ignore
         277  +
/// use aws_sdk_s3::operation::get_object::GetObjectOutput;
         278  +
/// use aws_sdk_s3::Client;
         279  +
/// use aws_smithy_types::byte_stream::ByteStream;
         280  +
/// use aws_smithy_mocks::{mock_client, mock, RuleMode};
         281  +
/// let get_object_happy_path = mock!(Client::get_object)
         282  +
///   .match_requests(|req|req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
         283  +
///   .then_output(||GetObjectOutput::builder().body(ByteStream::from_static(b"12345-abcde")).build())
         284  +
///   .build();
         285  +
/// let client = mock_client!(
         286  +
///     aws_sdk_s3,
         287  +
///     RuleMode::Sequential,
         288  +
///     &[&get_object_happy_path],
         289  +
///     // Perhaps you need to force path style
         290  +
///     |client_builder|client_builder.force_path_style(true)
         291  +
/// );
         292  +
/// ```
         293  +
///
         294  +
#[macro_export]
         295  +
macro_rules! mock_client {
         296  +
    ($aws_crate: ident, $rules: expr) => {
         297  +
        $crate::mock_client!($aws_crate, $crate::RuleMode::Sequential, $rules)
         298  +
    };
         299  +
    ($aws_crate: ident, $rule_mode: expr, $rules: expr) => {{
         300  +
        $crate::mock_client!($aws_crate, $rule_mode, $rules, |conf| conf)
         301  +
    }};
         302  +
    ($aws_crate: ident, $rule_mode: expr, $rules: expr, $additional_configuration: expr) => {{
         303  +
        let mut mock_response_interceptor =
         304  +
            $crate::MockResponseInterceptor::new().rule_mode($rule_mode);
         305  +
        for rule in $rules {
         306  +
            mock_response_interceptor = mock_response_interceptor.with_rule(rule)
         307  +
        }
         308  +
         309  +
        // Create a mock HTTP client
         310  +
        let mock_http_client = $crate::create_mock_http_client();
         311  +
         312  +
        // Allow callers to avoid explicitly specifying the type
         313  +
        fn coerce<T: Fn($aws_crate::config::Builder) -> $aws_crate::config::Builder>(f: T) -> T {
         314  +
            f
         315  +
        }
         316  +
         317  +
        $aws_crate::client::Client::from_conf(
         318  +
            coerce($additional_configuration)(
         319  +
                $aws_crate::config::Config::builder()
         320  +
                    .with_test_defaults()
         321  +
                    .region($aws_crate::config::Region::from_static("us-east-1"))
         322  +
                    .http_client(mock_http_client)
         323  +
                    .interceptor(mock_response_interceptor),
         324  +
            )
         325  +
            .build(),
         326  +
        )
         327  +
    }};
         328  +
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-mocks/src/rule.rs

@@ -0,1 +0,336 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, Output};
           7  +
use aws_smithy_runtime_api::client::orchestrator::HttpResponse;
           8  +
use aws_smithy_runtime_api::client::result::SdkError;
           9  +
use aws_smithy_runtime_api::http::StatusCode;
          10  +
use aws_smithy_types::body::SdkBody;
          11  +
use std::fmt;
          12  +
use std::future::Future;
          13  +
use std::sync::atomic::{AtomicUsize, Ordering};
          14  +
use std::sync::Arc;
          15  +
          16  +
/// A mock response that can be returned by a rule.
          17  +
///
          18  +
/// This enum represents the different types of responses that can be returned by a mock rule:
          19  +
/// - `Output`: A successful modeled response
          20  +
/// - `Error`: A modeled error
          21  +
/// - `Http`: An HTTP response
          22  +
///
          23  +
#[derive(Debug)]
          24  +
pub(crate) enum MockResponse<O, E> {
          25  +
    /// A successful modeled response.
          26  +
    Output(O),
          27  +
    /// A modeled error.
          28  +
    Error(E),
          29  +
    /// An HTTP response.
          30  +
    Http(HttpResponse),
          31  +
}
          32  +
          33  +
/// A function that matches requests.
          34  +
type MatchFn = Arc<dyn Fn(&Input) -> bool + Send + Sync>;
          35  +
type ServeFn = Arc<dyn Fn(usize) -> Option<MockResponse<Output, Error>> + Send + Sync>;
          36  +
          37  +
/// A rule for matching requests and providing mock responses.
          38  +
///
          39  +
/// Rules are created using the `mock!` macro or the `RuleBuilder`.
          40  +
///
          41  +
#[derive(Clone)]
          42  +
pub struct Rule {
          43  +
    /// Function that determines if this rule matches a request.
          44  +
    pub(crate) matcher: MatchFn,
          45  +
          46  +
    /// Handler function that generates responses.
          47  +
    pub(crate) response_handler: ServeFn,
          48  +
          49  +
    /// Number of times this rule has been called.
          50  +
    pub(crate) call_count: Arc<AtomicUsize>,
          51  +
          52  +
    /// Maximum number of responses this rule will provide.
          53  +
    pub(crate) max_responses: usize,
          54  +
}
          55  +
          56  +
impl fmt::Debug for Rule {
          57  +
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
          58  +
        write!(f, "Rule")
          59  +
    }
          60  +
}
          61  +
          62  +
impl Rule {
          63  +
    /// Creates a new rule with the given matcher, response handler, and max responses.
          64  +
    pub(crate) fn new<O, E>(
          65  +
        matcher: MatchFn,
          66  +
        response_handler: Arc<dyn Fn(usize) -> Option<MockResponse<O, E>> + Send + Sync>,
          67  +
        max_responses: usize,
          68  +
    ) -> Self
          69  +
    where
          70  +
        O: fmt::Debug + Send + Sync + 'static,
          71  +
        E: fmt::Debug + Send + Sync + std::error::Error + 'static,
          72  +
    {
          73  +
        Rule {
          74  +
            matcher,
          75  +
            response_handler: Arc::new(move |idx: usize| {
          76  +
                if idx < max_responses {
          77  +
                    response_handler(idx).map(|resp| match resp {
          78  +
                        MockResponse::Output(o) => MockResponse::Output(Output::erase(o)),
          79  +
                        MockResponse::Error(e) => MockResponse::Error(Error::erase(e)),
          80  +
                        MockResponse::Http(http_resp) => MockResponse::Http(http_resp),
          81  +
                    })
          82  +
                } else {
          83  +
                    None
          84  +
                }
          85  +
            }),
          86  +
            call_count: Arc::new(AtomicUsize::new(0)),
          87  +
            max_responses,
          88  +
        }
          89  +
    }
          90  +
          91  +
    /// Gets the next response.
          92  +
    pub(crate) fn next_response(&self) -> Option<MockResponse<Output, Error>> {
          93  +
        let idx = self.call_count.fetch_add(1, Ordering::SeqCst);
          94  +
        (self.response_handler)(idx)
          95  +
    }
          96  +
          97  +
    /// Returns the number of times this rule has been called.
          98  +
    pub fn num_calls(&self) -> usize {
          99  +
        self.call_count.load(Ordering::SeqCst)
         100  +
    }
         101  +
         102  +
    /// Checks if this rule is exhausted (has provided all its responses).
         103  +
    pub fn is_exhausted(&self) -> bool {
         104  +
        self.num_calls() >= self.max_responses
         105  +
    }
         106  +
}
         107  +
         108  +
/// RuleMode describes how rules will be interpreted.
         109  +
/// - In RuleMode::MatchAny, the first matching rule will be applied, and the rules will remain unchanged.
         110  +
/// - In RuleMode::Sequential, the first matching rule will be applied, and that rule will be removed from the list of rules **once it is exhausted**.
         111  +
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
         112  +
pub enum RuleMode {
         113  +
    /// Match rules in the order they were added. The first matching rule will be applied and the
         114  +
    /// rules will remain unchanged
         115  +
    Sequential,
         116  +
    /// The first matching rule will be applied, and that rule will be removed from the list of rules
         117  +
    /// **once it is exhausted**. Each rule can have multiple responses, and all responses in a rule
         118  +
    /// will be consumed before moving to the next rule.
         119  +
    MatchAny,
         120  +
}
         121  +
         122  +
/// A builder for creating rules.
         123  +
///
         124  +
/// This builder provides a fluent API for creating rules with different response types.
         125  +
///
         126  +
pub struct RuleBuilder<I, O, E> {
         127  +
    /// Function that determines if this rule matches a request.
         128  +
    pub(crate) input_filter: MatchFn,
         129  +
         130  +
    /// Phantom data for the input type.
         131  +
    pub(crate) _ty: std::marker::PhantomData<(I, O, E)>,
         132  +
}
         133  +
         134  +
impl<I, O, E> RuleBuilder<I, O, E>
         135  +
where
         136  +
    I: fmt::Debug + Send + Sync + 'static,
         137  +
    O: fmt::Debug + Send + Sync + 'static,
         138  +
    E: fmt::Debug + Send + Sync + std::error::Error + 'static,
         139  +
{
         140  +
    /// Creates a new [`RuleBuilder`]
         141  +
    #[doc(hidden)]
         142  +
    pub fn new() -> Self {
         143  +
        RuleBuilder {
         144  +
            input_filter: Arc::new(|i: &Input| i.downcast_ref::<I>().is_some()),
         145  +
            _ty: std::marker::PhantomData,
         146  +
        }
         147  +
    }
         148  +
         149  +
    /// Creates a new [`RuleBuilder`]. This is normally constructed with the [`mock!`] macro
         150  +
    #[doc(hidden)]
         151  +
    pub fn new_from_mock<F, R>(_input_hint: impl Fn() -> I, _output_hint: impl Fn() -> F) -> Self
         152  +
    where
         153  +
        F: Future<Output = Result<O, SdkError<E, R>>>,
         154  +
    {
         155  +
        Self {
         156  +
            input_filter: Arc::new(|i: &Input| i.downcast_ref::<I>().is_some()),
         157  +
            _ty: Default::default(),
         158  +
        }
         159  +
    }
         160  +
         161  +
    /// Sets the function that determines if this rule matches a request.
         162  +
    pub fn match_requests<F>(mut self, filter: F) -> Self
         163  +
    where
         164  +
        F: Fn(&I) -> bool + Send + Sync + 'static,
         165  +
    {
         166  +
        self.input_filter = Arc::new(move |i: &Input| match i.downcast_ref::<I>() {
         167  +
            Some(typed_input) => filter(typed_input),
         168  +
            _ => false,
         169  +
        });
         170  +
        self
         171  +
    }
         172  +
         173  +
    /// Start building a response sequence
         174  +
    ///
         175  +
    /// A sequence allows a single rule to generate multiple responses which can
         176  +
    /// be used to test retry behavior.
         177  +
    ///
         178  +
    /// # Examples
         179  +
    ///
         180  +
    /// With repetition using `times()`:
         181  +
    ///
         182  +
    /// ```rust,ignore
         183  +
    /// let rule = mock!(Client::get_object)
         184  +
    ///     .sequence()
         185  +
    ///     .http_status(503, None)
         186  +
    ///     .times(2)                                        // First two calls return 503
         187  +
    ///     .output(|| GetObjectOutput::builder().build())   // Third call succeeds
         188  +
    ///     .build();
         189  +
    /// ```
         190  +
    pub fn sequence(self) -> ResponseSequenceBuilder<I, O, E> {
         191  +
        ResponseSequenceBuilder::new(self.input_filter)
         192  +
    }
         193  +
         194  +
    /// Creates a rule that returns a modeled output.
         195  +
    pub fn then_output<F>(self, output_fn: F) -> Rule
         196  +
    where
         197  +
        F: Fn() -> O + Send + Sync + 'static,
         198  +
    {
         199  +
        self.sequence().output(output_fn).build()
         200  +
    }
         201  +
         202  +
    /// Creates a rule that returns a modeled error.
         203  +
    pub fn then_error<F>(self, error_fn: F) -> Rule
         204  +
    where
         205  +
        F: Fn() -> E + Send + Sync + 'static,
         206  +
    {
         207  +
        self.sequence().error(error_fn).build()
         208  +
    }
         209  +
         210  +
    /// Creates a rule that returns an HTTP response.
         211  +
    pub fn then_http_response<F>(self, response_fn: F) -> Rule
         212  +
    where
         213  +
        F: Fn() -> HttpResponse + Send + Sync + 'static,
         214  +
    {
         215  +
        self.sequence().http_response(response_fn).build()
         216  +
    }
         217  +
}
         218  +
         219  +
type SequenceGeneratorFn<O, E> = Arc<dyn Fn() -> MockResponse<O, E> + Send + Sync>;
         220  +
         221  +
/// A builder for creating response sequences
         222  +
pub struct ResponseSequenceBuilder<I, O, E> {
         223  +
    /// The response generators in the sequence
         224  +
    generators: Vec<SequenceGeneratorFn<O, E>>,
         225  +
         226  +
    /// Function that determines if this rule matches a request
         227  +
    input_filter: MatchFn,
         228  +
         229  +
    /// Marker for the input, output, and error types
         230  +
    _marker: std::marker::PhantomData<I>,
         231  +
}
         232  +
         233  +
impl<I, O, E> ResponseSequenceBuilder<I, O, E>
         234  +
where
         235  +
    I: fmt::Debug + Send + Sync + 'static,
         236  +
    O: fmt::Debug + Send + Sync + 'static,
         237  +
    E: fmt::Debug + Send + Sync + std::error::Error + 'static,
         238  +
{
         239  +
    /// Create a new response sequence builder
         240  +
    pub(crate) fn new(input_filter: MatchFn) -> Self {
         241  +
        Self {
         242  +
            generators: Vec::new(),
         243  +
            input_filter,
         244  +
            _marker: std::marker::PhantomData,
         245  +
        }
         246  +
    }
         247  +
         248  +
    /// Add a modeled output response to the sequence
         249  +
    ///
         250  +
    /// # Examples
         251  +
    ///
         252  +
    /// ```rust,ignore
         253  +
    /// let rule = mock!(Client::get_object)
         254  +
    ///     .sequence()
         255  +
    ///     .output(|| GetObjectOutput::builder().build())
         256  +
    ///     .build();
         257  +
    /// ```
         258  +
    pub fn output<F>(mut self, output_fn: F) -> Self
         259  +
    where
         260  +
        F: Fn() -> O + Send + Sync + 'static,
         261  +
    {
         262  +
        let generator = Arc::new(move || MockResponse::Output(output_fn()));
         263  +
        self.generators.push(generator);
         264  +
        self
         265  +
    }
         266  +
         267  +
    /// Add a modeled error response to the sequence
         268  +
    pub fn error<F>(mut self, error_fn: F) -> Self
         269  +
    where
         270  +
        F: Fn() -> E + Send + Sync + 'static,
         271  +
    {
         272  +
        let generator = Arc::new(move || MockResponse::Error(error_fn()));
         273  +
        self.generators.push(generator);
         274  +
        self
         275  +
    }
         276  +
         277  +
    /// Add an HTTP status code response to the sequence
         278  +
    pub fn http_status(mut self, status: u16, body: Option<String>) -> Self {
         279  +
        let status_code = StatusCode::try_from(status).unwrap();
         280  +
         281  +
        let generator: SequenceGeneratorFn<O, E> = match body {
         282  +
            Some(body) => Arc::new(move || {
         283  +
                MockResponse::Http(HttpResponse::new(status_code, SdkBody::from(body.clone())))
         284  +
            }),
         285  +
            None => Arc::new(move || {
         286  +
                MockResponse::Http(HttpResponse::new(status_code, SdkBody::empty()))
         287  +
            }),
         288  +
        };
         289  +
         290  +
        self.generators.push(generator);
         291  +
        self
         292  +
    }
         293  +
         294  +
    /// Add an HTTP response to the sequence
         295  +
    pub fn http_response<F>(mut self, response_fn: F) -> Self
         296  +
    where
         297  +
        F: Fn() -> HttpResponse + Send + Sync + 'static,
         298  +
    {
         299  +
        let generator = Arc::new(move || MockResponse::Http(response_fn()));
         300  +
        self.generators.push(generator);
         301  +
        self
         302  +
    }
         303  +
         304  +
    /// Repeat the last added response multiple times (total count)
         305  +
    pub fn times(mut self, count: usize) -> Self {
         306  +
        if count <= 1 {
         307  +
            return self;
         308  +
        }
         309  +
         310  +
        if let Some(last_generator) = self.generators.last().cloned() {
         311  +
            // Add count-1 more copies (we already have one)
         312  +
            for _ in 1..count {
         313  +
                self.generators.push(last_generator.clone());
         314  +
            }
         315  +
        }
         316  +
        self
         317  +
    }
         318  +
         319  +
    /// Build the rule with this response sequence
         320  +
    pub fn build(self) -> Rule {
         321  +
        let generators = self.generators;
         322  +
        let count = generators.len();
         323  +
         324  +
        Rule::new(
         325  +
            self.input_filter,
         326  +
            Arc::new(move |idx| {
         327  +
                if idx < count {
         328  +
                    Some(generators[idx]())
         329  +
                } else {
         330  +
                    None
         331  +
                }
         332  +
            }),
         333  +
            count,
         334  +
        )
         335  +
    }
         336  +
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-mocks/tests/macros.rs

@@ -0,1 +0,61 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
/// Basic test of using the mock_client macro from an "external" crate
           7  +
           8  +
mod fake_crate {
           9  +
    pub(crate) mod client {
          10  +
        use crate::fake_crate::config;
          11  +
          12  +
        pub(crate) struct Client {}
          13  +
        impl Client {
          14  +
            pub(crate) fn from_conf(_conf: config::Config) -> Self {
          15  +
                Self {}
          16  +
            }
          17  +
        }
          18  +
    }
          19  +
          20  +
    pub(crate) mod config {
          21  +
        use aws_smithy_runtime_api::client::http::SharedHttpClient;
          22  +
        use aws_smithy_runtime_api::client::interceptors::Intercept;
          23  +
          24  +
        pub(crate) struct Config {}
          25  +
        impl Config {
          26  +
            pub(crate) fn builder() -> Builder {
          27  +
                Builder {}
          28  +
            }
          29  +
        }
          30  +
        pub(crate) struct Builder {}
          31  +
        impl Builder {
          32  +
            pub fn build(self) -> Config {
          33  +
                Config {}
          34  +
            }
          35  +
            pub fn region(self, _region: crate::fake_crate::config::Region) -> Self {
          36  +
                Self {}
          37  +
            }
          38  +
            pub fn with_test_defaults(self) -> Self {
          39  +
                Self {}
          40  +
            }
          41  +
            pub fn http_client(self, _http_client: SharedHttpClient) -> Self {
          42  +
                Self {}
          43  +
            }
          44  +
          45  +
            pub fn interceptor(self, _interceptor: impl Intercept + 'static) -> Self {
          46  +
                self
          47  +
            }
          48  +
        }
          49  +
          50  +
        pub(crate) struct Region {}
          51  +
        impl Region {
          52  +
            pub fn from_static(_region: &'static str) -> Self {
          53  +
                Self {}
          54  +
            }
          55  +
        }
          56  +
    }
          57  +
}
          58  +
#[test]
          59  +
fn mock_client() {
          60  +
    aws_smithy_mocks::mock_client!(fake_crate, &[]);
          61  +
}

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

@@ -33,33 +93,93 @@
   53     53   
version = "1.7.4"
   54     54   
   55     55   
[dependencies.aws-smithy-types]
   56     56   
path = "../aws-smithy-types"
   57     57   
features = ["http-body-0-4-x"]
   58     58   
version = "1.3.0"
   59     59   
   60     60   
[dependencies.aws-smithy-http-client]
   61     61   
path = "../aws-smithy-http-client"
   62     62   
optional = true
   63         -
version = "1.0.1"
          63  +
version = "1.0.2"
   64     64   
   65     65   
[dependencies.http-02x]
   66     66   
package = "http"
   67     67   
version = "0.2.9"
   68     68   
   69     69   
[dependencies.http-1x]
   70     70   
package = "http"
   71     71   
version = "1"
   72     72   
   73     73   
[dependencies.http-body-04x]

tmp-codegen-diff/aws-sdk/sdk/cloudwatchlogs/Cargo.toml

@@ -75,75 +135,135 @@
   95     95   
version = "1.2.5"
   96     96   
   97     97   
[dev-dependencies.aws-smithy-eventstream]
   98     98   
path = "../aws-smithy-eventstream"
   99     99   
features = ["test-util"]
  100    100   
version = "0.60.8"
  101    101   
  102    102   
[dev-dependencies.aws-smithy-http-client]
  103    103   
path = "../aws-smithy-http-client"
  104    104   
features = ["test-util", "wire-mock"]
  105         -
version = "1.0.1"
         105  +
version = "1.0.2"
  106    106   
  107    107   
[dev-dependencies.aws-smithy-protocol-test]
  108    108   
path = "../aws-smithy-protocol-test"
  109    109   
version = "0.63.1"
  110    110   
  111    111   
[dev-dependencies.aws-smithy-runtime]
  112    112   
path = "../aws-smithy-runtime"
  113    113   
features = ["test-util"]
  114    114   
version = "1.8.2"
  115    115   

tmp-codegen-diff/aws-sdk/sdk/codecatalyst/Cargo.toml

@@ -64,64 +124,124 @@
   84     84   
version = "1.5.7"
   85     85   
   86     86   
[dev-dependencies.aws-smithy-async]
   87     87   
path = "../aws-smithy-async"
   88     88   
features = ["test-util"]
   89     89   
version = "1.2.5"
   90     90   
   91     91   
[dev-dependencies.aws-smithy-http-client]
   92     92   
path = "../aws-smithy-http-client"
   93     93   
features = ["test-util", "wire-mock"]
   94         -
version = "1.0.1"
          94  +
version = "1.0.2"
   95     95   
   96     96   
[dev-dependencies.aws-smithy-protocol-test]
   97     97   
path = "../aws-smithy-protocol-test"
   98     98   
version = "0.63.1"
   99     99   
  100    100   
[dev-dependencies.aws-smithy-runtime]
  101    101   
path = "../aws-smithy-runtime"
  102    102   
features = ["test-util"]
  103    103   
version = "1.8.2"
  104    104   

tmp-codegen-diff/aws-sdk/sdk/dynamodb/Cargo.toml

@@ -67,67 +127,127 @@
   87     87   
version = "1.5.7"
   88     88   
   89     89   
[dev-dependencies.aws-smithy-async]
   90     90   
path = "../aws-smithy-async"
   91     91   
features = ["test-util"]
   92     92   
version = "1.2.5"
   93     93   
   94     94   
[dev-dependencies.aws-smithy-http-client]
   95     95   
path = "../aws-smithy-http-client"
   96     96   
features = ["test-util", "wire-mock"]
   97         -
version = "1.0.1"
          97  +
version = "1.0.2"
   98     98   
   99     99   
[dev-dependencies.aws-smithy-protocol-test]
  100    100   
path = "../aws-smithy-protocol-test"
  101    101   
version = "0.63.1"
  102    102   
  103    103   
[dev-dependencies.aws-smithy-runtime]
  104    104   
path = "../aws-smithy-runtime"
  105    105   
features = ["test-util"]
  106    106   
version = "1.8.2"
  107    107   

tmp-codegen-diff/aws-sdk/sdk/ec2/Cargo.toml

@@ -69,69 +129,129 @@
   89     89   
version = "1.5.7"
   90     90   
   91     91   
[dev-dependencies.aws-smithy-async]
   92     92   
path = "../aws-smithy-async"
   93     93   
features = ["test-util"]
   94     94   
version = "1.2.5"
   95     95   
   96     96   
[dev-dependencies.aws-smithy-http-client]
   97     97   
path = "../aws-smithy-http-client"
   98     98   
features = ["test-util", "wire-mock"]
   99         -
version = "1.0.1"
          99  +
version = "1.0.2"
  100    100   
  101    101   
[dev-dependencies.aws-smithy-protocol-test]
  102    102   
path = "../aws-smithy-protocol-test"
  103    103   
version = "0.63.1"
  104    104   
  105    105   
[dev-dependencies.aws-smithy-runtime]
  106    106   
path = "../aws-smithy-runtime"
  107    107   
features = ["test-util"]
  108    108   
version = "1.8.2"
  109    109   

tmp-codegen-diff/aws-sdk/sdk/glacier/Cargo.toml

@@ -78,78 +138,138 @@
   98     98   
version = "1.5.7"
   99     99   
  100    100   
[dev-dependencies.aws-smithy-async]
  101    101   
path = "../aws-smithy-async"
  102    102   
features = ["test-util"]
  103    103   
version = "1.2.5"
  104    104   
  105    105   
[dev-dependencies.aws-smithy-http-client]
  106    106   
path = "../aws-smithy-http-client"
  107    107   
features = ["test-util", "wire-mock"]
  108         -
version = "1.0.1"
         108  +
version = "1.0.2"
  109    109   
  110    110   
[dev-dependencies.aws-smithy-protocol-test]
  111    111   
path = "../aws-smithy-protocol-test"
  112    112   
version = "0.63.1"
  113    113   
  114    114   
[dev-dependencies.aws-smithy-runtime]
  115    115   
path = "../aws-smithy-runtime"
  116    116   
features = ["test-util"]
  117    117   
version = "1.8.2"
  118    118   

tmp-codegen-diff/aws-sdk/sdk/iam/Cargo.toml

@@ -69,69 +129,129 @@
   89     89   
version = "1.5.7"
   90     90   
   91     91   
[dev-dependencies.aws-smithy-async]
   92     92   
path = "../aws-smithy-async"
   93     93   
features = ["test-util"]
   94     94   
version = "1.2.5"
   95     95   
   96     96   
[dev-dependencies.aws-smithy-http-client]
   97     97   
path = "../aws-smithy-http-client"
   98     98   
features = ["test-util", "wire-mock"]
   99         -
version = "1.0.1"
          99  +
version = "1.0.2"
  100    100   
  101    101   
[dev-dependencies.aws-smithy-protocol-test]
  102    102   
path = "../aws-smithy-protocol-test"
  103    103   
version = "0.63.1"
  104    104   
  105    105   
[dev-dependencies.aws-smithy-runtime]
  106    106   
path = "../aws-smithy-runtime"
  107    107   
features = ["test-util"]
  108    108   
version = "1.8.2"
  109    109   

tmp-codegen-diff/aws-sdk/sdk/kms/Cargo.toml

@@ -64,64 +124,124 @@
   84     84   
version = "1.5.7"
   85     85   
   86     86   
[dev-dependencies.aws-smithy-async]
   87     87   
path = "../aws-smithy-async"
   88     88   
features = ["test-util"]
   89     89   
version = "1.2.5"
   90     90   
   91     91   
[dev-dependencies.aws-smithy-http-client]
   92     92   
path = "../aws-smithy-http-client"
   93     93   
features = ["test-util", "wire-mock"]
   94         -
version = "1.0.1"
          94  +
version = "1.0.2"
   95     95   
   96     96   
[dev-dependencies.aws-smithy-protocol-test]
   97     97   
path = "../aws-smithy-protocol-test"
   98     98   
version = "0.63.1"
   99     99   
  100    100   
[dev-dependencies.aws-smithy-runtime]
  101    101   
path = "../aws-smithy-runtime"
  102    102   
features = ["test-util"]
  103    103   
version = "1.8.2"
  104    104   

tmp-codegen-diff/aws-sdk/sdk/lambda/Cargo.toml

@@ -75,75 +135,135 @@
   95     95   
version = "1.2.5"
   96     96   
   97     97   
[dev-dependencies.aws-smithy-eventstream]
   98     98   
path = "../aws-smithy-eventstream"
   99     99   
features = ["test-util"]
  100    100   
version = "0.60.8"
  101    101   
  102    102   
[dev-dependencies.aws-smithy-http-client]
  103    103   
path = "../aws-smithy-http-client"
  104    104   
features = ["test-util", "wire-mock"]
  105         -
version = "1.0.1"
         105  +
version = "1.0.2"
  106    106   
  107    107   
[dev-dependencies.aws-smithy-protocol-test]
  108    108   
path = "../aws-smithy-protocol-test"
  109    109   
version = "0.63.1"
  110    110   
  111    111   
[dev-dependencies.aws-smithy-runtime]
  112    112   
path = "../aws-smithy-runtime"
  113    113   
features = ["test-util"]
  114    114   
version = "1.8.2"
  115    115   

tmp-codegen-diff/aws-sdk/sdk/polly/Cargo.toml

@@ -77,77 +137,137 @@
   97     97   
version = "1.5.7"
   98     98   
   99     99   
[dev-dependencies.aws-smithy-async]
  100    100   
path = "../aws-smithy-async"
  101    101   
features = ["test-util"]
  102    102   
version = "1.2.5"
  103    103   
  104    104   
[dev-dependencies.aws-smithy-http-client]
  105    105   
path = "../aws-smithy-http-client"
  106    106   
features = ["test-util", "wire-mock"]
  107         -
version = "1.0.1"
         107  +
version = "1.0.2"
  108    108   
  109    109   
[dev-dependencies.aws-smithy-protocol-test]
  110    110   
path = "../aws-smithy-protocol-test"
  111    111   
version = "0.63.1"
  112    112   
  113    113   
[dev-dependencies.aws-smithy-runtime]
  114    114   
path = "../aws-smithy-runtime"
  115    115   
features = ["test-util"]
  116    116   
version = "1.8.2"
  117    117   

tmp-codegen-diff/aws-sdk/sdk/qldbsession/Cargo.toml

@@ -64,64 +124,124 @@
   84     84   
version = "1.5.7"
   85     85   
   86     86   
[dev-dependencies.aws-smithy-async]
   87     87   
path = "../aws-smithy-async"
   88     88   
features = ["test-util"]
   89     89   
version = "1.2.5"
   90     90   
   91     91   
[dev-dependencies.aws-smithy-http-client]
   92     92   
path = "../aws-smithy-http-client"
   93     93   
features = ["test-util", "wire-mock"]
   94         -
version = "1.0.1"
          94  +
version = "1.0.2"
   95     95   
   96     96   
[dev-dependencies.aws-smithy-protocol-test]
   97     97   
path = "../aws-smithy-protocol-test"
   98     98   
version = "0.63.1"
   99     99   
  100    100   
[dev-dependencies.aws-smithy-runtime]
  101    101   
path = "../aws-smithy-runtime"
  102    102   
features = ["test-util"]
  103    103   
version = "1.8.2"
  104    104   

tmp-codegen-diff/aws-sdk/sdk/route53/Cargo.toml

@@ -55,55 +105,105 @@
   75     75   
version = "1.6.1"
   76     76   
   77     77   
[dev-dependencies.aws-credential-types]
   78     78   
path = "../aws-credential-types"
   79     79   
features = ["test-util"]
   80     80   
version = "1.2.2"
   81     81   
   82     82   
[dev-dependencies.aws-smithy-http-client]
   83     83   
path = "../aws-smithy-http-client"
   84     84   
features = ["test-util"]
   85         -
version = "1.0.1"
          85  +
version = "1.0.2"
   86     86   
   87     87   
[dev-dependencies.pretty_assertions]
   88     88   
version = "1.3.0"
   89     89   
   90     90   
[dev-dependencies.tokio]
   91     91   
version = "1.23.1"
   92     92   
features = ["macros", "test-util", "rt-multi-thread"]
   93     93   
   94     94   
[dev-dependencies.tracing-test]
   95     95   
version = "0.2.5"

tmp-codegen-diff/aws-sdk/sdk/s3/Cargo.toml

@@ -121,121 +181,181 @@
  141    141   
version = "1.2.5"
  142    142   
  143    143   
[dev-dependencies.aws-smithy-eventstream]
  144    144   
path = "../aws-smithy-eventstream"
  145    145   
features = ["test-util"]
  146    146   
version = "0.60.8"
  147    147   
  148    148   
[dev-dependencies.aws-smithy-http-client]
  149    149   
path = "../aws-smithy-http-client"
  150    150   
features = ["test-util", "wire-mock", "rustls-ring"]
  151         -
version = "1.0.1"
         151  +
version = "1.0.2"
  152    152   
  153    153   
[dev-dependencies.aws-smithy-protocol-test]
  154    154   
path = "../aws-smithy-protocol-test"
  155    155   
version = "0.63.1"
  156    156   
  157    157   
[dev-dependencies.aws-smithy-runtime]
  158    158   
path = "../aws-smithy-runtime"
  159    159   
features = ["test-util"]
  160    160   
version = "1.8.2"
  161    161