Skip to main content

aws_lite_rs/api/
cloudfront.rs

1//! Amazon CloudFront API client.
2//!
3//! Thin wrapper over generated ops. All URL construction and HTTP methods
4//! are in `ops::cloudfront::CloudfrontOps`. This layer adds:
5//! - Ergonomic method signatures
6
7use crate::{
8    AwsHttpClient, Result,
9    ops::cloudfront::CloudfrontOps,
10    types::cloudfront::{
11        Distribution, DistributionConfig, DistributionList, OriginAccessControl,
12        OriginAccessControlConfig,
13    },
14};
15
16/// Client for the Amazon CloudFront API
17pub struct CloudfrontClient<'a> {
18    ops: CloudfrontOps<'a>,
19}
20
21impl<'a> CloudfrontClient<'a> {
22    /// Create a new Amazon CloudFront API client
23    pub(crate) fn new(client: &'a AwsHttpClient) -> Self {
24        Self {
25            ops: CloudfrontOps::new(client),
26        }
27    }
28
29    /// List CloudFront distributions.
30    pub async fn list_distributions(&self) -> Result<DistributionList> {
31        self.ops.list_distributions().await
32    }
33
34    /// Get the configuration for a CloudFront distribution.
35    pub async fn get_distribution_config(&self, id: &str) -> Result<DistributionConfig> {
36        self.ops.get_distribution_config(id).await
37    }
38
39    /// Update a CloudFront distribution configuration.
40    pub async fn update_distribution(
41        &self,
42        id: &str,
43        body: &DistributionConfig,
44    ) -> Result<Distribution> {
45        self.ops.update_distribution(id, body).await
46    }
47
48    /// Creates a new origin access control in CloudFront.
49    pub async fn create_origin_access_control(
50        &self,
51        body: &OriginAccessControlConfig,
52    ) -> Result<OriginAccessControl> {
53        self.ops.create_origin_access_control(body).await
54    }
55
56    /// Creates a new CloudFront distribution.
57    pub async fn create_distribution(&self, body: &DistributionConfig) -> Result<Distribution> {
58        self.ops.create_distribution(body).await
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use crate::AwsHttpClient;
65    use crate::mock_client::MockClient;
66    use crate::test_support::cloudfront_mock_helpers::CloudfrontMockHelpers;
67
68    #[tokio::test]
69    async fn list_distributions_returns_parsed_response() {
70        let mut mock = MockClient::new();
71        mock.expect_list_distributions().returning_bytes(
72            b"<DistributionList>\
73                <Marker></Marker>\
74                <MaxItems>100</MaxItems>\
75                <IsTruncated>false</IsTruncated>\
76                <Quantity>1</Quantity>\
77                <Items>\
78                  <DistributionSummary>\
79                    <Id>E1ABC2DEF3GHIJ</Id>\
80                    <ARN>arn:aws:cloudfront::123456789012:distribution/E1ABC2DEF3GHIJ</ARN>\
81                    <Status>Deployed</Status>\
82                    <DomainName>d111111abcdef8.cloudfront.net</DomainName>\
83                    <Origins><Quantity>1</Quantity></Origins>\
84                    <DefaultCacheBehavior>\
85                      <TargetOriginId>myS3Origin</TargetOriginId>\
86                      <ViewerProtocolPolicy>redirect-to-https</ViewerProtocolPolicy>\
87                    </DefaultCacheBehavior>\
88                    <PriceClass>PriceClass_100</PriceClass>\
89                    <Enabled>true</Enabled>\
90                    <Comment>Test distribution</Comment>\
91                  </DistributionSummary>\
92                </Items>\
93              </DistributionList>"
94                .to_vec(),
95        );
96
97        let client = AwsHttpClient::from_mock(mock);
98        let list = client.cloudfront().list_distributions().await.unwrap();
99
100        assert_eq!(list.quantity, 1);
101        assert!(!list.is_truncated);
102        assert_eq!(list.items.len(), 1);
103
104        let dist = &list.items[0];
105        assert_eq!(dist.id, "E1ABC2DEF3GHIJ");
106        assert_eq!(
107            dist.arn,
108            "arn:aws:cloudfront::123456789012:distribution/E1ABC2DEF3GHIJ"
109        );
110        assert_eq!(dist.status, "Deployed");
111        assert_eq!(dist.domain_name, "d111111abcdef8.cloudfront.net");
112        assert_eq!(dist.price_class, "PriceClass_100");
113        assert!(dist.enabled);
114        assert_eq!(
115            dist.default_cache_behavior.viewer_protocol_policy,
116            "redirect-to-https"
117        );
118    }
119
120    #[tokio::test]
121    async fn list_distributions_empty_returns_zero_items() {
122        let mut mock = MockClient::new();
123        mock.expect_list_distributions().returning_bytes(
124            b"<DistributionList>\
125                <Marker></Marker>\
126                <MaxItems>100</MaxItems>\
127                <IsTruncated>false</IsTruncated>\
128                <Quantity>0</Quantity>\
129              </DistributionList>"
130                .to_vec(),
131        );
132
133        let client = AwsHttpClient::from_mock(mock);
134        let list = client.cloudfront().list_distributions().await.unwrap();
135
136        assert_eq!(list.quantity, 0);
137        assert!(list.items.is_empty());
138    }
139
140    #[tokio::test]
141    async fn get_distribution_config_returns_parsed_response() {
142        let mut mock = MockClient::new();
143        mock.expect_get_distribution_config("E1ABC2DEF3GHIJ")
144            .returning_bytes(
145                b"<DistributionConfig>\
146                    <CallerReference>unique-ref-123</CallerReference>\
147                    <Origins>\
148                      <Quantity>1</Quantity>\
149                      <Items>\
150                        <Origin>\
151                          <Id>myS3Origin</Id>\
152                          <DomainName>mybucket.s3.amazonaws.com</DomainName>\
153                        </Origin>\
154                      </Items>\
155                    </Origins>\
156                    <DefaultCacheBehavior>\
157                      <TargetOriginId>myS3Origin</TargetOriginId>\
158                      <ViewerProtocolPolicy>https-only</ViewerProtocolPolicy>\
159                    </DefaultCacheBehavior>\
160                    <Comment>My CloudFront distribution</Comment>\
161                    <Enabled>true</Enabled>\
162                  </DistributionConfig>"
163                    .to_vec(),
164            );
165
166        let client = AwsHttpClient::from_mock(mock);
167        let config = client
168            .cloudfront()
169            .get_distribution_config("E1ABC2DEF3GHIJ")
170            .await
171            .unwrap();
172
173        assert_eq!(config.caller_reference, "unique-ref-123");
174        assert_eq!(config.comment, "My CloudFront distribution");
175        assert!(config.enabled);
176        assert_eq!(config.origins.quantity, 1);
177        assert_eq!(config.origins.items.len(), 1);
178        assert_eq!(config.origins.items[0].id, "myS3Origin");
179        assert_eq!(
180            config.origins.items[0].domain_name,
181            "mybucket.s3.amazonaws.com"
182        );
183        assert_eq!(
184            config.default_cache_behavior.viewer_protocol_policy,
185            "https-only"
186        );
187    }
188
189    #[tokio::test]
190    async fn get_distribution_config_parses_aliases_default_root_object_viewer_certificate() {
191        let mut mock = MockClient::new();
192        mock.expect_get_distribution_config("E2XYZ3CUSTOM456")
193            .returning_bytes(
194                b"<DistributionConfig>\
195                    <CallerReference>ref-with-custom-domain</CallerReference>\
196                    <Aliases>\
197                      <Quantity>2</Quantity>\
198                      <Items>\
199                        <CNAME>www.example.com</CNAME>\
200                        <CNAME>example.com</CNAME>\
201                      </Items>\
202                    </Aliases>\
203                    <DefaultRootObject>index.html</DefaultRootObject>\
204                    <Origins>\
205                      <Quantity>1</Quantity>\
206                    </Origins>\
207                    <DefaultCacheBehavior>\
208                      <TargetOriginId>myOrigin</TargetOriginId>\
209                      <ViewerProtocolPolicy>redirect-to-https</ViewerProtocolPolicy>\
210                    </DefaultCacheBehavior>\
211                    <Comment>Custom domain distribution</Comment>\
212                    <Enabled>true</Enabled>\
213                    <ViewerCertificate>\
214                      <ACMCertificateArn>arn:aws:acm:us-east-1:123456789012:certificate/abc123</ACMCertificateArn>\
215                      <SSLSupportMethod>sni-only</SSLSupportMethod>\
216                      <MinimumProtocolVersion>TLSv1.2_2021</MinimumProtocolVersion>\
217                      <CloudFrontDefaultCertificate>false</CloudFrontDefaultCertificate>\
218                    </ViewerCertificate>\
219                  </DistributionConfig>"
220                    .to_vec(),
221            );
222
223        let client = AwsHttpClient::from_mock(mock);
224        let config = client
225            .cloudfront()
226            .get_distribution_config("E2XYZ3CUSTOM456")
227            .await
228            .unwrap();
229
230        // Aliases
231        let aliases = config.aliases.as_ref().expect("aliases should be present");
232        assert_eq!(aliases.quantity, 2);
233        assert_eq!(aliases.items, vec!["www.example.com", "example.com"]);
234
235        // DefaultRootObject
236        assert_eq!(config.default_root_object.as_deref(), Some("index.html"));
237
238        // ViewerCertificate
239        let vc = config
240            .viewer_certificate
241            .as_ref()
242            .expect("viewer_certificate should be present");
243        assert_eq!(
244            vc.acm_certificate_arn.as_deref(),
245            Some("arn:aws:acm:us-east-1:123456789012:certificate/abc123")
246        );
247        assert_eq!(vc.ssl_support_method.as_deref(), Some("sni-only"));
248        assert_eq!(vc.minimum_protocol_version.as_deref(), Some("TLSv1.2_2021"));
249        assert_eq!(vc.cloud_front_default_certificate, Some(false));
250    }
251
252    #[tokio::test]
253    async fn get_distribution_config_uses_cloudfront_default_cert_when_no_custom_domain() {
254        let mut mock = MockClient::new();
255        mock.expect_get_distribution_config("E3DEFAULT789")
256            .returning_bytes(
257                b"<DistributionConfig>\
258                    <CallerReference>ref-default-cert</CallerReference>\
259                    <Origins><Quantity>1</Quantity></Origins>\
260                    <DefaultCacheBehavior>\
261                      <TargetOriginId>origin</TargetOriginId>\
262                      <ViewerProtocolPolicy>allow-all</ViewerProtocolPolicy>\
263                    </DefaultCacheBehavior>\
264                    <Comment></Comment>\
265                    <Enabled>true</Enabled>\
266                    <ViewerCertificate>\
267                      <CloudFrontDefaultCertificate>true</CloudFrontDefaultCertificate>\
268                    </ViewerCertificate>\
269                  </DistributionConfig>"
270                    .to_vec(),
271            );
272
273        let client = AwsHttpClient::from_mock(mock);
274        let config = client
275            .cloudfront()
276            .get_distribution_config("E3DEFAULT789")
277            .await
278            .unwrap();
279
280        // No aliases or default root object
281        assert!(config.aliases.is_none());
282        assert!(config.default_root_object.is_none());
283
284        // CloudFront default certificate only
285        let vc = config
286            .viewer_certificate
287            .as_ref()
288            .expect("viewer_certificate should be present");
289        assert_eq!(vc.cloud_front_default_certificate, Some(true));
290        assert!(vc.acm_certificate_arn.is_none());
291        assert!(vc.ssl_support_method.is_none());
292    }
293
294    #[tokio::test]
295    async fn create_origin_access_control_returns_parsed_response() {
296        let mut mock = MockClient::new();
297        mock.expect_create_origin_access_control().returning_bytes(
298            b"<OriginAccessControl>\
299                <Id>E1XYZ2ABC3DEFG</Id>\
300                <OriginAccessControlConfig>\
301                  <Name>test-oac</Name>\
302                  <Description>Test OAC</Description>\
303                  <SigningProtocol>sigv4</SigningProtocol>\
304                  <SigningBehavior>always</SigningBehavior>\
305                  <OriginAccessControlOriginType>s3</OriginAccessControlOriginType>\
306                </OriginAccessControlConfig>\
307              </OriginAccessControl>"
308                .to_vec(),
309        );
310
311        let client = AwsHttpClient::from_mock(mock);
312        let oac_config = crate::types::cloudfront::OriginAccessControlConfig {
313            name: "test-oac".to_string(),
314            description: Some("Test OAC".to_string()),
315            signing_protocol: "sigv4".to_string(),
316            signing_behavior: "always".to_string(),
317            origin_access_control_origin_type: "s3".to_string(),
318        };
319        let oac = client
320            .cloudfront()
321            .create_origin_access_control(&oac_config)
322            .await
323            .unwrap();
324
325        assert_eq!(oac.id, "E1XYZ2ABC3DEFG");
326        let config = oac
327            .origin_access_control_config
328            .as_ref()
329            .expect("OAC config should be present");
330        assert_eq!(config.name, "test-oac");
331        assert_eq!(config.signing_protocol, "sigv4");
332        assert_eq!(config.signing_behavior, "always");
333        assert_eq!(config.origin_access_control_origin_type, "s3");
334    }
335}