Skip to main content

fakecloud_core/
registry.rs

1use crate::service::AwsService;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5/// Registry of AWS services available in this FakeCloud instance.
6#[derive(Default)]
7pub struct ServiceRegistry {
8    services: HashMap<String, Arc<dyn AwsService>>,
9}
10
11impl ServiceRegistry {
12    pub fn new() -> Self {
13        Self::default()
14    }
15
16    pub fn register(&mut self, service: Arc<dyn AwsService>) {
17        self.services
18            .insert(service.service_name().to_string(), service);
19    }
20
21    pub fn get(&self, name: &str) -> Option<&Arc<dyn AwsService>> {
22        self.services.get(name)
23    }
24
25    pub fn service_names(&self) -> Vec<&str> {
26        self.services.keys().map(|s| s.as_str()).collect()
27    }
28
29    /// Partition registered services into `(iam_enforceable, not
30    /// enforceable)` lists, sorted alphabetically within each group. Used
31    /// by main.rs to emit the startup log when IAM enforcement is on.
32    pub fn iam_enforcement_split(&self) -> (Vec<&str>, Vec<&str>) {
33        let mut enforced: Vec<&str> = Vec::new();
34        let mut skipped: Vec<&str> = Vec::new();
35        for (name, service) in &self.services {
36            if service.iam_enforceable() {
37                enforced.push(name.as_str());
38            } else {
39                skipped.push(name.as_str());
40            }
41        }
42        enforced.sort();
43        skipped.sort();
44        (enforced, skipped)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
52    use async_trait::async_trait;
53
54    struct EnforcedService {
55        name: &'static str,
56    }
57
58    #[async_trait]
59    impl AwsService for EnforcedService {
60        fn service_name(&self) -> &str {
61            self.name
62        }
63        async fn handle(&self, _: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
64            unreachable!()
65        }
66        fn supported_actions(&self) -> &[&str] {
67            &[]
68        }
69        fn iam_enforceable(&self) -> bool {
70            true
71        }
72    }
73
74    struct UnenforcedService {
75        name: &'static str,
76    }
77
78    #[async_trait]
79    impl AwsService for UnenforcedService {
80        fn service_name(&self) -> &str {
81            self.name
82        }
83        async fn handle(&self, _: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
84            unreachable!()
85        }
86        fn supported_actions(&self) -> &[&str] {
87            &[]
88        }
89    }
90
91    #[test]
92    fn new_is_empty() {
93        let r = ServiceRegistry::new();
94        assert!(r.service_names().is_empty());
95        assert!(r.get("anything").is_none());
96    }
97
98    #[test]
99    fn register_then_get_roundtrip() {
100        let mut r = ServiceRegistry::new();
101        r.register(Arc::new(EnforcedService { name: "s3" }));
102        assert!(r.get("s3").is_some());
103        assert!(r.get("missing").is_none());
104    }
105
106    #[test]
107    fn service_names_collects_registered() {
108        let mut r = ServiceRegistry::new();
109        r.register(Arc::new(EnforcedService { name: "s3" }));
110        r.register(Arc::new(UnenforcedService { name: "sts" }));
111        let mut names = r.service_names();
112        names.sort();
113        assert_eq!(names, vec!["s3", "sts"]);
114    }
115
116    #[test]
117    fn iam_split_sorts_and_separates_groups() {
118        let mut r = ServiceRegistry::new();
119        r.register(Arc::new(EnforcedService { name: "s3" }));
120        r.register(Arc::new(EnforcedService { name: "iam" }));
121        r.register(Arc::new(UnenforcedService { name: "sts" }));
122        r.register(Arc::new(UnenforcedService { name: "bedrock" }));
123        let (enforced, skipped) = r.iam_enforcement_split();
124        assert_eq!(enforced, vec!["iam", "s3"]);
125        assert_eq!(skipped, vec!["bedrock", "sts"]);
126    }
127
128    #[test]
129    fn register_overwrites_same_name() {
130        let mut r = ServiceRegistry::new();
131        r.register(Arc::new(EnforcedService { name: "s3" }));
132        r.register(Arc::new(UnenforcedService { name: "s3" }));
133        let (enforced, skipped) = r.iam_enforcement_split();
134        assert!(enforced.is_empty());
135        assert_eq!(skipped, vec!["s3"]);
136    }
137}