1use chrono::Utc;
5use http::{HeaderMap, StatusCode};
6
7use fakecloud_aws::arn::Arn;
8use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError};
9
10use crate::extras::{
11 CaCertificatesBundleSource, CreateAnycastIpListRequest, CreateTrustStoreRequest,
12 CreateVpcOriginRequest, ResourcePolicyRequest, StoredAnycastIpList, StoredResourcePolicy,
13 StoredTrustStore, StoredVpcOrigin, UpdateAnycastIpListRequest, VpcOriginEndpointConfig,
14};
15use crate::policies::{
16 not_found, precondition_failed, require_if_match, rfc3339, route_id, xml_with_etag,
17};
18use crate::router::Route;
19use crate::service::{
20 aws_error, esc, generate_id_with_prefix, invalid_argument, xml_response, CloudFrontService,
21 DEFAULT_ACCOUNT,
22};
23use crate::xml_io;
24
25const NS: &str = crate::NAMESPACE;
26const XML_DECL: &str = r#"<?xml version="1.0" encoding="UTF-8"?>"#;
27
28impl CloudFrontService {
31 pub(crate) fn create_vpc_origin(
32 &self,
33 req: &AwsRequest,
34 ) -> Result<AwsResponse, AwsServiceError> {
35 let parsed: CreateVpcOriginRequest = xml_io::from_xml_root(&req.body)
36 .map_err(|e| invalid_argument(format!("invalid CreateVpcOriginRequest XML: {e}")))?;
37 let cfg = parsed.vpc_origin_endpoint_config;
38 if cfg.name.is_empty() {
39 return Err(invalid_argument("Name is required"));
40 }
41 if cfg.arn.is_empty() {
42 return Err(invalid_argument("Arn is required"));
43 }
44 let mut state = self.state.write();
45 let account = state
46 .accounts
47 .entry(DEFAULT_ACCOUNT.to_string())
48 .or_default();
49 if account
50 .vpc_origins
51 .values()
52 .any(|v| v.config.name == cfg.name)
53 {
54 return Err(aws_error(
55 StatusCode::CONFLICT,
56 "EntityAlreadyExists",
57 format!("VpcOrigin {} already exists", cfg.name),
58 ));
59 }
60 let id = generate_id_with_prefix("VO");
61 let etag = generate_id_with_prefix("E");
62 let now = Utc::now();
63 let arn =
64 Arn::global("cloudfront", DEFAULT_ACCOUNT, &format!("vpc-origin/{id}")).to_string();
65 let stored = StoredVpcOrigin {
66 id: id.clone(),
67 arn,
68 status: "Deployed".to_string(),
69 etag: etag.clone(),
70 created_time: now,
71 last_modified_time: now,
72 config: cfg,
73 };
74 account.vpc_origins.insert(id.clone(), stored.clone());
75 drop(state);
76 let body = render_vpc_origin(&stored);
77 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
78 }
79
80 pub(crate) fn get_vpc_origin(&self, route: &Route) -> Result<AwsResponse, AwsServiceError> {
81 let id = route_id(route, "VpcOrigin")?;
82 let state = self.state.read();
83 let v = state
84 .accounts
85 .get(DEFAULT_ACCOUNT)
86 .and_then(|a| a.vpc_origins.get(&id).cloned())
87 .ok_or_else(|| not_found("VpcOrigin", &id))?;
88 drop(state);
89 let body = render_vpc_origin(&v);
90 Ok(xml_with_etag(StatusCode::OK, body, &v.etag, None))
91 }
92
93 pub(crate) fn update_vpc_origin(
94 &self,
95 req: &AwsRequest,
96 route: &Route,
97 ) -> Result<AwsResponse, AwsServiceError> {
98 let id = route_id(route, "VpcOrigin")?;
99 let if_match = require_if_match(req)?;
100 let cfg: VpcOriginEndpointConfig = xml_io::from_xml_root(&req.body)
101 .map_err(|e| invalid_argument(format!("invalid VpcOriginEndpointConfig XML: {e}")))?;
102 let mut state = self.state.write();
103 let account = state
104 .accounts
105 .get_mut(DEFAULT_ACCOUNT)
106 .ok_or_else(|| not_found("VpcOrigin", &id))?;
107 let v = account
108 .vpc_origins
109 .get_mut(&id)
110 .ok_or_else(|| not_found("VpcOrigin", &id))?;
111 if v.etag != if_match {
112 return Err(precondition_failed());
113 }
114 if cfg.name.is_empty() {
115 return Err(invalid_argument("Name is required"));
116 }
117 if cfg.arn.is_empty() {
118 return Err(invalid_argument("Arn is required"));
119 }
120 v.config = cfg;
121 v.etag = generate_id_with_prefix("E");
122 v.last_modified_time = Utc::now();
123 let snap = v.clone();
124 drop(state);
125 let body = render_vpc_origin(&snap);
126 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
127 }
128
129 pub(crate) fn delete_vpc_origin(
130 &self,
131 req: &AwsRequest,
132 route: &Route,
133 ) -> Result<AwsResponse, AwsServiceError> {
134 let id = route_id(route, "VpcOrigin")?;
135 let if_match = require_if_match(req)?;
136 let mut state = self.state.write();
137 let account = state
138 .accounts
139 .get_mut(DEFAULT_ACCOUNT)
140 .ok_or_else(|| not_found("VpcOrigin", &id))?;
141 let v = account
142 .vpc_origins
143 .get(&id)
144 .ok_or_else(|| not_found("VpcOrigin", &id))?;
145 if v.etag != if_match {
146 return Err(precondition_failed());
147 }
148 let arn = v.arn.clone();
149 let snap = v.clone();
150 account.vpc_origins.remove(&id);
151 account.tags.remove(&arn);
152 drop(state);
153 let body = render_vpc_origin(&snap);
154 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
155 }
156
157 pub(crate) fn list_vpc_origins(
158 &self,
159 _req: &AwsRequest,
160 ) -> Result<AwsResponse, AwsServiceError> {
161 let state = self.state.read();
162 let mut items: Vec<StoredVpcOrigin> = state
163 .accounts
164 .get(DEFAULT_ACCOUNT)
165 .map(|a| a.vpc_origins.values().cloned().collect())
166 .unwrap_or_default();
167 drop(state);
168 items.sort_by(|a, b| a.id.cmp(&b.id));
169
170 let mut body = String::with_capacity(512);
171 body.push_str(XML_DECL);
172 body.push_str(&format!("<VpcOriginList xmlns=\"{NS}\">"));
173 body.push_str("<Marker></Marker>");
174 body.push_str("<MaxItems>100</MaxItems>");
175 body.push_str(&format!("<IsTruncated>{}</IsTruncated>", false));
176 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
177 body.push_str("<Items>");
178 for v in &items {
179 body.push_str("<VpcOriginSummary>");
180 body.push_str(&format!("<Id>{}</Id>", esc(&v.id)));
181 body.push_str(&format!("<Name>{}</Name>", esc(&v.config.name)));
182 body.push_str(&format!("<Status>{}</Status>", esc(&v.status)));
183 body.push_str(&format!(
184 "<CreatedTime>{}</CreatedTime>",
185 rfc3339(&v.created_time)
186 ));
187 body.push_str(&format!(
188 "<LastModifiedTime>{}</LastModifiedTime>",
189 rfc3339(&v.last_modified_time)
190 ));
191 body.push_str(&format!("<Arn>{}</Arn>", esc(&v.arn)));
192 body.push_str(&format!(
193 "<OriginEndpointArn>{}</OriginEndpointArn>",
194 esc(&v.config.arn)
195 ));
196 body.push_str("</VpcOriginSummary>");
197 }
198 body.push_str("</Items>");
199 body.push_str("</VpcOriginList>");
200 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
201 }
202}
203
204impl CloudFrontService {
207 pub(crate) fn create_anycast_ip_list(
208 &self,
209 req: &AwsRequest,
210 ) -> Result<AwsResponse, AwsServiceError> {
211 let cfg: CreateAnycastIpListRequest = xml_io::from_xml_root(&req.body).map_err(|e| {
212 invalid_argument(format!("invalid CreateAnycastIpListRequest XML: {e}"))
213 })?;
214 if cfg.name.is_empty() {
215 return Err(invalid_argument("Name is required"));
216 }
217 if cfg.ip_count != 3 && cfg.ip_count != 21 {
218 return Err(invalid_argument("IpCount must be 3 or 21"));
219 }
220 let mut state = self.state.write();
221 let account = state
222 .accounts
223 .entry(DEFAULT_ACCOUNT.to_string())
224 .or_default();
225 if account
226 .anycast_ip_lists
227 .values()
228 .any(|a| a.name == cfg.name)
229 {
230 return Err(aws_error(
231 StatusCode::CONFLICT,
232 "EntityAlreadyExists",
233 format!("AnycastIpList {} already exists", cfg.name),
234 ));
235 }
236 let id = generate_id_with_prefix("AIL");
237 let arn = format!(
238 "arn:aws:cloudfront::{}:anycast-ip-list/{}",
239 DEFAULT_ACCOUNT, id
240 );
241 let anycast_ips: Vec<String> = (0..cfg.ip_count)
243 .map(|i| format!("198.51.100.{}", (i + 1) as u8))
244 .collect();
245 let etag = generate_id_with_prefix("E");
246 let stored = StoredAnycastIpList {
247 id: id.clone(),
248 name: cfg.name,
249 status: "Deployed".to_string(),
250 arn,
251 ip_count: cfg.ip_count,
252 ip_address_type: cfg.ip_address_type,
253 anycast_ips,
254 last_modified_time: Utc::now(),
255 etag: etag.clone(),
256 };
257 account.anycast_ip_lists.insert(id.clone(), stored.clone());
258 drop(state);
259 let body = render_anycast_ip_list(&stored);
260 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
261 }
262
263 pub(crate) fn get_anycast_ip_list(
264 &self,
265 route: &Route,
266 ) -> Result<AwsResponse, AwsServiceError> {
267 let id = route_id(route, "AnycastIpList")?;
268 let state = self.state.read();
269 let a = state
270 .accounts
271 .get(DEFAULT_ACCOUNT)
272 .and_then(|a| a.anycast_ip_lists.get(&id).cloned())
273 .ok_or_else(|| not_found("AnycastIpList", &id))?;
274 drop(state);
275 let body = render_anycast_ip_list(&a);
276 Ok(xml_with_etag(StatusCode::OK, body, &a.etag, None))
277 }
278
279 pub(crate) fn update_anycast_ip_list(
280 &self,
281 req: &AwsRequest,
282 route: &Route,
283 ) -> Result<AwsResponse, AwsServiceError> {
284 let id = route_id(route, "AnycastIpList")?;
285 let if_match = require_if_match(req)?;
286 let cfg: UpdateAnycastIpListRequest = xml_io::from_xml_root(&req.body).map_err(|e| {
287 invalid_argument(format!("invalid UpdateAnycastIpListRequest XML: {e}"))
288 })?;
289 let mut state = self.state.write();
290 let account = state
291 .accounts
292 .get_mut(DEFAULT_ACCOUNT)
293 .ok_or_else(|| not_found("AnycastIpList", &id))?;
294 let a = account
295 .anycast_ip_lists
296 .get_mut(&id)
297 .ok_or_else(|| not_found("AnycastIpList", &id))?;
298 if a.etag != if_match {
299 return Err(precondition_failed());
300 }
301 if let Some(t) = cfg.ip_address_type {
302 a.ip_address_type = Some(t);
303 }
304 a.last_modified_time = Utc::now();
305 a.etag = generate_id_with_prefix("E");
306 let snap = a.clone();
307 drop(state);
308 let body = render_anycast_ip_list(&snap);
309 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
310 }
311
312 pub(crate) fn delete_anycast_ip_list(
313 &self,
314 req: &AwsRequest,
315 route: &Route,
316 ) -> Result<AwsResponse, AwsServiceError> {
317 let id = route_id(route, "AnycastIpList")?;
318 let if_match = require_if_match(req)?;
319 let mut state = self.state.write();
320 let account = state
321 .accounts
322 .get_mut(DEFAULT_ACCOUNT)
323 .ok_or_else(|| not_found("AnycastIpList", &id))?;
324 let a = account
325 .anycast_ip_lists
326 .get(&id)
327 .ok_or_else(|| not_found("AnycastIpList", &id))?;
328 if a.etag != if_match {
329 return Err(precondition_failed());
330 }
331 let arn = a.arn.clone();
332 account.anycast_ip_lists.remove(&id);
333 account.tags.remove(&arn);
334 drop(state);
335 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
336 }
337
338 pub(crate) fn list_anycast_ip_lists(
339 &self,
340 _req: &AwsRequest,
341 ) -> Result<AwsResponse, AwsServiceError> {
342 let state = self.state.read();
343 let mut items: Vec<StoredAnycastIpList> = state
344 .accounts
345 .get(DEFAULT_ACCOUNT)
346 .map(|a| a.anycast_ip_lists.values().cloned().collect())
347 .unwrap_or_default();
348 drop(state);
349 items.sort_by(|a, b| a.id.cmp(&b.id));
350
351 let mut body = String::with_capacity(512);
352 body.push_str(XML_DECL);
353 body.push_str(&format!("<AnycastIpListCollection xmlns=\"{NS}\">"));
354 body.push_str("<Marker></Marker>");
355 body.push_str("<MaxItems>100</MaxItems>");
356 body.push_str(&format!("<IsTruncated>{}</IsTruncated>", false));
357 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
358 body.push_str("<Items>");
359 for a in &items {
360 body.push_str("<AnycastIpListSummary>");
361 push_anycast_summary(&mut body, a);
362 body.push_str("</AnycastIpListSummary>");
363 }
364 body.push_str("</Items>");
365 body.push_str("</AnycastIpListCollection>");
366 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
367 }
368}
369
370impl CloudFrontService {
373 pub(crate) fn create_trust_store(
374 &self,
375 req: &AwsRequest,
376 ) -> Result<AwsResponse, AwsServiceError> {
377 let cfg: CreateTrustStoreRequest = xml_io::from_xml_root(&req.body)
378 .map_err(|e| invalid_argument(format!("invalid CreateTrustStoreRequest XML: {e}")))?;
379 if cfg.name.is_empty() {
380 return Err(invalid_argument("Name is required"));
381 }
382 if cfg
383 .ca_certificates_bundle_source
384 .ca_certificates_bundle_s3_location
385 .is_none()
386 {
387 return Err(invalid_argument(
388 "CaCertificatesBundleSource must specify a non-empty member",
389 ));
390 }
391 let mut state = self.state.write();
392 let account = state
393 .accounts
394 .entry(DEFAULT_ACCOUNT.to_string())
395 .or_default();
396 if account.trust_stores.values().any(|t| t.name == cfg.name) {
397 return Err(aws_error(
398 StatusCode::CONFLICT,
399 "EntityAlreadyExists",
400 format!("TrustStore {} already exists", cfg.name),
401 ));
402 }
403 let id = generate_id_with_prefix("TS");
404 let arn =
405 Arn::global("cloudfront", DEFAULT_ACCOUNT, &format!("trust-store/{id}")).to_string();
406 let etag = generate_id_with_prefix("E");
407 let stored = StoredTrustStore {
408 id: id.clone(),
409 arn,
410 name: cfg.name,
411 etag: etag.clone(),
412 last_modified_time: Utc::now(),
413 ca_certificates_bundle_source: cfg.ca_certificates_bundle_source,
414 };
415 account.trust_stores.insert(id.clone(), stored.clone());
416 drop(state);
417 let body = render_trust_store(&stored);
418 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
419 }
420
421 pub(crate) fn get_trust_store(&self, route: &Route) -> Result<AwsResponse, AwsServiceError> {
422 let id = route_id(route, "TrustStore")?;
423 let state = self.state.read();
424 let t = state
425 .accounts
426 .get(DEFAULT_ACCOUNT)
427 .and_then(|a| a.trust_stores.get(&id).cloned())
428 .ok_or_else(|| not_found("TrustStore", &id))?;
429 drop(state);
430 let body = render_trust_store(&t);
431 Ok(xml_with_etag(StatusCode::OK, body, &t.etag, None))
432 }
433
434 pub(crate) fn update_trust_store(
435 &self,
436 req: &AwsRequest,
437 route: &Route,
438 ) -> Result<AwsResponse, AwsServiceError> {
439 let id = route_id(route, "TrustStore")?;
440 let if_match = require_if_match(req)?;
441 let bundle: CaCertificatesBundleSource = xml_io::from_xml_root(&req.body).map_err(|e| {
442 invalid_argument(format!("invalid CaCertificatesBundleSource XML: {e}"))
443 })?;
444 if bundle.ca_certificates_bundle_s3_location.is_none() {
445 return Err(invalid_argument(
446 "CaCertificatesBundleSource must specify a non-empty member",
447 ));
448 }
449 let mut state = self.state.write();
450 let account = state
451 .accounts
452 .get_mut(DEFAULT_ACCOUNT)
453 .ok_or_else(|| not_found("TrustStore", &id))?;
454 let t = account
455 .trust_stores
456 .get_mut(&id)
457 .ok_or_else(|| not_found("TrustStore", &id))?;
458 if t.etag != if_match {
459 return Err(precondition_failed());
460 }
461 t.ca_certificates_bundle_source = bundle;
462 t.last_modified_time = Utc::now();
463 t.etag = generate_id_with_prefix("E");
464 let snap = t.clone();
465 drop(state);
466 let body = render_trust_store(&snap);
467 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
468 }
469
470 pub(crate) fn delete_trust_store(
471 &self,
472 req: &AwsRequest,
473 route: &Route,
474 ) -> Result<AwsResponse, AwsServiceError> {
475 let id = route_id(route, "TrustStore")?;
476 let if_match = require_if_match(req)?;
477 let mut state = self.state.write();
478 let account = state
479 .accounts
480 .get_mut(DEFAULT_ACCOUNT)
481 .ok_or_else(|| not_found("TrustStore", &id))?;
482 let t = account
483 .trust_stores
484 .get(&id)
485 .ok_or_else(|| not_found("TrustStore", &id))?;
486 if t.etag != if_match {
487 return Err(precondition_failed());
488 }
489 let arn = t.arn.clone();
490 account.trust_stores.remove(&id);
491 account.tags.remove(&arn);
492 drop(state);
493 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
494 }
495
496 pub(crate) fn list_trust_stores(
497 &self,
498 _req: &AwsRequest,
499 ) -> Result<AwsResponse, AwsServiceError> {
500 let state = self.state.read();
501 let mut items: Vec<StoredTrustStore> = state
502 .accounts
503 .get(DEFAULT_ACCOUNT)
504 .map(|a| a.trust_stores.values().cloned().collect())
505 .unwrap_or_default();
506 drop(state);
507 items.sort_by(|a, b| a.id.cmp(&b.id));
508
509 let mut body = String::with_capacity(512);
510 body.push_str(XML_DECL);
511 body.push_str(&format!("<ListTrustStoresResult xmlns=\"{NS}\">"));
512 body.push_str("<TrustStoreList>");
513 for t in &items {
514 body.push_str("<TrustStoreSummary>");
515 body.push_str(&format!("<Id>{}</Id>", esc(&t.id)));
516 body.push_str(&format!("<Arn>{}</Arn>", esc(&t.arn)));
517 body.push_str(&format!("<Name>{}</Name>", esc(&t.name)));
518 body.push_str(&format!(
519 "<LastModifiedTime>{}</LastModifiedTime>",
520 rfc3339(&t.last_modified_time)
521 ));
522 body.push_str("</TrustStoreSummary>");
523 }
524 body.push_str("</TrustStoreList>");
525 body.push_str("</ListTrustStoresResult>");
526 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
527 }
528}
529
530impl CloudFrontService {
533 pub(crate) fn put_resource_policy(
534 &self,
535 req: &AwsRequest,
536 ) -> Result<AwsResponse, AwsServiceError> {
537 let parsed: ResourcePolicyRequest = xml_io::from_xml_root(&req.body)
538 .map_err(|e| invalid_argument(format!("invalid PutResourcePolicyRequest XML: {e}")))?;
539 if parsed.resource_arn.is_empty() {
540 return Err(invalid_argument("ResourceArn is required"));
541 }
542 let policy = parsed
543 .policy_document
544 .ok_or_else(|| invalid_argument("PolicyDocument is required"))?;
545 let mut state = self.state.write();
546 let account = state
547 .accounts
548 .entry(DEFAULT_ACCOUNT.to_string())
549 .or_default();
550 account.resource_policies.insert(
551 parsed.resource_arn.clone(),
552 StoredResourcePolicy {
553 resource_arn: parsed.resource_arn,
554 policy_document: policy,
555 },
556 );
557 drop(state);
558 let mut body = String::with_capacity(128);
559 body.push_str(XML_DECL);
560 body.push_str(&format!("<PutResourcePolicyResult xmlns=\"{NS}\"/>"));
561 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
562 }
563
564 pub(crate) fn get_resource_policy(
565 &self,
566 req: &AwsRequest,
567 ) -> Result<AwsResponse, AwsServiceError> {
568 let parsed: ResourcePolicyRequest = xml_io::from_xml_root(&req.body)
569 .map_err(|e| invalid_argument(format!("invalid GetResourcePolicyRequest XML: {e}")))?;
570 if parsed.resource_arn.is_empty() {
571 return Err(invalid_argument("ResourceArn is required"));
572 }
573 let state = self.state.read();
574 let p = state
575 .accounts
576 .get(DEFAULT_ACCOUNT)
577 .and_then(|a| a.resource_policies.get(&parsed.resource_arn).cloned())
578 .ok_or_else(|| not_found("ResourcePolicy", &parsed.resource_arn))?;
579 drop(state);
580 let mut body = String::with_capacity(512);
581 body.push_str(XML_DECL);
582 body.push_str(&format!("<GetResourcePolicyResult xmlns=\"{NS}\">"));
583 body.push_str(&format!(
584 "<ResourceArn>{}</ResourceArn>",
585 esc(&p.resource_arn)
586 ));
587 body.push_str(&format!(
588 "<PolicyDocument>{}</PolicyDocument>",
589 esc(&p.policy_document)
590 ));
591 body.push_str("</GetResourcePolicyResult>");
592 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
593 }
594
595 pub(crate) fn delete_resource_policy(
596 &self,
597 req: &AwsRequest,
598 ) -> Result<AwsResponse, AwsServiceError> {
599 let parsed: ResourcePolicyRequest = xml_io::from_xml_root(&req.body).map_err(|e| {
600 invalid_argument(format!("invalid DeleteResourcePolicyRequest XML: {e}"))
601 })?;
602 if parsed.resource_arn.is_empty() {
603 return Err(invalid_argument("ResourceArn is required"));
604 }
605 let mut state = self.state.write();
606 let account = state
607 .accounts
608 .get_mut(DEFAULT_ACCOUNT)
609 .ok_or_else(|| not_found("ResourcePolicy", &parsed.resource_arn))?;
610 if account
611 .resource_policies
612 .remove(&parsed.resource_arn)
613 .is_none()
614 {
615 return Err(not_found("ResourcePolicy", &parsed.resource_arn));
616 }
617 drop(state);
618 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
619 }
620}
621
622fn render_vpc_origin(v: &StoredVpcOrigin) -> String {
625 let mut out = String::with_capacity(512);
626 out.push_str(XML_DECL);
627 out.push_str(&format!("<VpcOrigin xmlns=\"{NS}\">"));
628 out.push_str(&format!("<Id>{}</Id>", esc(&v.id)));
629 out.push_str(&format!("<Arn>{}</Arn>", esc(&v.arn)));
630 out.push_str(&format!("<Status>{}</Status>", esc(&v.status)));
631 out.push_str(&format!(
632 "<CreatedTime>{}</CreatedTime>",
633 rfc3339(&v.created_time)
634 ));
635 out.push_str(&format!(
636 "<LastModifiedTime>{}</LastModifiedTime>",
637 rfc3339(&v.last_modified_time)
638 ));
639 out.push_str(&render_vpc_origin_endpoint_config(&v.config));
640 out.push_str("</VpcOrigin>");
641 out
642}
643
644fn render_vpc_origin_endpoint_config(c: &VpcOriginEndpointConfig) -> String {
645 let mut out = String::with_capacity(256);
646 out.push_str("<VpcOriginEndpointConfig>");
647 out.push_str(&format!("<Name>{}</Name>", esc(&c.name)));
648 out.push_str(&format!("<Arn>{}</Arn>", esc(&c.arn)));
649 out.push_str(&format!("<HTTPPort>{}</HTTPPort>", c.http_port));
650 out.push_str(&format!("<HTTPSPort>{}</HTTPSPort>", c.https_port));
651 out.push_str(&format!(
652 "<OriginProtocolPolicy>{}</OriginProtocolPolicy>",
653 esc(&c.origin_protocol_policy)
654 ));
655 if let Some(ssl) = &c.origin_ssl_protocols {
656 out.push_str("<OriginSslProtocols>");
657 out.push_str(&format!("<Quantity>{}</Quantity>", ssl.quantity));
658 out.push_str("<Items>");
659 for p in &ssl.items.ssl_protocol {
660 out.push_str(&format!("<SslProtocol>{}</SslProtocol>", esc(p)));
661 }
662 out.push_str("</Items>");
663 out.push_str("</OriginSslProtocols>");
664 }
665 out.push_str("</VpcOriginEndpointConfig>");
666 out
667}
668
669fn render_anycast_ip_list(a: &StoredAnycastIpList) -> String {
670 let mut out = String::with_capacity(512);
671 out.push_str(XML_DECL);
672 out.push_str(&format!("<AnycastIpList xmlns=\"{NS}\">"));
673 out.push_str(&format!("<Id>{}</Id>", esc(&a.id)));
674 out.push_str(&format!("<Name>{}</Name>", esc(&a.name)));
675 out.push_str(&format!("<Status>{}</Status>", esc(&a.status)));
676 out.push_str(&format!("<Arn>{}</Arn>", esc(&a.arn)));
677 if let Some(t) = &a.ip_address_type {
678 out.push_str(&format!("<IpAddressType>{}</IpAddressType>", esc(t)));
679 }
680 out.push_str("<AnycastIps>");
681 for ip in &a.anycast_ips {
682 out.push_str(&format!("<AnycastIp>{}</AnycastIp>", esc(ip)));
683 }
684 out.push_str("</AnycastIps>");
685 out.push_str(&format!("<IpCount>{}</IpCount>", a.ip_count));
686 out.push_str(&format!(
687 "<LastModifiedTime>{}</LastModifiedTime>",
688 rfc3339(&a.last_modified_time)
689 ));
690 out.push_str("</AnycastIpList>");
691 out
692}
693
694fn push_anycast_summary(out: &mut String, a: &StoredAnycastIpList) {
695 out.push_str(&format!("<Id>{}</Id>", esc(&a.id)));
696 out.push_str(&format!("<Name>{}</Name>", esc(&a.name)));
697 out.push_str(&format!("<Status>{}</Status>", esc(&a.status)));
698 out.push_str(&format!("<Arn>{}</Arn>", esc(&a.arn)));
699 if let Some(t) = &a.ip_address_type {
700 out.push_str(&format!("<IpAddressType>{}</IpAddressType>", esc(t)));
701 }
702 out.push_str(&format!("<IpCount>{}</IpCount>", a.ip_count));
703 out.push_str(&format!(
704 "<LastModifiedTime>{}</LastModifiedTime>",
705 rfc3339(&a.last_modified_time)
706 ));
707}
708
709fn render_trust_store(t: &StoredTrustStore) -> String {
710 let mut out = String::with_capacity(512);
711 out.push_str(XML_DECL);
712 out.push_str(&format!("<TrustStore xmlns=\"{NS}\">"));
713 out.push_str(&format!("<Id>{}</Id>", esc(&t.id)));
714 out.push_str(&format!("<Arn>{}</Arn>", esc(&t.arn)));
715 out.push_str(&format!("<Name>{}</Name>", esc(&t.name)));
716 out.push_str(&format!(
717 "<LastModifiedTime>{}</LastModifiedTime>",
718 rfc3339(&t.last_modified_time)
719 ));
720 out.push_str(&render_bundle_source(&t.ca_certificates_bundle_source));
721 out.push_str("</TrustStore>");
722 out
723}
724
725fn render_bundle_source(s: &CaCertificatesBundleSource) -> String {
726 let mut out = String::with_capacity(256);
727 out.push_str("<CaCertificatesBundleSource>");
728 if let Some(s3) = &s.ca_certificates_bundle_s3_location {
729 out.push_str("<CaCertificatesBundleS3Location>");
730 out.push_str(&format!("<Bucket>{}</Bucket>", esc(&s3.bucket)));
731 out.push_str(&format!("<Key>{}</Key>", esc(&s3.key)));
732 out.push_str(&format!("<Region>{}</Region>", esc(&s3.region)));
733 if let Some(v) = &s3.version {
734 out.push_str(&format!("<Version>{}</Version>", esc(v)));
735 }
736 out.push_str("</CaCertificatesBundleS3Location>");
737 }
738 out.push_str("</CaCertificatesBundleSource>");
739 out
740}