rusty_s3/credentials/
rotating.rs

1use std::fmt::{Debug, Formatter, Result as FmtResult};
2use std::sync::{Arc, RwLock};
3
4use super::Credentials;
5
6/// Credentials that can be rotated
7///
8/// This struct can be cloned and shared around the rest of
9/// the application and will always yield the latest credentials,
10/// by calling [`RotatingCredentials::get`].
11///
12/// Credentials can be updated by calling [`RotatingCredentials::update`].
13#[allow(clippy::module_name_repetitions)]
14pub struct RotatingCredentials {
15    inner: Arc<RwLock<Arc<Credentials>>>,
16}
17
18impl RotatingCredentials {
19    /// Construct a new `RotatingCredentials` using the provided key, secret and token
20    #[must_use]
21    pub fn new(key: String, secret: String, token: Option<String>) -> Self {
22        let credentials = Credentials::new_with_maybe_token(key, secret, token);
23
24        Self {
25            inner: Arc::new(RwLock::new(Arc::new(credentials))),
26        }
27    }
28
29    /// Get the latest credentials inside this `RotatingCredentials`
30    ///
31    /// # Panics
32    ///
33    /// If the lock is poisoned
34    #[must_use]
35    pub fn get(&self) -> Arc<Credentials> {
36        let lock = self.inner.read().expect("can't be poisoned");
37        Arc::clone(&lock)
38    }
39
40    /// Update the credentials inside this `RotatingCredentials`
41    ///
42    /// # Panics
43    ///
44    /// If the lock is poisoned
45    pub fn update(&self, key: String, secret: String, token: Option<String>) {
46        let credentials = Credentials::new_with_maybe_token(key, secret, token);
47
48        let mut lock = self.inner.write().expect("can't be poisoned");
49        match Arc::get_mut(&mut lock) {
50            Some(arc) => *arc = credentials,
51            None => *lock = Arc::new(credentials),
52        };
53    }
54}
55
56impl Debug for RotatingCredentials {
57    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
58        let current = self.get();
59        Debug::fmt(&*current, f)
60    }
61}
62
63impl Clone for RotatingCredentials {
64    fn clone(&self) -> Self {
65        Self {
66            inner: Arc::clone(&self.inner),
67        }
68    }
69
70    fn clone_from(&mut self, source: &Self) {
71        self.inner = Arc::clone(&source.inner);
72    }
73}
74
75impl PartialEq for RotatingCredentials {
76    fn eq(&self, other: &Self) -> bool {
77        let current1 = self.get();
78        let current2 = other.get();
79        *current1 == *current2
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use pretty_assertions::assert_eq;
86
87    use super::*;
88
89    #[test]
90    fn rotate() {
91        let credentials =
92            RotatingCredentials::new("abcd".into(), "1234".into(), Some("xyz".into()));
93
94        let current = credentials.get();
95        assert_eq!(current.key(), "abcd");
96        assert_eq!(current.secret(), "1234");
97        assert_eq!(current.token(), Some("xyz"));
98        drop(current);
99
100        credentials.update("1234".into(), "5678".into(), Some("9012".into()));
101
102        let current = credentials.get();
103        assert_eq!(current.key(), "1234");
104        assert_eq!(current.secret(), "5678");
105        assert_eq!(current.token(), Some("9012"));
106        drop(current);
107
108        credentials.update("dcba".into(), "4321".into(), Some("yxz".into()));
109
110        let current = credentials.get();
111        assert_eq!(current.key(), "dcba");
112        assert_eq!(current.secret(), "4321");
113        assert_eq!(current.token(), Some("yxz"));
114        drop(current);
115    }
116
117    #[test]
118    fn rotate_cloned() {
119        let credentials =
120            RotatingCredentials::new("abcd".into(), "1234".into(), Some("xyz".into()));
121
122        let current = credentials.get();
123        assert_eq!(current.key(), "abcd");
124        assert_eq!(current.secret(), "1234");
125        assert_eq!(current.token(), Some("xyz"));
126        drop(current);
127
128        let credentials2 = credentials.clone();
129
130        credentials.update("1234".into(), "5678".into(), Some("9012".into()));
131
132        let current = credentials2.get();
133        assert_eq!(current.key(), "1234");
134        assert_eq!(current.secret(), "5678");
135        assert_eq!(current.token(), Some("9012"));
136        drop(current);
137
138        assert_eq!(credentials, credentials2);
139
140        credentials.update("dcba".into(), "4321".into(), Some("yxz".into()));
141
142        let current = credentials.get();
143        assert_eq!(current.key(), "dcba");
144        assert_eq!(current.secret(), "4321");
145        assert_eq!(current.token(), Some("yxz"));
146        drop(current);
147
148        assert_eq!(credentials, credentials2);
149    }
150
151    #[test]
152    fn debug() {
153        let credentials =
154            RotatingCredentials::new("abcd".into(), "1234".into(), Some("xyz".into()));
155        let debug_output = format!("{credentials:?}");
156        assert_eq!(debug_output, "Credentials { key: \"abcd\", .. }");
157    }
158}