aws_config/meta/
region.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Region providers that augment existing providers with new functionality
7
8use aws_types::region::Region;
9use std::borrow::Cow;
10use std::fmt::Debug;
11use tracing::Instrument;
12
13/// Load a region by selecting the first from a series of region providers.
14///
15/// # Examples
16///
17/// ```no_run
18/// # fn example() {
19/// use aws_types::region::Region;
20/// use std::env;
21/// use aws_config::meta::region::RegionProviderChain;
22///
23/// // region provider that first checks the `CUSTOM_REGION` environment variable,
24/// // then checks the default provider chain, then falls back to us-east-2
25/// let provider = RegionProviderChain::first_try(env::var("CUSTOM_REGION").ok().map(Region::new))
26///     .or_default_provider()
27///     .or_else(Region::new("us-east-2"));
28/// # }
29/// ```
30#[derive(Debug)]
31pub struct RegionProviderChain {
32    providers: Vec<Box<dyn ProvideRegion>>,
33}
34
35impl RegionProviderChain {
36    /// Load a region from the provider chain
37    ///
38    /// The first provider to return a non-optional region will be selected
39    pub async fn region(&self) -> Option<Region> {
40        for provider in &self.providers {
41            if let Some(region) = provider
42                .region()
43                .instrument(tracing::info_span!("load_region", provider = ?provider))
44                .await
45            {
46                return Some(region);
47            }
48        }
49        None
50    }
51
52    /// Create a default provider chain that starts by checking this provider.
53    pub fn first_try(provider: impl ProvideRegion + 'static) -> Self {
54        RegionProviderChain {
55            providers: vec![Box::new(provider)],
56        }
57    }
58
59    /// Add a fallback provider to the region provider chain.
60    pub fn or_else(mut self, fallback: impl ProvideRegion + 'static) -> Self {
61        self.providers.push(Box::new(fallback));
62        self
63    }
64
65    /// Create a region provider chain that starts by checking the default provider.
66    pub fn default_provider() -> Self {
67        Self::first_try(crate::default_provider::region::default_provider())
68    }
69
70    /// Fallback to the default provider
71    pub fn or_default_provider(mut self) -> Self {
72        self.providers
73            .push(Box::new(crate::default_provider::region::default_provider()));
74        self
75    }
76}
77
78impl ProvideRegion for Option<Region> {
79    fn region(&self) -> future::ProvideRegion<'_> {
80        future::ProvideRegion::ready(self.clone())
81    }
82}
83
84impl ProvideRegion for RegionProviderChain {
85    fn region(&self) -> future::ProvideRegion<'_> {
86        future::ProvideRegion::new(RegionProviderChain::region(self))
87    }
88}
89
90/// Future wrapper returned by [`ProvideRegion`]
91///
92/// Note: this module should only be used when implementing your own region providers.
93pub mod future {
94    use std::future::Future;
95    use std::pin::Pin;
96    use std::task::{Context, Poll};
97
98    use aws_smithy_async::future::now_or_later::NowOrLater;
99
100    use aws_types::region::Region;
101
102    type BoxFuture<'a> = Pin<Box<dyn Future<Output = Option<Region>> + Send + 'a>>;
103    /// Future returned by [`ProvideRegion`](super::ProvideRegion)
104    ///
105    /// - When wrapping an already loaded region, use [`ready`](ProvideRegion::ready).
106    /// - When wrapping an asynchronously loaded region, use [`new`](ProvideRegion::new).
107    #[derive(Debug)]
108    pub struct ProvideRegion<'a>(NowOrLater<Option<Region>, BoxFuture<'a>>);
109    impl<'a> ProvideRegion<'a> {
110        /// A future that wraps the given future
111        pub fn new(future: impl Future<Output = Option<Region>> + Send + 'a) -> Self {
112            Self(NowOrLater::new(Box::pin(future)))
113        }
114
115        /// A future that resolves to a given region
116        pub fn ready(region: Option<Region>) -> Self {
117            Self(NowOrLater::ready(region))
118        }
119    }
120
121    impl Future for ProvideRegion<'_> {
122        type Output = Option<Region>;
123
124        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
125            Pin::new(&mut self.0).poll(cx)
126        }
127    }
128}
129
130/// Provide a [`Region`] to use with AWS requests
131///
132/// For most cases [`default_provider`](crate::default_provider::region::default_provider) will be the best option, implementing
133/// a standard provider chain.
134pub trait ProvideRegion: Send + Sync + Debug {
135    /// Load a region from this provider
136    fn region(&self) -> future::ProvideRegion<'_>;
137}
138
139impl ProvideRegion for Region {
140    fn region(&self) -> future::ProvideRegion<'_> {
141        future::ProvideRegion::ready(Some(self.clone()))
142    }
143}
144
145impl<'a> ProvideRegion for &'a Region {
146    fn region(&self) -> future::ProvideRegion<'_> {
147        future::ProvideRegion::ready(Some((*self).clone()))
148    }
149}
150
151impl ProvideRegion for Box<dyn ProvideRegion> {
152    fn region(&self) -> future::ProvideRegion<'_> {
153        self.as_ref().region()
154    }
155}
156
157impl ProvideRegion for &'static str {
158    fn region(&self) -> future::ProvideRegion<'_> {
159        future::ProvideRegion::ready(Some(Region::new(Cow::Borrowed(*self))))
160    }
161}
162
163#[cfg(test)]
164mod test {
165    use crate::meta::region::RegionProviderChain;
166    use aws_types::region::Region;
167    use futures_util::FutureExt;
168
169    #[test]
170    fn provider_chain() {
171        let a = None;
172        let b = Some(Region::new("us-east-1"));
173        let chain = RegionProviderChain::first_try(a).or_else(b);
174        assert_eq!(
175            chain.region().now_or_never().expect("ready"),
176            Some(Region::new("us-east-1"))
177        );
178    }
179
180    #[test]
181    fn empty_chain() {
182        let chain = RegionProviderChain::first_try(None).or_else(None);
183        assert_eq!(chain.region().now_or_never().expect("ready"), None);
184    }
185}