Skip to main content

aws_lite_rs/api/
cloudwatch.rs

1//! Amazon CloudWatch API client.
2//!
3//! Thin wrapper over generated ops. All URL construction and HTTP methods
4//! are in `ops::cloudwatch::CloudwatchOps`. This layer adds:
5//! - Ergonomic method signatures
6
7use 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
17/// Client for the Amazon CloudWatch API
18pub struct CloudWatchClient<'a> {
19    ops: CloudwatchOps<'a>,
20}
21
22impl<'a> CloudWatchClient<'a> {
23    /// Create a new Amazon CloudWatch API client
24    pub(crate) fn new(client: &'a AwsHttpClient) -> Self {
25        Self {
26            ops: CloudwatchOps::new(client),
27        }
28    }
29
30    /// Gets statistics for the specified metric.
31    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    /// List the specified metrics.
39    pub async fn list_metrics(&self, body: &ListMetricsInput) -> Result<ListMetricsResponse> {
40        self.ops.list_metrics(body).await
41    }
42
43    /// Retrieves the specified alarms.
44    pub async fn describe_alarms(
45        &self,
46        body: &DescribeAlarmsInput,
47    ) -> Result<DescribeAlarmsResponse> {
48        self.ops.describe_alarms(body).await
49    }
50
51    /// Creates or updates an alarm and associates it with the specified metric.
52    pub async fn put_metric_alarm(&self, body: &PutMetricAlarmInput) -> Result<()> {
53        self.ops.put_metric_alarm(body).await
54    }
55
56    /// Deletes the specified alarms.
57    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    /// Retrieves CloudWatch metric values for one or more metrics in a single batch request.
63    ///
64    /// A single call can include up to 500 `MetricDataQuery` structures. Supports math
65    /// expressions across metrics. Use `next_token` in the returned response to page through
66    /// results when the requested time range yields more than `max_datapoints` data points.
67    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}