aws_smithy_mocks/
lib.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6#![doc = include_str!("../README.md")]
7/* Automatically managed default lints */
8#![cfg_attr(docsrs, feature(doc_auto_cfg))]
9/* End of automatically managed default lints */
10#![warn(
11    missing_docs,
12    rustdoc::missing_crate_level_docs,
13    unreachable_pub,
14    rust_2018_idioms
15)]
16
17mod interceptor;
18mod rule;
19
20pub use interceptor::{create_mock_http_client, MockResponseInterceptor};
21pub(crate) use rule::MockResponse;
22pub use rule::{Rule, RuleBuilder, RuleMode};
23
24// why do we need a macro for this?
25// We want customers to be able to provide an ergonomic way to say the method they're looking for,
26// `Client::list_buckets`, e.g. But there isn't enough information on that type to recover everything.
27// This macro commits a small amount of crimes to recover that type information so we can construct
28// a rule that can intercept these operations.
29
30/// `mock!` macro that produces a [`RuleBuilder`] from a client invocation
31///
32/// See the `examples` folder of this crate for fully worked examples.
33///
34/// # Examples
35///
36/// **Mock and return a success response**:
37///
38/// ```rust,ignore
39/// use aws_sdk_s3::operation::get_object::GetObjectOutput;
40/// use aws_sdk_s3::Client;
41/// use aws_smithy_types::byte_stream::ByteStream;
42/// use aws_smithy_mocks::mock;
43/// let get_object_happy_path = mock!(Client::get_object)
44///   .match_requests(|req|req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
45///   .then_output(||GetObjectOutput::builder().body(ByteStream::from_static(b"12345-abcde")).build());
46/// ```
47///
48/// **Mock and return an error**:
49/// ```rust,ignore
50/// use aws_sdk_s3::operation::get_object::GetObjectError;
51/// use aws_sdk_s3::types::error::NoSuchKey;
52/// use aws_sdk_s3::Client;
53/// use aws_smithy_mocks::mock;
54/// let get_object_error_path = mock!(Client::get_object)
55///   .then_error(||GetObjectError::NoSuchKey(NoSuchKey::builder().build()));
56/// ```
57///
58#[macro_export]
59macro_rules! mock {
60    ($operation: expr) => {
61        #[allow(unreachable_code)]
62        {
63            $crate::RuleBuilder::new_from_mock(
64                // We don't actually want to run this code, so we put it in a closure. The closure
65                // has the types we want which makes this whole thing type-safe (and the IDE can even
66                // figure out the right input/output types in inference!)
67                // The code generated here is:
68                // `Client::list_buckets(todo!())`
69                || $operation(todo!()).as_input().clone().build().unwrap(),
70                || $operation(todo!()).send(),
71            )
72        }
73    };
74}
75
76// This could be obviated by a reasonable trait, since you can express it with SdkConfig if clients implement From<&SdkConfig>.
77
78/// `mock_client!` macro produces a Client configured with a number of Rules and appropriate test default configuration.
79///
80/// # Examples
81///
82/// **Create a client that uses a mock failure and then a success**:
83///
84/// ```rust,ignore
85/// use aws_sdk_s3::operation::get_object::{GetObjectOutput, GetObjectError};
86/// use aws_sdk_s3::types::error::NoSuchKey;
87/// use aws_sdk_s3::Client;
88/// use aws_smithy_types::byte_stream::ByteStream;
89/// use aws_smithy_mocks::{mock_client, mock, RuleMode};
90/// let get_object_error_path = mock!(Client::get_object)
91///   .then_error(||GetObjectError::NoSuchKey(NoSuchKey::builder().build()))
92///   .build();
93/// let get_object_happy_path = mock!(Client::get_object)
94///   .match_requests(|req|req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
95///   .then_output(||GetObjectOutput::builder().body(ByteStream::from_static(b"12345-abcde")).build())
96///   .build();
97/// let client = mock_client!(aws_sdk_s3, RuleMode::Sequential, &[&get_object_error_path, &get_object_happy_path]);
98///
99///
100/// **Create a client but customize a specific setting**:
101/// rust,ignore
102/// use aws_sdk_s3::operation::get_object::GetObjectOutput;
103/// use aws_sdk_s3::Client;
104/// use aws_smithy_types::byte_stream::ByteStream;
105/// use aws_smithy_mocks::{mock_client, mock, RuleMode};
106/// let get_object_happy_path = mock!(Client::get_object)
107///   .match_requests(|req|req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
108///   .then_output(||GetObjectOutput::builder().body(ByteStream::from_static(b"12345-abcde")).build())
109///   .build();
110/// let client = mock_client!(
111///     aws_sdk_s3,
112///     RuleMode::Sequential,
113///     &[&get_object_happy_path],
114///     // Perhaps you need to force path style
115///     |client_builder|client_builder.force_path_style(true)
116/// );
117/// ```
118///
119#[macro_export]
120macro_rules! mock_client {
121    ($aws_crate: ident, $rules: expr) => {
122        $crate::mock_client!($aws_crate, $crate::RuleMode::Sequential, $rules)
123    };
124    ($aws_crate: ident, $rule_mode: expr, $rules: expr) => {{
125        $crate::mock_client!($aws_crate, $rule_mode, $rules, |conf| conf)
126    }};
127    ($aws_crate: ident, $rule_mode: expr, $rules: expr, $additional_configuration: expr) => {{
128        let mut mock_response_interceptor =
129            $crate::MockResponseInterceptor::new().rule_mode($rule_mode);
130        for rule in $rules {
131            mock_response_interceptor = mock_response_interceptor.with_rule(rule)
132        }
133
134        // Create a mock HTTP client
135        let mock_http_client = $crate::create_mock_http_client();
136
137        // Allow callers to avoid explicitly specifying the type
138        fn coerce<T: Fn($aws_crate::config::Builder) -> $aws_crate::config::Builder>(f: T) -> T {
139            f
140        }
141
142        $aws_crate::client::Client::from_conf(
143            coerce($additional_configuration)(
144                $aws_crate::config::Config::builder()
145                    .with_test_defaults()
146                    .region($aws_crate::config::Region::from_static("us-east-1"))
147                    .http_client(mock_http_client)
148                    .interceptor(mock_response_interceptor),
149            )
150            .build(),
151        )
152    }};
153}