fakecloud_core/
multi_account.rs1use std::collections::HashMap;
9
10use serde::{Deserialize, Serialize};
11
12pub trait AccountState: Sized {
15 fn new_for_account(account_id: &str, region: &str, endpoint: &str) -> Self;
17
18 fn inherit_from(&mut self, _sibling: &Self) {}
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct MultiAccountState<T> {
31 default_account_id: String,
32 region: String,
33 endpoint: String,
34 accounts: HashMap<String, T>,
35}
36
37impl<T: AccountState> MultiAccountState<T> {
38 pub fn new(default_account_id: &str, region: &str, endpoint: &str) -> Self {
40 let mut accounts = HashMap::new();
41 accounts.insert(
42 default_account_id.to_string(),
43 T::new_for_account(default_account_id, region, endpoint),
44 );
45 Self {
46 default_account_id: default_account_id.to_string(),
47 region: region.to_string(),
48 endpoint: endpoint.to_string(),
49 accounts,
50 }
51 }
52
53 pub fn map<U>(&self, mut f: impl FnMut(&T) -> U) -> MultiAccountState<U> {
55 MultiAccountState {
56 default_account_id: self.default_account_id.clone(),
57 region: self.region.clone(),
58 endpoint: self.endpoint.clone(),
59 accounts: self
60 .accounts
61 .iter()
62 .map(|(k, v)| (k.clone(), f(v)))
63 .collect(),
64 }
65 }
66
67 pub fn get_or_create(&mut self, account_id: &str) -> &mut T {
73 if !self.accounts.contains_key(account_id) {
74 let mut state = T::new_for_account(account_id, &self.region, &self.endpoint);
75 if let Some(sibling) = self.accounts.get(&self.default_account_id) {
77 state.inherit_from(sibling);
78 }
79 self.accounts.insert(account_id.to_string(), state);
80 }
81 self.accounts.get_mut(account_id).unwrap()
82 }
83
84 pub fn get_or_create_with<F>(&mut self, account_id: &str, init: F) -> &mut T
88 where
89 F: FnOnce(&mut T),
90 {
91 if !self.accounts.contains_key(account_id) {
92 let mut state = T::new_for_account(account_id, &self.region, &self.endpoint);
93 init(&mut state);
94 self.accounts.insert(account_id.to_string(), state);
95 }
96 self.accounts.get_mut(account_id).unwrap()
97 }
98
99 pub fn get(&self, account_id: &str) -> Option<&T> {
101 self.accounts.get(account_id)
102 }
103
104 pub fn get_mut(&mut self, account_id: &str) -> Option<&mut T> {
106 self.accounts.get_mut(account_id)
107 }
108
109 pub fn iter(&self) -> impl Iterator<Item = (&str, &T)> {
111 self.accounts.iter().map(|(k, v)| (k.as_str(), v))
112 }
113
114 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut T)> {
116 self.accounts.iter_mut().map(|(k, v)| (k.as_str(), v))
117 }
118
119 pub fn default_account_id(&self) -> &str {
121 &self.default_account_id
122 }
123
124 pub fn default_mut(&mut self) -> &mut T {
126 self.accounts.get_mut(&self.default_account_id).unwrap()
127 }
128
129 pub fn default_ref(&self) -> &T {
131 self.accounts.get(&self.default_account_id).unwrap()
132 }
133
134 pub fn reset(&mut self) {
137 self.accounts.clear();
138 self.accounts.insert(
139 self.default_account_id.clone(),
140 T::new_for_account(&self.default_account_id, &self.region, &self.endpoint),
141 );
142 }
143
144 pub fn find_account<F>(&self, predicate: F) -> Option<&str>
148 where
149 F: Fn(&T) -> bool,
150 {
151 self.accounts
152 .iter()
153 .find(|(_, v)| predicate(v))
154 .map(|(k, _)| k.as_str())
155 }
156
157 pub fn account_count(&self) -> usize {
159 self.accounts.len()
160 }
161
162 pub fn region(&self) -> &str {
164 &self.region
165 }
166
167 pub fn endpoint(&self) -> &str {
169 &self.endpoint
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[derive(Debug, Clone, Serialize, Deserialize)]
178 struct TestState {
179 account_id: String,
180 items: Vec<String>,
181 }
182
183 impl AccountState for TestState {
184 fn new_for_account(account_id: &str, _region: &str, _endpoint: &str) -> Self {
185 Self {
186 account_id: account_id.to_string(),
187 items: Vec::new(),
188 }
189 }
190 }
191
192 #[test]
193 fn default_account_exists_on_creation() {
194 let mas: MultiAccountState<TestState> =
195 MultiAccountState::new("111111111111", "us-east-1", "http://localhost:4566");
196 assert_eq!(mas.account_count(), 1);
197 assert!(mas.get("111111111111").is_some());
198 }
199
200 #[test]
201 fn get_or_create_makes_new_account() {
202 let mut mas: MultiAccountState<TestState> =
203 MultiAccountState::new("111111111111", "us-east-1", "http://localhost:4566");
204 let state = mas.get_or_create("222222222222");
205 assert_eq!(state.account_id, "222222222222");
206 assert_eq!(mas.account_count(), 2);
207 }
208
209 #[test]
210 fn get_returns_none_for_unknown() {
211 let mas: MultiAccountState<TestState> =
212 MultiAccountState::new("111111111111", "us-east-1", "http://localhost:4566");
213 assert!(mas.get("999999999999").is_none());
214 }
215
216 #[test]
217 fn reset_clears_all_but_default() {
218 let mut mas: MultiAccountState<TestState> =
219 MultiAccountState::new("111111111111", "us-east-1", "http://localhost:4566");
220 mas.get_or_create("222222222222");
221 mas.get_or_create("333333333333");
222 assert_eq!(mas.account_count(), 3);
223 mas.reset();
224 assert_eq!(mas.account_count(), 1);
225 assert!(mas.get("111111111111").is_some());
226 assert!(mas.get("222222222222").is_none());
227 }
228
229 #[test]
230 fn iter_visits_all_accounts() {
231 let mut mas: MultiAccountState<TestState> =
232 MultiAccountState::new("111111111111", "us-east-1", "http://localhost:4566");
233 mas.get_or_create("222222222222");
234 let ids: Vec<&str> = mas.iter().map(|(id, _)| id).collect();
235 assert_eq!(ids.len(), 2);
236 assert!(ids.contains(&"111111111111"));
237 assert!(ids.contains(&"222222222222"));
238 }
239}