aws_lite_rs/api/
config.rs1use crate::{
9 AwsHttpClient, Result,
10 ops::config::ConfigOps,
11 types::config::{
12 ConfigurationRecorder, ConfigurationRecorderStatus,
13 DescribeConfigurationRecorderStatusRequest, DescribeConfigurationRecorderStatusResponse,
14 DescribeConfigurationRecordersRequest, DescribeConfigurationRecordersResponse,
15 SelectResourceConfigRequest, SelectResourceConfigResponse,
16 },
17};
18
19pub struct ConfigClient<'a> {
21 ops: ConfigOps<'a>,
22}
23
24impl<'a> ConfigClient<'a> {
25 pub(crate) fn new(client: &'a AwsHttpClient) -> Self {
27 Self {
28 ops: ConfigOps::new(client),
29 }
30 }
31
32 pub async fn select_resource_config(
34 &self,
35 body: &SelectResourceConfigRequest,
36 ) -> Result<SelectResourceConfigResponse> {
37 self.ops.select_resource_config(body).await
38 }
39
40 pub async fn describe_configuration_recorders(
51 &self,
52 recorder_names: &[&str],
53 ) -> Result<DescribeConfigurationRecordersResponse> {
54 let body = DescribeConfigurationRecordersRequest {
55 configuration_recorder_names: recorder_names.iter().map(|s| s.to_string()).collect(),
56 ..Default::default()
57 };
58 self.ops.describe_configuration_recorders(&body).await
59 }
60
61 pub async fn list_configuration_recorders(&self) -> Result<Vec<ConfigurationRecorder>> {
63 let resp = self.describe_configuration_recorders(&[]).await?;
64 Ok(resp.configuration_recorders)
65 }
66
67 pub async fn describe_configuration_recorder_status(
72 &self,
73 recorder_names: &[&str],
74 ) -> Result<DescribeConfigurationRecorderStatusResponse> {
75 let body = DescribeConfigurationRecorderStatusRequest {
76 configuration_recorder_names: recorder_names.iter().map(|s| s.to_string()).collect(),
77 ..Default::default()
78 };
79 self.ops.describe_configuration_recorder_status(&body).await
80 }
81
82 pub async fn list_configuration_recorder_statuses(
84 &self,
85 ) -> Result<Vec<ConfigurationRecorderStatus>> {
86 let resp = self.describe_configuration_recorder_status(&[]).await?;
87 Ok(resp.configuration_recorders_status)
88 }
89
90 pub fn select_resource_config_stream(
94 &self,
95 expression: &str,
96 ) -> impl futures_core::Stream<Item = Result<String>> + '_ {
97 let expression = expression.to_string();
98 async_stream::try_stream! {
99 let mut next_token: Option<String> = None;
100 loop {
101 let request = SelectResourceConfigRequest {
102 expression: expression.clone(),
103 next_token: next_token.clone(),
104 ..Default::default()
105 };
106 let response = self.ops.select_resource_config(&request).await?;
107 for result in response.results {
108 yield result;
109 }
110 match response.next_token {
111 Some(token) if !token.is_empty() => next_token = Some(token),
112 _ => break,
113 }
114 }
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::mock_client::MockClient;
123 use crate::test_support::config_mock_helpers::ConfigMockHelpers;
124 use tokio_stream::StreamExt;
125
126 #[tokio::test]
127 async fn select_resource_config_stream_paginates() {
128 let mut mock = MockClient::new();
129
130 mock.expect_post("/")
131 .returning_json_sequence(vec![
132 serde_json::json!({
133 "Results": ["{\"resourceId\":\"vol-1\",\"resourceType\":\"AWS::EC2::Volume\"}"],
134 "NextToken": "token-page2"
135 }),
136 serde_json::json!({
137 "Results": ["{\"resourceId\":\"vol-2\",\"resourceType\":\"AWS::EC2::Volume\"}"]
138 }),
139 ])
140 .times(2);
141
142 let client = AwsHttpClient::from_mock(mock);
143 let config = client.config();
144
145 let results: Vec<String> = config
146 .select_resource_config_stream(
147 "SELECT resourceId WHERE resourceType = 'AWS::EC2::Volume'",
148 )
149 .map(|r| r.unwrap())
150 .collect()
151 .await;
152
153 assert_eq!(results.len(), 2);
154 assert!(results[0].contains("vol-1"));
155 assert!(results[1].contains("vol-2"));
156 }
157
158 #[tokio::test]
159 async fn select_resource_config_stream_single_page() {
160 let mut mock = MockClient::new();
161
162 mock.expect_post("/").returning_json(serde_json::json!({
163 "Results": [
164 "{\"resourceId\":\"vol-1\"}",
165 "{\"resourceId\":\"vol-2\"}"
166 ]
167 }));
168
169 let client = AwsHttpClient::from_mock(mock);
170 let config = client.config();
171
172 let results: Vec<String> = config
173 .select_resource_config_stream("SELECT resourceId")
174 .map(|r| r.unwrap())
175 .collect()
176 .await;
177
178 assert_eq!(results.len(), 2);
179 }
180
181 #[tokio::test]
184 async fn describe_configuration_recorders_returns_recorders() {
185 let mut mock = MockClient::new();
186 mock.expect_describe_configuration_recorders()
187 .returning_json(serde_json::json!({
188 "ConfigurationRecorders": [
189 {
190 "name": "default",
191 "roleARN": "arn:aws:iam::123456789012:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig",
192 "recordingGroup": {
193 "allSupported": true,
194 "includeGlobalResourceTypes": true
195 }
196 }
197 ]
198 }));
199
200 let client = crate::AwsHttpClient::from_mock(mock);
201 let resp = client
202 .config()
203 .describe_configuration_recorders(&[])
204 .await
205 .unwrap();
206 assert_eq!(resp.configuration_recorders.len(), 1);
207 let r = &resp.configuration_recorders[0];
208 assert_eq!(r.name.as_deref(), Some("default"));
209 assert!(
210 r.role_arn
211 .as_deref()
212 .unwrap_or("")
213 .contains("AWSServiceRoleForConfig")
214 );
215 let group = r
216 .recording_group
217 .as_ref()
218 .expect("recording_group should be set");
219 assert_eq!(group.all_supported, Some(true));
220 }
221
222 #[tokio::test]
223 async fn describe_configuration_recorders_handles_empty() {
224 let mut mock = MockClient::new();
225 mock.expect_describe_configuration_recorders()
226 .returning_json(serde_json::json!({"ConfigurationRecorders": []}));
227
228 let client = crate::AwsHttpClient::from_mock(mock);
229 let resp = client
230 .config()
231 .describe_configuration_recorders(&[])
232 .await
233 .unwrap();
234 assert!(resp.configuration_recorders.is_empty());
235 }
236
237 #[tokio::test]
238 async fn describe_configuration_recorder_status_returns_status() {
239 let mut mock = MockClient::new();
240 mock.expect_describe_configuration_recorder_status()
241 .returning_json(serde_json::json!({
242 "ConfigurationRecordersStatus": [
243 {
244 "name": "default",
245 "recording": true,
246 "lastStatus": "Success"
247 }
248 ]
249 }));
250
251 let client = crate::AwsHttpClient::from_mock(mock);
252 let resp = client
253 .config()
254 .describe_configuration_recorder_status(&[])
255 .await
256 .unwrap();
257 assert_eq!(resp.configuration_recorders_status.len(), 1);
258 let s = &resp.configuration_recorders_status[0];
259 assert_eq!(s.name.as_deref(), Some("default"));
260 assert_eq!(s.recording, Some(true));
261 assert_eq!(
262 s.last_status,
263 Some(crate::types::config::RecorderStatus::Success)
264 );
265 }
266
267 #[tokio::test]
268 async fn describe_configuration_recorder_status_handles_empty() {
269 let mut mock = MockClient::new();
270 mock.expect_describe_configuration_recorder_status()
271 .returning_json(serde_json::json!({"ConfigurationRecordersStatus": []}));
272
273 let client = crate::AwsHttpClient::from_mock(mock);
274 let resp = client
275 .config()
276 .describe_configuration_recorder_status(&[])
277 .await
278 .unwrap();
279 assert!(resp.configuration_recorders_status.is_empty());
280 }
281
282 #[tokio::test]
283 async fn select_resource_config_stream_empty() {
284 let mut mock = MockClient::new();
285
286 mock.expect_post("/").returning_json(serde_json::json!({
287 "Results": []
288 }));
289
290 let client = AwsHttpClient::from_mock(mock);
291 let config = client.config();
292
293 let results: Vec<String> = config
294 .select_resource_config_stream(
295 "SELECT resourceId WHERE resourceType = 'AWS::Fake::Thing'",
296 )
297 .map(|r| r.unwrap())
298 .collect()
299 .await;
300
301 assert_eq!(results.len(), 0);
302 }
303}