1use crate::{
8 AwsHttpClient, Result,
9 ops::cloudwatch::CloudwatchOps,
10 types::cloudwatch::{
11 DeleteAlarmsInput, DescribeAlarmsInput, DescribeAlarmsResponse, GetMetricDataInput,
12 GetMetricDataResponse, GetMetricStatisticsInput, GetMetricStatisticsResponse,
13 ListMetricsInput, ListMetricsResponse, PutMetricAlarmInput,
14 },
15};
16
17pub struct CloudWatchClient<'a> {
19 ops: CloudwatchOps<'a>,
20}
21
22impl<'a> CloudWatchClient<'a> {
23 pub(crate) fn new(client: &'a AwsHttpClient) -> Self {
25 Self {
26 ops: CloudwatchOps::new(client),
27 }
28 }
29
30 pub async fn get_metric_statistics(
32 &self,
33 body: &GetMetricStatisticsInput,
34 ) -> Result<GetMetricStatisticsResponse> {
35 self.ops.get_metric_statistics(body).await
36 }
37
38 pub async fn list_metrics(&self, body: &ListMetricsInput) -> Result<ListMetricsResponse> {
40 self.ops.list_metrics(body).await
41 }
42
43 pub async fn describe_alarms(
45 &self,
46 body: &DescribeAlarmsInput,
47 ) -> Result<DescribeAlarmsResponse> {
48 self.ops.describe_alarms(body).await
49 }
50
51 pub async fn put_metric_alarm(&self, body: &PutMetricAlarmInput) -> Result<()> {
53 self.ops.put_metric_alarm(body).await
54 }
55
56 pub async fn delete_alarms(&self, alarm_names: Vec<String>) -> Result<()> {
58 let body = DeleteAlarmsInput { alarm_names };
59 self.ops.delete_alarms(&body).await
60 }
61
62 pub async fn get_metric_data(
68 &self,
69 body: &GetMetricDataInput,
70 ) -> Result<GetMetricDataResponse> {
71 self.ops.get_metric_data(body).await
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use crate::types::cloudwatch::{
78 GetMetricDataInput, Metric, MetricDataQuery, MetricDataResult, MetricStat, ScanBy,
79 StatusCode, *,
80 };
81
82 #[tokio::test]
83 async fn list_metrics_returns_parsed_response() {
84 let mut mock = crate::MockClient::new();
85 mock.expect_post("/").returning_bytes(
86 r#"<ListMetricsResponse><ListMetricsResult>
87 <Metrics>
88 <member><Namespace>AWS/EC2</Namespace><MetricName>CPUUtilization</MetricName></member>
89 </Metrics>
90 </ListMetricsResult></ListMetricsResponse>"#
91 .as_bytes()
92 .to_vec(),
93 );
94 let client = crate::AwsHttpClient::from_mock(mock);
95 let result = client
96 .cloudwatch()
97 .list_metrics(&ListMetricsInput {
98 namespace: Some("AWS/EC2".into()),
99 ..Default::default()
100 })
101 .await
102 .unwrap();
103 assert_eq!(result.metrics.len(), 1);
104 assert_eq!(result.metrics[0].namespace.as_deref(), Some("AWS/EC2"));
105 assert_eq!(
106 result.metrics[0].metric_name.as_deref(),
107 Some("CPUUtilization")
108 );
109 }
110
111 #[tokio::test]
112 async fn describe_alarms_returns_metric_alarms() {
113 let mut mock = crate::MockClient::new();
114 mock.expect_post("/").returning_bytes(
115 r#"<DescribeAlarmsResponse><DescribeAlarmsResult>
116 <MetricAlarms>
117 <member>
118 <AlarmName>test-alarm</AlarmName>
119 <MetricName>CPUUtilization</MetricName>
120 <Namespace>AWS/EC2</Namespace>
121 <StateValue>OK</StateValue>
122 <Threshold>90.0</Threshold>
123 <ComparisonOperator>GreaterThanThreshold</ComparisonOperator>
124 <EvaluationPeriods>1</EvaluationPeriods>
125 <Period>300</Period>
126 <Statistic>Average</Statistic>
127 </member>
128 </MetricAlarms>
129 </DescribeAlarmsResult></DescribeAlarmsResponse>"#
130 .as_bytes()
131 .to_vec(),
132 );
133 let client = crate::AwsHttpClient::from_mock(mock);
134 let result = client
135 .cloudwatch()
136 .describe_alarms(&DescribeAlarmsInput {
137 alarm_names: vec!["test-alarm".into()],
138 ..Default::default()
139 })
140 .await
141 .unwrap();
142 assert_eq!(result.metric_alarms.len(), 1);
143 let alarm = &result.metric_alarms[0];
144 assert_eq!(alarm.alarm_name.as_deref(), Some("test-alarm"));
145 assert_eq!(alarm.metric_name.as_deref(), Some("CPUUtilization"));
146 assert_eq!(alarm.state_value.as_deref(), Some("OK"));
147 assert!((alarm.threshold.unwrap() - 90.0).abs() < f64::EPSILON);
148 assert_eq!(
149 alarm.comparison_operator,
150 Some(ComparisonOperator::GreaterThanThreshold)
151 );
152 assert_eq!(alarm.statistic, Some(Statistic::Average));
153 }
154
155 #[tokio::test]
156 async fn put_metric_alarm_succeeds() {
157 let mut mock = crate::MockClient::new();
158 mock.expect_post("/").returning_bytes(vec![]);
159 let client = crate::AwsHttpClient::from_mock(mock);
160 let input = PutMetricAlarmInput {
161 alarm_name: "test-alarm".into(),
162 metric_name: Some("CPUUtilization".into()),
163 namespace: Some("AWS/EC2".into()),
164 statistic: Some(Statistic::Average),
165 period: Some(300),
166 evaluation_periods: 1,
167 threshold: Some(90.0),
168 comparison_operator: Some(ComparisonOperator::GreaterThanThreshold),
169 ..Default::default()
170 };
171 client.cloudwatch().put_metric_alarm(&input).await.unwrap();
172 }
173
174 #[tokio::test]
175 async fn delete_alarms_succeeds() {
176 let mut mock = crate::MockClient::new();
177 mock.expect_post("/").returning_bytes(vec![]);
178 let client = crate::AwsHttpClient::from_mock(mock);
179 client
180 .cloudwatch()
181 .delete_alarms(vec!["test-alarm".into()])
182 .await
183 .unwrap();
184 }
185
186 #[tokio::test]
187 async fn get_metric_data_returns_results_for_all_query_ids() {
188 let mut mock = crate::MockClient::new();
189 mock.expect_post("/").returning_bytes(
190 r#"<GetMetricDataResponse><GetMetricDataResult>
191 <MetricDataResults>
192 <member>
193 <Id>cpu</Id>
194 <Label>CPUUtilization</Label>
195 <Timestamps><member>2024-01-01T00:00:00Z</member></Timestamps>
196 <Values><member>42.5</member></Values>
197 <StatusCode>Complete</StatusCode>
198 </member>
199 <member>
200 <Id>networkin</Id>
201 <Label>NetworkIn</Label>
202 <StatusCode>Complete</StatusCode>
203 </member>
204 </MetricDataResults>
205 </GetMetricDataResult></GetMetricDataResponse>"#
206 .as_bytes()
207 .to_vec(),
208 );
209 let client = crate::AwsHttpClient::from_mock(mock);
210 let input = GetMetricDataInput {
211 metric_data_queries: vec![
212 MetricDataQuery {
213 id: "cpu".into(),
214 metric_stat: Some(MetricStat {
215 metric: Metric {
216 namespace: Some("AWS/EC2".into()),
217 metric_name: Some("CPUUtilization".into()),
218 dimensions: vec![],
219 },
220 period: 300,
221 stat: "Average".into(),
222 ..Default::default()
223 }),
224 return_data: Some(true),
225 ..Default::default()
226 },
227 MetricDataQuery {
228 id: "networkin".into(),
229 metric_stat: Some(MetricStat {
230 metric: Metric {
231 namespace: Some("AWS/EC2".into()),
232 metric_name: Some("NetworkIn".into()),
233 dimensions: vec![],
234 },
235 period: 300,
236 stat: "Sum".into(),
237 ..Default::default()
238 }),
239 return_data: Some(true),
240 ..Default::default()
241 },
242 ],
243 start_time: "2024-01-01T00:00:00Z".into(),
244 end_time: "2024-01-01T01:00:00Z".into(),
245 ..Default::default()
246 };
247 let response = client.cloudwatch().get_metric_data(&input).await.unwrap();
248 assert_eq!(response.metric_data_results.len(), 2);
249 let cpu = &response.metric_data_results[0];
250 assert_eq!(cpu.id.as_deref(), Some("cpu"));
251 assert_eq!(cpu.label.as_deref(), Some("CPUUtilization"));
252 assert_eq!(cpu.values.len(), 1);
253 assert!((cpu.values[0] - 42.5).abs() < f64::EPSILON);
254 assert_eq!(cpu.status_code, Some(StatusCode::Complete));
255 let net = &response.metric_data_results[1];
256 assert_eq!(net.id.as_deref(), Some("networkin"));
257 assert_eq!(net.status_code, Some(StatusCode::Complete));
258 }
259
260 #[tokio::test]
261 async fn get_metric_data_returns_next_token_for_pagination() {
262 let mut mock = crate::MockClient::new();
263 mock.expect_post("/").returning_bytes(
264 r#"<GetMetricDataResponse><GetMetricDataResult>
265 <MetricDataResults>
266 <member>
267 <Id>cpu</Id>
268 <StatusCode>Complete</StatusCode>
269 </member>
270 </MetricDataResults>
271 <NextToken>token-page2</NextToken>
272 </GetMetricDataResult></GetMetricDataResponse>"#
273 .as_bytes()
274 .to_vec(),
275 );
276 let client = crate::AwsHttpClient::from_mock(mock);
277 let input = GetMetricDataInput {
278 metric_data_queries: vec![MetricDataQuery {
279 id: "cpu".into(),
280 metric_stat: Some(MetricStat {
281 metric: Metric {
282 namespace: Some("AWS/EC2".into()),
283 metric_name: Some("CPUUtilization".into()),
284 dimensions: vec![],
285 },
286 period: 300,
287 stat: "Average".into(),
288 ..Default::default()
289 }),
290 ..Default::default()
291 }],
292 start_time: "2024-01-01T00:00:00Z".into(),
293 end_time: "2024-01-01T01:00:00Z".into(),
294 max_datapoints: Some(1),
295 ..Default::default()
296 };
297 let response = client.cloudwatch().get_metric_data(&input).await.unwrap();
298 assert_eq!(response.next_token.as_deref(), Some("token-page2"));
299 assert_eq!(response.metric_data_results.len(), 1);
300 }
301
302 #[tokio::test]
303 async fn get_metric_data_with_math_expression_returns_only_return_data_results() {
304 let mut mock = crate::MockClient::new();
305 mock.expect_post("/").returning_bytes(
306 r#"<GetMetricDataResponse><GetMetricDataResult>
307 <MetricDataResults>
308 <member>
309 <Id>error_rate</Id>
310 <Label>error_rate</Label>
311 <StatusCode>Complete</StatusCode>
312 </member>
313 </MetricDataResults>
314 </GetMetricDataResult></GetMetricDataResponse>"#
315 .as_bytes()
316 .to_vec(),
317 );
318 let client = crate::AwsHttpClient::from_mock(mock);
319 let input = GetMetricDataInput {
320 metric_data_queries: vec![
321 MetricDataQuery {
322 id: "errors".into(),
323 metric_stat: Some(MetricStat {
324 metric: Metric {
325 namespace: Some("AWS/Lambda".into()),
326 metric_name: Some("Errors".into()),
327 dimensions: vec![],
328 },
329 period: 300,
330 stat: "Sum".into(),
331 ..Default::default()
332 }),
333 return_data: Some(false),
334 ..Default::default()
335 },
336 MetricDataQuery {
337 id: "error_rate".into(),
338 expression: Some("errors / 100".into()),
339 return_data: Some(true),
340 ..Default::default()
341 },
342 ],
343 start_time: "2024-01-01T00:00:00Z".into(),
344 end_time: "2024-01-02T00:00:00Z".into(),
345 scan_by: Some(ScanBy::TimestampDescending),
346 ..Default::default()
347 };
348 let response = client.cloudwatch().get_metric_data(&input).await.unwrap();
349 assert_eq!(response.metric_data_results.len(), 1);
350 let result = &response.metric_data_results[0];
351 assert_eq!(result.id.as_deref(), Some("error_rate"));
352 assert_eq!(result.status_code, Some(StatusCode::Complete));
353 }
354}