aws_smithy_runtime_api/client/
runtime_plugin.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Runtime plugin type definitions.
7//!
8//! Runtime plugins are used to extend the runtime with custom behavior.
9//! This can include:
10//! - Registering interceptors
11//! - Registering auth schemes
12//! - Adding entries to the [`ConfigBag`] for orchestration
13//! - Setting runtime components
14//!
15//! Runtime plugins are divided into service/operation "levels", with service runtime plugins
16//! executing before operation runtime plugins. Runtime plugins configured in a service
17//! config will always be at the service level, while runtime plugins added during
18//! operation customization will be at the operation level. Custom runtime plugins will
19//! always run after the default runtime plugins within their level.
20
21use 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/// Runtime plugin ordering.
35///
36/// There are two runtime plugin "levels" that run in the following order:
37/// 1. Service runtime plugins - runtime plugins that pertain to the entire service.
38/// 2. Operation runtime plugins - runtime plugins relevant only to a single operation.
39///
40/// This enum is used to determine runtime plugin order within those levels.
41#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
42pub enum Order {
43    /// Runtime plugins with `Defaults` order are executed first within their level.
44    ///
45    /// Runtime plugins with this order should only be used for registering default components and config.
46    Defaults,
47
48    /// Runtime plugins with `Overrides` order are executed after `Defaults` within their level.
49    ///
50    /// This is the default order.
51    Overrides,
52
53    /// Runtime plugins with `NestedComponents` order are executed after `Overrides` within their level.
54    ///
55    /// This level is intended to be used for wrapping components configured in previous runtime plugins.
56    NestedComponents,
57}
58
59/// Runtime plugin trait
60///
61/// A `RuntimePlugin` is the unit of configuration for augmenting the client with new behavior.
62///
63/// Runtime plugins can register interceptors, set runtime components, and modify configuration.
64pub trait RuntimePlugin: Debug + Send + Sync {
65    /// Runtime plugin ordering.
66    ///
67    /// There are two runtime plugin "levels" that run in the following order:
68    /// 1. Service runtime plugins - runtime plugins that pertain to the entire service.
69    /// 2. Operation runtime plugins - runtime plugins relevant only to a single operation.
70    ///
71    /// This function is used to determine runtime plugin order within those levels. So
72    /// regardless of this `Order` value, service runtime plugins will still always execute before
73    /// operation runtime plugins. However, [`Defaults`](Order::Defaults)
74    /// service runtime plugins will run before [`Overrides`](Order::Overrides)
75    /// service runtime plugins.
76    fn order(&self) -> Order {
77        DEFAULT_ORDER
78    }
79
80    /// Optionally returns additional config that should be added to the [`ConfigBag`].
81    ///
82    /// As a best practice, a frozen layer should be stored on the runtime plugin instance as
83    /// a member, and then cloned upon return since that clone is cheap. Constructing a new
84    /// [`Layer`](aws_smithy_types::config_bag::Layer) and freezing it will require a lot of allocations.
85    fn config(&self) -> Option<FrozenLayer> {
86        None
87    }
88
89    /// Returns a [`RuntimeComponentsBuilder`] to incorporate into the final runtime components.
90    ///
91    /// The order of runtime plugins determines which runtime components "win". Components set by later runtime plugins will
92    /// override those set by earlier runtime plugins.
93    ///
94    /// If no runtime component changes are desired, just return an empty builder.
95    ///
96    /// This method returns a [`Cow`] for flexibility. Some implementers may want to store the components builder
97    /// as a member and return a reference to it, while others may need to create the builder every call. If possible,
98    /// returning a reference is preferred for performance.
99    ///
100    /// Components configured by previous runtime plugins are in the `current_components` argument, and can be used
101    /// to create nested/wrapped components, such as a connector calling into an inner (customer provided) connector.
102    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/// Shared runtime plugin
112///
113/// Allows for multiple places to share ownership of one runtime plugin.
114#[derive(Debug, Clone)]
115pub struct SharedRuntimePlugin(Arc<dyn RuntimePlugin>);
116
117impl SharedRuntimePlugin {
118    /// Creates a new [`SharedRuntimePlugin`].
119    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/// Runtime plugin that simply returns the config and components given at construction time.
144#[derive(Default, Debug)]
145pub struct StaticRuntimePlugin {
146    config: Option<FrozenLayer>,
147    runtime_components: Option<RuntimeComponentsBuilder>,
148    order: Option<Order>,
149}
150
151impl StaticRuntimePlugin {
152    /// Returns a new [`StaticRuntimePlugin`].
153    pub fn new() -> Self {
154        Default::default()
155    }
156
157    /// Changes the config.
158    pub fn with_config(mut self, config: FrozenLayer) -> Self {
159        self.config = Some(config);
160        self
161    }
162
163    /// Changes the runtime components.
164    pub fn with_runtime_components(mut self, runtime_components: RuntimeComponentsBuilder) -> Self {
165        self.runtime_components = Some(runtime_components);
166        self
167    }
168
169    /// Changes the order of this runtime plugin.
170    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        // Insert the plugin in the correct order
199        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/// Used internally in the orchestrator implementation and in the generated code. Not intended to be used elsewhere.
231#[derive(Default, Clone, Debug)]
232pub struct RuntimePlugins {
233    client_plugins: Vec<SharedRuntimePlugin>,
234    operation_plugins: Vec<SharedRuntimePlugin>,
235}
236
237impl RuntimePlugins {
238    /// Create a new empty set of runtime plugins.
239    pub fn new() -> Self {
240        Default::default()
241    }
242
243    /// Add several client-level runtime plugins from an iterator.
244    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    /// Adds a client-level runtime plugin.
255    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    /// Add several operation-level runtime plugins from an iterator.
264    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    /// Adds an operation-level runtime plugin.
275    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    /// Apply the client-level runtime plugins' config to the given config bag.
284    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    /// Apply the operation-level runtime plugins' config to the given config bag.
292    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        // Connector1, the inner connector, creates a response with a `rp1` header
387        #[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        // Connector2, the outer connector, calls the inner connector and adds the `rp2` header to the response
404        #[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        // Plugin1 registers Connector1
419        #[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        // Plugin2 registers Connector2
438        #[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        // Emulate assembling a full runtime plugins list and using it to apply configuration
462        let plugins = RuntimePlugins::new()
463            // intentionally configure the plugins in the reverse order
464            .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        // Use the resulting HTTP connector to make a response
471        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        // Verify headers from both connectors are present,
481        // which will only be possible if they were run in the correct order
482        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}