1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

//! Load configuration from AWS Profiles
//!
//! AWS profiles are typically stored in `~/.aws/config` and `~/.aws/credentials`. For more details
//! see the [`load`] function.

pub mod parser;

pub mod credentials;
pub mod profile_file;
pub mod region;

#[cfg(feature = "sso")]
pub mod token;
#[cfg(feature = "sso")]
#[doc(inline)]
pub use token::ProfileFileTokenProvider;

#[doc(inline)]
pub use aws_runtime::env_config::error::EnvConfigFileLoadError as ProfileFileLoadError;
#[doc(inline)]
pub use aws_runtime::env_config::parse::EnvConfigParseError as ProfileParseError;
#[doc(inline)]
pub use aws_runtime::env_config::property::Property;
#[doc(inline)]
pub use aws_runtime::env_config::section::{EnvConfigSections as ProfileSet, Profile};
#[doc(inline)]
pub use credentials::ProfileFileCredentialsProvider;
#[doc(inline)]
pub use parser::load;
#[doc(inline)]
pub use region::ProfileFileRegionProvider;

mod cell {
    use std::future::Future;
    use std::sync::{Arc, Mutex};
    use tokio::sync::OnceCell;

    /// Once cell with a result where the error can be taken.
    ///
    /// The profile providers need to cache their inner provider value (specifically for SSO)
    /// in order to preserve the SSO token cache. This wrapper around [`OnceCell`] allows
    /// for initializing the inner provider once in a way that if it fails, the error can
    /// be taken so that it doesn't need to implement `Clone`.
    #[derive(Debug)]
    pub(super) struct ErrorTakingOnceCell<T, E> {
        cell: OnceCell<Result<Arc<T>, Mutex<E>>>,
    }

    impl<T, E> ErrorTakingOnceCell<T, E> {
        pub(super) fn new() -> Self {
            Self {
                cell: OnceCell::new(),
            }
        }

        pub(super) async fn get_or_init<F, Fut>(
            &self,
            init: F,
            mut taken_error: E,
        ) -> Result<Arc<T>, E>
        where
            F: FnOnce() -> Fut,
            Fut: Future<Output = Result<T, E>>,
        {
            let init = || async move { (init)().await.map(Arc::new).map_err(Mutex::new) };
            match self.cell.get_or_init(init).await {
                Ok(value) => Ok(value.clone()),
                Err(err) => {
                    let mut locked = err.lock().unwrap();
                    std::mem::swap(&mut *locked, &mut taken_error);
                    Err(taken_error)
                }
            }
        }
    }

    #[cfg(test)]
    mod tests {
        use crate::profile::cell::ErrorTakingOnceCell;
        use std::sync::{
            atomic::{AtomicUsize, Ordering},
            Arc,
        };

        #[derive(Debug)]
        enum Error {
            InitError,
            Taken,
        }

        #[tokio::test]
        async fn taken_error() {
            let cell = ErrorTakingOnceCell::new();
            let calls = AtomicUsize::new(0);
            let init = || async {
                calls.fetch_add(1, Ordering::SeqCst);
                Result::<String, _>::Err(Error::InitError)
            };

            let result = cell.get_or_init(init, Error::Taken).await;
            assert!(matches!(result, Err(Error::InitError)));

            let result = cell.get_or_init(init, Error::Taken).await;
            assert!(matches!(result, Err(Error::Taken)));

            let result = cell.get_or_init(init, Error::Taken).await;
            assert!(matches!(result, Err(Error::Taken)));
            assert_eq!(1, calls.load(Ordering::SeqCst));
        }

        #[tokio::test]
        async fn value_initialized_once() {
            let cell = ErrorTakingOnceCell::new();
            let calls = AtomicUsize::new(0);
            let init = || async {
                calls.fetch_add(1, Ordering::SeqCst);
                Result::<_, Error>::Ok("test".to_string())
            };

            let original = cell.get_or_init(init, Error::Taken).await.unwrap();
            let next = cell.get_or_init(init, Error::Taken).await.unwrap();
            assert!(Arc::ptr_eq(&original, &next));
            assert_eq!(1, calls.load(Ordering::SeqCst));
        }
    }
}