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