1use crate::box_error::BoxError;
22use crate::client::runtime_components::{
23 RuntimeComponentsBuilder, EMPTY_RUNTIME_COMPONENTS_BUILDER,
24};
25use crate::impl_shared_conversions;
26use crate::shared::IntoShared;
27use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer};
28use std::borrow::Cow;
29use std::fmt::Debug;
30use std::sync::Arc;
31
32const DEFAULT_ORDER: Order = Order::Overrides;
33
34#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
42pub enum Order {
43 Defaults,
47
48 Overrides,
52
53 NestedComponents,
57}
58
59pub trait RuntimePlugin: Debug + Send + Sync {
65 fn order(&self) -> Order {
77 DEFAULT_ORDER
78 }
79
80 fn config(&self) -> Option<FrozenLayer> {
86 None
87 }
88
89 fn runtime_components(
103 &self,
104 current_components: &RuntimeComponentsBuilder,
105 ) -> Cow<'_, RuntimeComponentsBuilder> {
106 let _ = current_components;
107 Cow::Borrowed(&EMPTY_RUNTIME_COMPONENTS_BUILDER)
108 }
109}
110
111#[derive(Debug, Clone)]
115pub struct SharedRuntimePlugin(Arc<dyn RuntimePlugin>);
116
117impl SharedRuntimePlugin {
118 pub fn new(plugin: impl RuntimePlugin + 'static) -> Self {
120 Self(Arc::new(plugin))
121 }
122}
123
124impl RuntimePlugin for SharedRuntimePlugin {
125 fn order(&self) -> Order {
126 self.0.order()
127 }
128
129 fn config(&self) -> Option<FrozenLayer> {
130 self.0.config()
131 }
132
133 fn runtime_components(
134 &self,
135 current_components: &RuntimeComponentsBuilder,
136 ) -> Cow<'_, RuntimeComponentsBuilder> {
137 self.0.runtime_components(current_components)
138 }
139}
140
141impl_shared_conversions!(convert SharedRuntimePlugin from RuntimePlugin using SharedRuntimePlugin::new);
142
143#[derive(Default, Debug)]
145pub struct StaticRuntimePlugin {
146 config: Option<FrozenLayer>,
147 runtime_components: Option<RuntimeComponentsBuilder>,
148 order: Option<Order>,
149}
150
151impl StaticRuntimePlugin {
152 pub fn new() -> Self {
154 Default::default()
155 }
156
157 pub fn with_config(mut self, config: FrozenLayer) -> Self {
159 self.config = Some(config);
160 self
161 }
162
163 pub fn with_runtime_components(mut self, runtime_components: RuntimeComponentsBuilder) -> Self {
165 self.runtime_components = Some(runtime_components);
166 self
167 }
168
169 pub fn with_order(mut self, order: Order) -> Self {
171 self.order = Some(order);
172 self
173 }
174}
175
176impl RuntimePlugin for StaticRuntimePlugin {
177 fn order(&self) -> Order {
178 self.order.unwrap_or(DEFAULT_ORDER)
179 }
180
181 fn config(&self) -> Option<FrozenLayer> {
182 self.config.clone()
183 }
184
185 fn runtime_components(
186 &self,
187 _current_components: &RuntimeComponentsBuilder,
188 ) -> Cow<'_, RuntimeComponentsBuilder> {
189 self.runtime_components
190 .as_ref()
191 .map(Cow::Borrowed)
192 .unwrap_or_else(|| Cow::Borrowed(&EMPTY_RUNTIME_COMPONENTS_BUILDER))
193 }
194}
195
196macro_rules! insert_plugin {
197 ($vec:expr, $plugin:expr) => {{
198 let plugin = $plugin;
200 let mut insert_index = 0;
201 let order = plugin.order();
202 for (index, other_plugin) in $vec.iter().enumerate() {
203 let other_order = other_plugin.order();
204 if other_order <= order {
205 insert_index = index + 1;
206 } else if other_order > order {
207 break;
208 }
209 }
210 $vec.insert(insert_index, plugin);
211 }};
212}
213
214macro_rules! apply_plugins {
215 ($name:ident, $plugins:expr, $cfg:ident) => {{
216 tracing::trace!(concat!("applying ", stringify!($name), " runtime plugins"));
217 let mut merged =
218 RuntimeComponentsBuilder::new(concat!("apply_", stringify!($name), "_configuration"));
219 for plugin in &$plugins {
220 if let Some(layer) = plugin.config() {
221 $cfg.push_shared_layer(layer);
222 }
223 let next = plugin.runtime_components(&merged);
224 merged = merged.merge_from(&next);
225 }
226 Ok(merged)
227 }};
228}
229
230#[derive(Default, Clone, Debug)]
232pub struct RuntimePlugins {
233 client_plugins: Vec<SharedRuntimePlugin>,
234 operation_plugins: Vec<SharedRuntimePlugin>,
235}
236
237impl RuntimePlugins {
238 pub fn new() -> Self {
240 Default::default()
241 }
242
243 pub fn with_client_plugins(
245 mut self,
246 plugins: impl IntoIterator<Item = SharedRuntimePlugin>,
247 ) -> Self {
248 for plugin in plugins.into_iter() {
249 self = self.with_client_plugin(plugin);
250 }
251 self
252 }
253
254 pub fn with_client_plugin(mut self, plugin: impl RuntimePlugin + 'static) -> Self {
256 insert_plugin!(
257 self.client_plugins,
258 IntoShared::<SharedRuntimePlugin>::into_shared(plugin)
259 );
260 self
261 }
262
263 pub fn with_operation_plugins(
265 mut self,
266 plugins: impl IntoIterator<Item = SharedRuntimePlugin>,
267 ) -> Self {
268 for plugin in plugins.into_iter() {
269 self = self.with_operation_plugin(plugin);
270 }
271 self
272 }
273
274 pub fn with_operation_plugin(mut self, plugin: impl RuntimePlugin + 'static) -> Self {
276 insert_plugin!(
277 self.operation_plugins,
278 IntoShared::<SharedRuntimePlugin>::into_shared(plugin)
279 );
280 self
281 }
282
283 pub fn apply_client_configuration(
285 &self,
286 cfg: &mut ConfigBag,
287 ) -> Result<RuntimeComponentsBuilder, BoxError> {
288 apply_plugins!(client, self.client_plugins, cfg)
289 }
290
291 pub fn apply_operation_configuration(
293 &self,
294 cfg: &mut ConfigBag,
295 ) -> Result<RuntimeComponentsBuilder, BoxError> {
296 apply_plugins!(operation, self.operation_plugins, cfg)
297 }
298}
299
300#[cfg(all(test, feature = "test-util", feature = "http-02x"))]
301mod tests {
302 use super::{RuntimePlugin, RuntimePlugins};
303 use crate::client::http::{
304 http_client_fn, HttpClient, HttpConnector, HttpConnectorFuture, SharedHttpConnector,
305 };
306 use crate::client::orchestrator::HttpRequest;
307 use crate::client::runtime_components::RuntimeComponentsBuilder;
308 use crate::client::runtime_plugin::{Order, SharedRuntimePlugin};
309 use crate::shared::IntoShared;
310 use aws_smithy_types::body::SdkBody;
311 use aws_smithy_types::config_bag::ConfigBag;
312 use http_02x::HeaderValue;
313 use std::borrow::Cow;
314
315 #[derive(Debug)]
316 struct SomeStruct;
317
318 impl RuntimePlugin for SomeStruct {}
319
320 #[test]
321 fn can_add_runtime_plugin_implementors_to_runtime_plugins() {
322 RuntimePlugins::new().with_client_plugin(SomeStruct);
323 }
324
325 #[test]
326 fn runtime_plugins_are_send_sync() {
327 fn assert_send_sync<T: Send + Sync>() {}
328 assert_send_sync::<RuntimePlugins>();
329 }
330
331 #[test]
332 fn insert_plugin() {
333 #[derive(Debug)]
334 struct RP(isize, Order);
335 impl RuntimePlugin for RP {
336 fn order(&self) -> Order {
337 self.1
338 }
339 }
340
341 fn insert_plugin(vec: &mut Vec<RP>, plugin: RP) {
342 insert_plugin!(vec, plugin);
343 }
344
345 let mut vec = Vec::new();
346 insert_plugin(&mut vec, RP(5, Order::NestedComponents));
347 insert_plugin(&mut vec, RP(3, Order::Overrides));
348 insert_plugin(&mut vec, RP(1, Order::Defaults));
349 insert_plugin(&mut vec, RP(6, Order::NestedComponents));
350 insert_plugin(&mut vec, RP(2, Order::Defaults));
351 insert_plugin(&mut vec, RP(4, Order::Overrides));
352 insert_plugin(&mut vec, RP(7, Order::NestedComponents));
353 assert_eq!(
354 vec![1, 2, 3, 4, 5, 6, 7],
355 vec.iter().map(|rp| rp.0).collect::<Vec<isize>>()
356 );
357
358 let mut vec = Vec::new();
359 insert_plugin(&mut vec, RP(3, Order::Overrides));
360 insert_plugin(&mut vec, RP(4, Order::Overrides));
361 insert_plugin(&mut vec, RP(5, Order::NestedComponents));
362 insert_plugin(&mut vec, RP(6, Order::NestedComponents));
363 insert_plugin(&mut vec, RP(7, Order::NestedComponents));
364 insert_plugin(&mut vec, RP(1, Order::Defaults));
365 insert_plugin(&mut vec, RP(2, Order::Defaults));
366 assert_eq!(
367 vec![1, 2, 3, 4, 5, 6, 7],
368 vec.iter().map(|rp| rp.0).collect::<Vec<isize>>()
369 );
370
371 let mut vec = Vec::new();
372 insert_plugin(&mut vec, RP(1, Order::Defaults));
373 insert_plugin(&mut vec, RP(2, Order::Defaults));
374 insert_plugin(&mut vec, RP(3, Order::Overrides));
375 insert_plugin(&mut vec, RP(4, Order::Overrides));
376 insert_plugin(&mut vec, RP(5, Order::NestedComponents));
377 insert_plugin(&mut vec, RP(6, Order::NestedComponents));
378 assert_eq!(
379 vec![1, 2, 3, 4, 5, 6],
380 vec.iter().map(|rp| rp.0).collect::<Vec<isize>>()
381 );
382 }
383
384 #[tokio::test]
385 async fn components_can_wrap_components() {
386 #[derive(Debug)]
388 struct Connector1;
389 impl HttpConnector for Connector1 {
390 fn call(&self, _: HttpRequest) -> HttpConnectorFuture {
391 HttpConnectorFuture::new(async {
392 Ok(http_02x::Response::builder()
393 .status(200)
394 .header("rp1", "1")
395 .body(SdkBody::empty())
396 .unwrap()
397 .try_into()
398 .unwrap())
399 })
400 }
401 }
402
403 #[derive(Debug)]
405 struct Connector2(SharedHttpConnector);
406 impl HttpConnector for Connector2 {
407 fn call(&self, request: HttpRequest) -> HttpConnectorFuture {
408 let inner = self.0.clone();
409 HttpConnectorFuture::new(async move {
410 let mut resp = inner.call(request).await.unwrap();
411 resp.headers_mut()
412 .append("rp2", HeaderValue::from_static("1"));
413 Ok(resp)
414 })
415 }
416 }
417
418 #[derive(Debug)]
420 struct Plugin1;
421 impl RuntimePlugin for Plugin1 {
422 fn order(&self) -> Order {
423 Order::Overrides
424 }
425
426 fn runtime_components(
427 &self,
428 _: &RuntimeComponentsBuilder,
429 ) -> Cow<'_, RuntimeComponentsBuilder> {
430 Cow::Owned(
431 RuntimeComponentsBuilder::new("Plugin1")
432 .with_http_client(Some(http_client_fn(|_, _| Connector1.into_shared()))),
433 )
434 }
435 }
436
437 #[derive(Debug)]
439 struct Plugin2;
440 impl RuntimePlugin for Plugin2 {
441 fn order(&self) -> Order {
442 Order::NestedComponents
443 }
444
445 fn runtime_components(
446 &self,
447 current_components: &RuntimeComponentsBuilder,
448 ) -> Cow<'_, RuntimeComponentsBuilder> {
449 let current = current_components.http_client().unwrap();
450 Cow::Owned(
451 RuntimeComponentsBuilder::new("Plugin2").with_http_client(Some(
452 http_client_fn(move |settings, components| {
453 let connector = current.http_connector(settings, components);
454 SharedHttpConnector::new(Connector2(connector))
455 }),
456 )),
457 )
458 }
459 }
460
461 let plugins = RuntimePlugins::new()
463 .with_client_plugin(Plugin2)
465 .with_client_plugin(Plugin1);
466 let mut cfg = ConfigBag::base();
467 let components = plugins.apply_client_configuration(&mut cfg).unwrap();
468 let fake_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
469
470 let resp = components
472 .http_client()
473 .unwrap()
474 .http_connector(&Default::default(), &fake_components)
475 .call(HttpRequest::empty())
476 .await
477 .unwrap();
478 dbg!(&resp);
479
480 assert_eq!("1", resp.headers().get("rp1").unwrap());
483 assert_eq!("1", resp.headers().get("rp2").unwrap());
484 }
485
486 #[test]
487 fn shared_runtime_plugin_new_specialization() {
488 #[derive(Debug)]
489 struct RP;
490 impl RuntimePlugin for RP {}
491
492 use crate::shared::IntoShared;
493 let shared1 = SharedRuntimePlugin::new(RP);
494 let shared2: SharedRuntimePlugin = shared1.clone().into_shared();
495 assert_eq!(
496 "SharedRuntimePlugin(RP)",
497 format!("{shared1:?}"),
498 "precondition: RP shows up in the debug format"
499 );
500 assert_eq!(
501 format!("{shared1:?}"),
502 format!("{shared2:?}"),
503 "it should not nest the shared runtime plugins"
504 );
505 }
506}