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