Skip to main content

rustack_cloudfront_http/xml/
ser.rs

1//! Serialize domain types to restXml response bodies.
2
3use chrono::{DateTime, Utc};
4use rustack_cloudfront_model::{
5    CLOUDFRONT_XML_NAMESPACE, CachePolicy, CachePolicyConfig, CloudFrontFunction,
6    CloudFrontOriginAccessIdentity, CloudFrontOriginAccessIdentityConfig, CustomErrorResponse,
7    CustomHeader, Distribution, DistributionConfig, FieldLevelEncryption,
8    FieldLevelEncryptionProfile, FunctionConfig, Invalidation, KeyGroup, KeyValueStore, Origin,
9    OriginAccessControl, OriginAccessControlConfig, OriginRequestPolicy, OriginRequestPolicyConfig,
10    PublicKey, RealtimeLogConfig, ResponseHeadersPolicy, ResponseHeadersPolicyConfig, TagSet,
11};
12
13use super::XmlWriter;
14
15/// Serialize an ISO 8601 timestamp with millisecond precision.
16pub fn iso8601(ts: &DateTime<Utc>) -> String {
17    ts.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()
18}
19
20// ---------------------------------------------------------------------------
21// DistributionConfig writing helpers
22// ---------------------------------------------------------------------------
23
24/// Write `<DistributionConfig>` into `w` (element name supplied).
25pub fn write_distribution_config(w: &mut XmlWriter, cfg: &DistributionConfig, element_name: &str) {
26    w.open(element_name);
27    w.element("CallerReference", &cfg.caller_reference);
28    write_string_list(w, "Aliases", "CNAME", &cfg.aliases);
29    w.element("DefaultRootObject", &cfg.default_root_object);
30    write_origins(w, &cfg.origins);
31    write_origin_groups(w, &cfg.origin_groups);
32    write_default_cache_behavior(w, &cfg.default_cache_behavior);
33    write_cache_behaviors(w, &cfg.cache_behaviors);
34    write_custom_error_responses(w, &cfg.custom_error_responses);
35    w.element("Comment", &cfg.comment);
36    write_logging(w, &cfg);
37    w.optional_str("PriceClass", &cfg.price_class);
38    w.bool("Enabled", cfg.enabled);
39    write_viewer_certificate(w, cfg);
40    write_restrictions(w, cfg);
41    w.optional_str("WebACLId", &cfg.web_acl_id);
42    w.optional_str("HttpVersion", &cfg.http_version);
43    w.bool("IsIPV6Enabled", cfg.is_ipv6_enabled);
44    w.optional_str(
45        "ContinuousDeploymentPolicyId",
46        &cfg.continuous_deployment_policy_id,
47    );
48    w.bool("Staging", cfg.staging);
49    w.optional_str("AnycastIpListId", &cfg.anycast_ip_list_id);
50    w.optional_str("ConnectionMode", &cfg.connection_mode);
51    w.close(element_name);
52}
53
54fn write_string_list(w: &mut XmlWriter, wrapper: &str, item_name: &str, items: &[String]) {
55    w.open(wrapper);
56    w.element_display("Quantity", items.len());
57    if !items.is_empty() {
58        w.open("Items");
59        for it in items {
60            w.element(item_name, it);
61        }
62        w.close("Items");
63    }
64    w.close(wrapper);
65}
66
67fn write_origins(w: &mut XmlWriter, origins: &[Origin]) {
68    w.open("Origins");
69    w.element_display("Quantity", origins.len());
70    if !origins.is_empty() {
71        w.open("Items");
72        for o in origins {
73            write_origin(w, o);
74        }
75        w.close("Items");
76    }
77    w.close("Origins");
78}
79
80fn write_origin(w: &mut XmlWriter, o: &Origin) {
81    w.open("Origin");
82    w.element("Id", &o.id);
83    w.element("DomainName", &o.domain_name);
84    w.element("OriginPath", &o.origin_path);
85    write_custom_header_list(w, &o.custom_headers);
86    if let Some(s3) = &o.s3_origin_config {
87        w.open("S3OriginConfig");
88        w.element("OriginAccessIdentity", &s3.origin_access_identity);
89        w.close("S3OriginConfig");
90    }
91    if let Some(c) = &o.custom_origin_config {
92        w.open("CustomOriginConfig");
93        w.element_display("HTTPPort", c.http_port);
94        w.element_display("HTTPSPort", c.https_port);
95        w.element("OriginProtocolPolicy", &c.origin_protocol_policy);
96        write_string_list(
97            w,
98            "OriginSslProtocols",
99            "SslProtocol",
100            &c.origin_ssl_protocols,
101        );
102        w.element_display("OriginReadTimeout", c.origin_read_timeout);
103        w.element_display("OriginKeepaliveTimeout", c.origin_keepalive_timeout);
104        w.close("CustomOriginConfig");
105    }
106    w.element_display(
107        "ConnectionAttempts",
108        if o.connection_attempts == 0 {
109            3
110        } else {
111            o.connection_attempts
112        },
113    );
114    w.element_display(
115        "ConnectionTimeout",
116        if o.connection_timeout == 0 {
117            10
118        } else {
119            o.connection_timeout
120        },
121    );
122    if let Some(os) = &o.origin_shield {
123        w.open("OriginShield");
124        w.bool("Enabled", os.enabled);
125        w.optional_str("OriginShieldRegion", &os.origin_shield_region);
126        w.close("OriginShield");
127    }
128    w.element("OriginAccessControlId", &o.origin_access_control_id);
129    w.close("Origin");
130}
131
132fn write_custom_header_list(w: &mut XmlWriter, items: &[CustomHeader]) {
133    w.open("CustomHeaders");
134    w.element_display("Quantity", items.len());
135    if !items.is_empty() {
136        w.open("Items");
137        for h in items {
138            w.open("OriginCustomHeader");
139            w.element("HeaderName", &h.header_name);
140            w.element("HeaderValue", &h.header_value);
141            w.close("OriginCustomHeader");
142        }
143        w.close("Items");
144    }
145    w.close("CustomHeaders");
146}
147
148fn write_origin_groups(w: &mut XmlWriter, groups: &[rustack_cloudfront_model::OriginGroup]) {
149    w.open("OriginGroups");
150    w.element_display("Quantity", groups.len());
151    if !groups.is_empty() {
152        w.open("Items");
153        for g in groups {
154            w.open("OriginGroup");
155            w.element("Id", &g.id);
156            w.open("FailoverCriteria");
157            write_i32_list(w, "StatusCodes", "StatusCode", &g.failover_status_codes);
158            w.close("FailoverCriteria");
159            write_string_list(w, "Members", "OriginGroupMember", &g.member_origins);
160            w.optional_str("SelectionCriteria", &g.selection_criteria);
161            w.close("OriginGroup");
162        }
163        w.close("Items");
164    }
165    w.close("OriginGroups");
166}
167
168fn write_i32_list(w: &mut XmlWriter, wrapper: &str, item_name: &str, items: &[i32]) {
169    w.open(wrapper);
170    w.element_display("Quantity", items.len());
171    if !items.is_empty() {
172        w.open("Items");
173        for it in items {
174            w.element_display(item_name, it);
175        }
176        w.close("Items");
177    }
178    w.close(wrapper);
179}
180
181fn write_default_cache_behavior(w: &mut XmlWriter, cb: &rustack_cloudfront_model::CacheBehavior) {
182    w.open("DefaultCacheBehavior");
183    write_cache_behavior_common(w, cb);
184    w.close("DefaultCacheBehavior");
185}
186
187fn write_cache_behaviors(w: &mut XmlWriter, cbs: &[rustack_cloudfront_model::CacheBehavior]) {
188    w.open("CacheBehaviors");
189    w.element_display("Quantity", cbs.len());
190    if !cbs.is_empty() {
191        w.open("Items");
192        for cb in cbs {
193            w.open("CacheBehavior");
194            w.element("PathPattern", &cb.path_pattern);
195            write_cache_behavior_common(w, cb);
196            w.close("CacheBehavior");
197        }
198        w.close("Items");
199    }
200    w.close("CacheBehaviors");
201}
202
203fn write_cache_behavior_common(w: &mut XmlWriter, cb: &rustack_cloudfront_model::CacheBehavior) {
204    w.element("TargetOriginId", &cb.target_origin_id);
205    w.open("TrustedSigners");
206    w.bool("Enabled", cb.trusted_signers_enabled);
207    w.element_display("Quantity", cb.trusted_signers.len());
208    if !cb.trusted_signers.is_empty() {
209        w.open("Items");
210        for s in &cb.trusted_signers {
211            w.element("AwsAccountNumber", s);
212        }
213        w.close("Items");
214    }
215    w.close("TrustedSigners");
216    w.open("TrustedKeyGroups");
217    w.bool("Enabled", cb.trusted_key_groups_enabled);
218    w.element_display("Quantity", cb.trusted_key_groups.len());
219    if !cb.trusted_key_groups.is_empty() {
220        w.open("Items");
221        for k in &cb.trusted_key_groups {
222            w.element("KeyGroup", k);
223        }
224        w.close("Items");
225    }
226    w.close("TrustedKeyGroups");
227    w.element("ViewerProtocolPolicy", &cb.viewer_protocol_policy);
228    write_allowed_methods(w, &cb.allowed_methods, &cb.cached_methods);
229    w.bool("SmoothStreaming", cb.smooth_streaming);
230    w.bool("Compress", cb.compress);
231    write_lambda_associations(w, &cb.lambda_function_associations);
232    write_function_associations(w, &cb.function_associations);
233    w.optional_str("FieldLevelEncryptionId", &cb.field_level_encryption_id);
234    w.optional_str("RealtimeLogConfigArn", &cb.realtime_log_config_arn);
235    w.optional_str("CachePolicyId", &cb.cache_policy_id);
236    w.optional_str("OriginRequestPolicyId", &cb.origin_request_policy_id);
237    w.optional_str("ResponseHeadersPolicyId", &cb.response_headers_policy_id);
238    if cb.grpc_enabled {
239        w.open("GrpcConfig");
240        w.bool("Enabled", true);
241        w.close("GrpcConfig");
242    }
243    if let Some(fv) = &cb.forwarded_values {
244        w.open("ForwardedValues");
245        w.bool("QueryString", fv.query_string);
246        w.open("Cookies");
247        w.element("Forward", &fv.cookies.forward);
248        if !fv.cookies.whitelisted_names.is_empty() {
249            write_string_list(w, "WhitelistedNames", "Name", &fv.cookies.whitelisted_names);
250        }
251        w.close("Cookies");
252        write_string_list(w, "Headers", "Name", &fv.headers);
253        write_string_list(
254            w,
255            "QueryStringCacheKeys",
256            "Name",
257            &fv.query_string_cache_keys,
258        );
259        w.close("ForwardedValues");
260    }
261    w.element_display("MinTTL", cb.min_ttl);
262    w.element_display("DefaultTTL", cb.default_ttl);
263    w.element_display("MaxTTL", cb.max_ttl);
264}
265
266fn write_allowed_methods(w: &mut XmlWriter, allowed: &[String], cached: &[String]) {
267    w.open("AllowedMethods");
268    w.element_display("Quantity", allowed.len());
269    if !allowed.is_empty() {
270        w.open("Items");
271        for m in allowed {
272            w.element("Method", m);
273        }
274        w.close("Items");
275    }
276    w.open("CachedMethods");
277    w.element_display("Quantity", cached.len());
278    if !cached.is_empty() {
279        w.open("Items");
280        for m in cached {
281            w.element("Method", m);
282        }
283        w.close("Items");
284    }
285    w.close("CachedMethods");
286    w.close("AllowedMethods");
287}
288
289fn write_lambda_associations(
290    w: &mut XmlWriter,
291    items: &[rustack_cloudfront_model::LambdaFunctionAssociation],
292) {
293    w.open("LambdaFunctionAssociations");
294    w.element_display("Quantity", items.len());
295    if !items.is_empty() {
296        w.open("Items");
297        for l in items {
298            w.open("LambdaFunctionAssociation");
299            w.element("LambdaFunctionARN", &l.lambda_function_arn);
300            w.element("EventType", &l.event_type);
301            w.bool("IncludeBody", l.include_body);
302            w.close("LambdaFunctionAssociation");
303        }
304        w.close("Items");
305    }
306    w.close("LambdaFunctionAssociations");
307}
308
309fn write_function_associations(
310    w: &mut XmlWriter,
311    items: &[rustack_cloudfront_model::FunctionAssociation],
312) {
313    w.open("FunctionAssociations");
314    w.element_display("Quantity", items.len());
315    if !items.is_empty() {
316        w.open("Items");
317        for f in items {
318            w.open("FunctionAssociation");
319            w.element("FunctionARN", &f.function_arn);
320            w.element("EventType", &f.event_type);
321            w.close("FunctionAssociation");
322        }
323        w.close("Items");
324    }
325    w.close("FunctionAssociations");
326}
327
328fn write_custom_error_responses(w: &mut XmlWriter, items: &[CustomErrorResponse]) {
329    w.open("CustomErrorResponses");
330    w.element_display("Quantity", items.len());
331    if !items.is_empty() {
332        w.open("Items");
333        for c in items {
334            w.open("CustomErrorResponse");
335            w.element_display("ErrorCode", c.error_code);
336            w.optional_str("ResponsePagePath", &c.response_page_path);
337            w.optional_str("ResponseCode", &c.response_code);
338            w.element_display("ErrorCachingMinTTL", c.error_caching_min_ttl);
339            w.close("CustomErrorResponse");
340        }
341        w.close("Items");
342    }
343    w.close("CustomErrorResponses");
344}
345
346fn write_logging(w: &mut XmlWriter, cfg: &DistributionConfig) {
347    w.open("Logging");
348    w.bool("Enabled", cfg.logging.enabled);
349    w.bool("IncludeCookies", cfg.logging.include_cookies);
350    w.element("Bucket", &cfg.logging.bucket);
351    w.element("Prefix", &cfg.logging.prefix);
352    w.close("Logging");
353}
354
355fn write_viewer_certificate(w: &mut XmlWriter, cfg: &DistributionConfig) {
356    let vc = &cfg.viewer_certificate;
357    w.open("ViewerCertificate");
358    w.bool(
359        "CloudFrontDefaultCertificate",
360        vc.cloud_front_default_certificate
361            || !vc.acm_certificate_arn.is_empty()
362            || !vc.iam_certificate_id.is_empty()
363            || vc.cloud_front_default_certificate,
364    );
365    w.optional_str("IAMCertificateId", &vc.iam_certificate_id);
366    w.optional_str("ACMCertificateArn", &vc.acm_certificate_arn);
367    w.optional_str("SSLSupportMethod", &vc.ssl_support_method);
368    w.optional_str("MinimumProtocolVersion", &vc.minimum_protocol_version);
369    w.optional_str("Certificate", &vc.certificate);
370    w.optional_str("CertificateSource", &vc.certificate_source);
371    w.close("ViewerCertificate");
372}
373
374fn write_restrictions(w: &mut XmlWriter, cfg: &DistributionConfig) {
375    w.open("Restrictions");
376    w.open("GeoRestriction");
377    w.element(
378        "RestrictionType",
379        if cfg.restrictions.geo_restriction.restriction_type.is_empty() {
380            "none"
381        } else {
382            &cfg.restrictions.geo_restriction.restriction_type
383        },
384    );
385    write_string_list(
386        w,
387        "Items",
388        "Location",
389        &cfg.restrictions.geo_restriction.locations,
390    );
391    w.close("GeoRestriction");
392    w.close("Restrictions");
393}
394
395// ---------------------------------------------------------------------------
396// Top-level distribution response bodies
397// ---------------------------------------------------------------------------
398
399/// Serialize a distribution as a `<Distribution>` response.
400#[must_use]
401pub fn distribution_xml(d: &Distribution) -> String {
402    let mut w = XmlWriter::new();
403    w.declaration();
404    w.open_root("Distribution", Some(CLOUDFRONT_XML_NAMESPACE));
405    w.element("Id", &d.id);
406    w.element("ARN", &d.arn);
407    w.element("Status", d.status.as_wire());
408    w.element("LastModifiedTime", &iso8601(&d.last_modified_time));
409    w.element_display(
410        "InProgressInvalidationBatches",
411        d.in_progress_invalidation_batches,
412    );
413    w.element("DomainName", &d.domain_name);
414    w.open("ActiveTrustedSigners");
415    w.bool("Enabled", d.active_trusted_signers_enabled);
416    w.element_display("Quantity", 0);
417    w.close("ActiveTrustedSigners");
418    w.open("ActiveTrustedKeyGroups");
419    w.bool("Enabled", d.active_trusted_key_groups_enabled);
420    w.element_display("Quantity", 0);
421    w.close("ActiveTrustedKeyGroups");
422    write_distribution_config(&mut w, &d.config, "DistributionConfig");
423    w.close("Distribution");
424    w.finish()
425}
426
427/// Serialize bare `<DistributionConfig>` wrapped in the namespace.
428#[must_use]
429pub fn distribution_config_xml(cfg: &DistributionConfig) -> String {
430    let mut w = XmlWriter::new();
431    w.declaration();
432    w.buf_mut_push_namespace_open("DistributionConfig");
433    write_distribution_config_body(&mut w, cfg);
434    w.close("DistributionConfig");
435    w.finish()
436}
437
438/// Serialize a list response.
439#[must_use]
440pub fn distribution_list_xml(items: &[Distribution], max_items: i32) -> String {
441    let mut w = XmlWriter::new();
442    w.declaration();
443    w.open_root("DistributionList", Some(CLOUDFRONT_XML_NAMESPACE));
444    w.element("Marker", "");
445    w.element_display("MaxItems", max_items);
446    w.bool("IsTruncated", false);
447    w.element_display("Quantity", items.len());
448    if !items.is_empty() {
449        w.open("Items");
450        for d in items {
451            w.open("DistributionSummary");
452            w.element("Id", &d.id);
453            w.element("ARN", &d.arn);
454            w.element("Status", d.status.as_wire());
455            w.element("LastModifiedTime", &iso8601(&d.last_modified_time));
456            w.element("DomainName", &d.domain_name);
457            write_string_list(&mut w, "Aliases", "CNAME", &d.config.aliases);
458            write_origins(&mut w, &d.config.origins);
459            write_origin_groups(&mut w, &d.config.origin_groups);
460            write_default_cache_behavior(&mut w, &d.config.default_cache_behavior);
461            write_cache_behaviors(&mut w, &d.config.cache_behaviors);
462            write_custom_error_responses(&mut w, &d.config.custom_error_responses);
463            w.element("Comment", &d.config.comment);
464            w.optional_str("PriceClass", &d.config.price_class);
465            w.bool("Enabled", d.config.enabled);
466            write_viewer_certificate(&mut w, &d.config);
467            write_restrictions(&mut w, &d.config);
468            w.optional_str("WebACLId", &d.config.web_acl_id);
469            w.optional_str("HttpVersion", &d.config.http_version);
470            w.bool("IsIPV6Enabled", d.config.is_ipv6_enabled);
471            w.bool("Staging", d.config.staging);
472            w.close("DistributionSummary");
473        }
474        w.close("Items");
475    }
476    w.close("DistributionList");
477    w.finish()
478}
479
480// ---------------------------------------------------------------------------
481// Invalidation
482// ---------------------------------------------------------------------------
483
484/// Serialize an `<Invalidation>`.
485#[must_use]
486pub fn invalidation_xml(inv: &Invalidation) -> String {
487    let mut w = XmlWriter::new();
488    w.declaration();
489    w.open_root("Invalidation", Some(CLOUDFRONT_XML_NAMESPACE));
490    w.element("Id", &inv.id);
491    w.element("Status", inv.status.as_wire());
492    w.element("CreateTime", &iso8601(&inv.create_time));
493    w.open("InvalidationBatch");
494    write_string_list(&mut w, "Paths", "Path", &inv.batch.paths);
495    w.element("CallerReference", &inv.batch.caller_reference);
496    w.close("InvalidationBatch");
497    w.close("Invalidation");
498    w.finish()
499}
500
501/// List of invalidations (summary form).
502#[must_use]
503pub fn invalidation_list_xml(items: &[Invalidation], max_items: i32) -> String {
504    let mut w = XmlWriter::new();
505    w.declaration();
506    w.open_root("InvalidationList", Some(CLOUDFRONT_XML_NAMESPACE));
507    w.element("Marker", "");
508    w.element_display("MaxItems", max_items);
509    w.bool("IsTruncated", false);
510    w.element_display("Quantity", items.len());
511    if !items.is_empty() {
512        w.open("Items");
513        for inv in items {
514            w.open("InvalidationSummary");
515            w.element("Id", &inv.id);
516            w.element("CreateTime", &iso8601(&inv.create_time));
517            w.element("Status", inv.status.as_wire());
518            w.close("InvalidationSummary");
519        }
520        w.close("Items");
521    }
522    w.close("InvalidationList");
523    w.finish()
524}
525
526// ---------------------------------------------------------------------------
527// OAC
528// ---------------------------------------------------------------------------
529
530/// Serialize an OAC response.
531#[must_use]
532pub fn oac_xml(o: &OriginAccessControl) -> String {
533    let mut w = XmlWriter::new();
534    w.declaration();
535    w.open_root("OriginAccessControl", Some(CLOUDFRONT_XML_NAMESPACE));
536    w.element("Id", &o.id);
537    write_oac_config(&mut w, &o.config, "OriginAccessControlConfig");
538    w.close("OriginAccessControl");
539    w.finish()
540}
541
542/// Serialize OAC config response.
543#[must_use]
544pub fn oac_config_xml(cfg: &OriginAccessControlConfig) -> String {
545    let mut w = XmlWriter::new();
546    w.declaration();
547    write_oac_config_root(&mut w, cfg);
548    w.finish()
549}
550
551fn write_oac_config_root(w: &mut XmlWriter, cfg: &OriginAccessControlConfig) {
552    w.open_root("OriginAccessControlConfig", Some(CLOUDFRONT_XML_NAMESPACE));
553    write_oac_config_body(w, cfg);
554    w.close("OriginAccessControlConfig");
555}
556
557fn write_oac_config(w: &mut XmlWriter, cfg: &OriginAccessControlConfig, el: &str) {
558    w.open(el);
559    write_oac_config_body(w, cfg);
560    w.close(el);
561}
562
563fn write_oac_config_body(w: &mut XmlWriter, cfg: &OriginAccessControlConfig) {
564    w.element("Name", &cfg.name);
565    w.optional_str("Description", &cfg.description);
566    w.element("SigningProtocol", &cfg.signing_protocol);
567    w.element("SigningBehavior", &cfg.signing_behavior);
568    w.element(
569        "OriginAccessControlOriginType",
570        &cfg.origin_access_control_origin_type,
571    );
572}
573
574/// Serialize OAC list response.
575#[must_use]
576pub fn oac_list_xml(items: &[OriginAccessControl], max_items: i32) -> String {
577    let mut w = XmlWriter::new();
578    w.declaration();
579    w.open_root("OriginAccessControlList", Some(CLOUDFRONT_XML_NAMESPACE));
580    w.element("Marker", "");
581    w.element_display("MaxItems", max_items);
582    w.bool("IsTruncated", false);
583    w.element_display("Quantity", items.len());
584    if !items.is_empty() {
585        w.open("Items");
586        for o in items {
587            w.open("OriginAccessControlSummary");
588            w.element("Id", &o.id);
589            w.element("Name", &o.config.name);
590            w.element("Description", &o.config.description);
591            w.element("SigningProtocol", &o.config.signing_protocol);
592            w.element("SigningBehavior", &o.config.signing_behavior);
593            w.element(
594                "OriginAccessControlOriginType",
595                &o.config.origin_access_control_origin_type,
596            );
597            w.close("OriginAccessControlSummary");
598        }
599        w.close("Items");
600    }
601    w.close("OriginAccessControlList");
602    w.finish()
603}
604
605// ---------------------------------------------------------------------------
606// OAI
607// ---------------------------------------------------------------------------
608
609/// Serialize an OAI response.
610#[must_use]
611pub fn oai_xml(o: &CloudFrontOriginAccessIdentity) -> String {
612    let mut w = XmlWriter::new();
613    w.declaration();
614    w.open_root(
615        "CloudFrontOriginAccessIdentity",
616        Some(CLOUDFRONT_XML_NAMESPACE),
617    );
618    w.element("Id", &o.id);
619    w.element("S3CanonicalUserId", &o.s3_canonical_user_id);
620    w.open("CloudFrontOriginAccessIdentityConfig");
621    w.element("CallerReference", &o.config.caller_reference);
622    w.element("Comment", &o.config.comment);
623    w.close("CloudFrontOriginAccessIdentityConfig");
624    w.close("CloudFrontOriginAccessIdentity");
625    w.finish()
626}
627
628/// Serialize OAI config.
629#[must_use]
630pub fn oai_config_xml(cfg: &CloudFrontOriginAccessIdentityConfig) -> String {
631    let mut w = XmlWriter::new();
632    w.declaration();
633    w.open_root(
634        "CloudFrontOriginAccessIdentityConfig",
635        Some(CLOUDFRONT_XML_NAMESPACE),
636    );
637    w.element("CallerReference", &cfg.caller_reference);
638    w.element("Comment", &cfg.comment);
639    w.close("CloudFrontOriginAccessIdentityConfig");
640    w.finish()
641}
642
643/// Serialize OAI list.
644#[must_use]
645pub fn oai_list_xml(items: &[CloudFrontOriginAccessIdentity], max_items: i32) -> String {
646    let mut w = XmlWriter::new();
647    w.declaration();
648    w.open_root(
649        "CloudFrontOriginAccessIdentityList",
650        Some(CLOUDFRONT_XML_NAMESPACE),
651    );
652    w.element("Marker", "");
653    w.element_display("MaxItems", max_items);
654    w.bool("IsTruncated", false);
655    w.element_display("Quantity", items.len());
656    if !items.is_empty() {
657        w.open("Items");
658        for o in items {
659            w.open("CloudFrontOriginAccessIdentitySummary");
660            w.element("Id", &o.id);
661            w.element("S3CanonicalUserId", &o.s3_canonical_user_id);
662            w.element("Comment", &o.config.comment);
663            w.close("CloudFrontOriginAccessIdentitySummary");
664        }
665        w.close("Items");
666    }
667    w.close("CloudFrontOriginAccessIdentityList");
668    w.finish()
669}
670
671// ---------------------------------------------------------------------------
672// Cache / OriginRequest / ResponseHeaders policies
673// ---------------------------------------------------------------------------
674
675/// Serialize a cache policy.
676#[must_use]
677pub fn cache_policy_xml(p: &CachePolicy) -> String {
678    let mut w = XmlWriter::new();
679    w.declaration();
680    w.open_root("CachePolicy", Some(CLOUDFRONT_XML_NAMESPACE));
681    w.element("Id", &p.id);
682    w.element("LastModifiedTime", &iso8601(&p.last_modified_time));
683    w.open("CachePolicyConfig");
684    write_cache_policy_config_body(&mut w, &p.config);
685    w.close("CachePolicyConfig");
686    w.close("CachePolicy");
687    w.finish()
688}
689
690/// Serialize cache policy config.
691#[must_use]
692pub fn cache_policy_config_xml(cfg: &CachePolicyConfig) -> String {
693    let mut w = XmlWriter::new();
694    w.declaration();
695    w.open_root("CachePolicyConfig", Some(CLOUDFRONT_XML_NAMESPACE));
696    write_cache_policy_config_body(&mut w, cfg);
697    w.close("CachePolicyConfig");
698    w.finish()
699}
700
701fn write_cache_policy_config_body(w: &mut XmlWriter, cfg: &CachePolicyConfig) {
702    w.optional_str("Comment", &cfg.comment);
703    w.element("Name", &cfg.name);
704    w.element_display("DefaultTTL", cfg.default_ttl);
705    w.element_display("MaxTTL", cfg.max_ttl);
706    w.element_display("MinTTL", cfg.min_ttl);
707    w.open("ParametersInCacheKeyAndForwardedToOrigin");
708    let p = &cfg.parameters_in_cache_key_and_forwarded_to_origin;
709    w.bool("EnableAcceptEncodingGzip", p.enable_accept_encoding_gzip);
710    w.bool(
711        "EnableAcceptEncodingBrotli",
712        p.enable_accept_encoding_brotli,
713    );
714    w.open("HeadersConfig");
715    w.element(
716        "HeaderBehavior",
717        if p.headers_config.header_behavior.is_empty() {
718            "none"
719        } else {
720            &p.headers_config.header_behavior
721        },
722    );
723    write_string_list(w, "Headers", "Name", &p.headers_config.headers);
724    w.close("HeadersConfig");
725    w.open("CookiesConfig");
726    w.element(
727        "CookieBehavior",
728        if p.cookies_config.cookie_behavior.is_empty() {
729            "none"
730        } else {
731            &p.cookies_config.cookie_behavior
732        },
733    );
734    write_string_list(w, "Cookies", "Name", &p.cookies_config.cookies);
735    w.close("CookiesConfig");
736    w.open("QueryStringsConfig");
737    w.element(
738        "QueryStringBehavior",
739        if p.query_strings_config.query_string_behavior.is_empty() {
740            "none"
741        } else {
742            &p.query_strings_config.query_string_behavior
743        },
744    );
745    write_string_list(
746        w,
747        "QueryStrings",
748        "Name",
749        &p.query_strings_config.query_strings,
750    );
751    w.close("QueryStringsConfig");
752    w.close("ParametersInCacheKeyAndForwardedToOrigin");
753}
754
755/// Serialize cache policy list.
756#[must_use]
757pub fn cache_policy_list_xml(items: &[CachePolicy], max_items: i32) -> String {
758    let mut w = XmlWriter::new();
759    w.declaration();
760    w.open_root("CachePolicyList", Some(CLOUDFRONT_XML_NAMESPACE));
761    w.element("Marker", "");
762    w.element_display("MaxItems", max_items);
763    w.bool("IsTruncated", false);
764    w.element_display("Quantity", items.len());
765    if !items.is_empty() {
766        w.open("Items");
767        for p in items {
768            w.open("CachePolicySummary");
769            w.element("Type", if p.managed { "managed" } else { "custom" });
770            w.open("CachePolicy");
771            w.element("Id", &p.id);
772            w.element("LastModifiedTime", &iso8601(&p.last_modified_time));
773            w.open("CachePolicyConfig");
774            write_cache_policy_config_body(&mut w, &p.config);
775            w.close("CachePolicyConfig");
776            w.close("CachePolicy");
777            w.close("CachePolicySummary");
778        }
779        w.close("Items");
780    }
781    w.close("CachePolicyList");
782    w.finish()
783}
784
785/// Serialize an origin request policy.
786#[must_use]
787pub fn origin_request_policy_xml(p: &OriginRequestPolicy) -> String {
788    let mut w = XmlWriter::new();
789    w.declaration();
790    w.open_root("OriginRequestPolicy", Some(CLOUDFRONT_XML_NAMESPACE));
791    w.element("Id", &p.id);
792    w.element("LastModifiedTime", &iso8601(&p.last_modified_time));
793    w.open("OriginRequestPolicyConfig");
794    write_origin_request_policy_body(&mut w, &p.config);
795    w.close("OriginRequestPolicyConfig");
796    w.close("OriginRequestPolicy");
797    w.finish()
798}
799
800/// Serialize ORP config.
801#[must_use]
802pub fn origin_request_policy_config_xml(cfg: &OriginRequestPolicyConfig) -> String {
803    let mut w = XmlWriter::new();
804    w.declaration();
805    w.open_root("OriginRequestPolicyConfig", Some(CLOUDFRONT_XML_NAMESPACE));
806    write_origin_request_policy_body(&mut w, cfg);
807    w.close("OriginRequestPolicyConfig");
808    w.finish()
809}
810
811fn write_origin_request_policy_body(w: &mut XmlWriter, cfg: &OriginRequestPolicyConfig) {
812    w.optional_str("Comment", &cfg.comment);
813    w.element("Name", &cfg.name);
814    w.open("HeadersConfig");
815    w.element(
816        "HeaderBehavior",
817        if cfg.headers_config.header_behavior.is_empty() {
818            "none"
819        } else {
820            &cfg.headers_config.header_behavior
821        },
822    );
823    write_string_list(w, "Headers", "Name", &cfg.headers_config.headers);
824    w.close("HeadersConfig");
825    w.open("CookiesConfig");
826    w.element(
827        "CookieBehavior",
828        if cfg.cookies_config.cookie_behavior.is_empty() {
829            "none"
830        } else {
831            &cfg.cookies_config.cookie_behavior
832        },
833    );
834    write_string_list(w, "Cookies", "Name", &cfg.cookies_config.cookies);
835    w.close("CookiesConfig");
836    w.open("QueryStringsConfig");
837    w.element(
838        "QueryStringBehavior",
839        if cfg.query_strings_config.query_string_behavior.is_empty() {
840            "none"
841        } else {
842            &cfg.query_strings_config.query_string_behavior
843        },
844    );
845    write_string_list(
846        w,
847        "QueryStrings",
848        "Name",
849        &cfg.query_strings_config.query_strings,
850    );
851    w.close("QueryStringsConfig");
852}
853
854/// Serialize ORP list.
855#[must_use]
856pub fn origin_request_policy_list_xml(items: &[OriginRequestPolicy], max_items: i32) -> String {
857    let mut w = XmlWriter::new();
858    w.declaration();
859    w.open_root("OriginRequestPolicyList", Some(CLOUDFRONT_XML_NAMESPACE));
860    w.element("Marker", "");
861    w.element_display("MaxItems", max_items);
862    w.bool("IsTruncated", false);
863    w.element_display("Quantity", items.len());
864    if !items.is_empty() {
865        w.open("Items");
866        for p in items {
867            w.open("OriginRequestPolicySummary");
868            w.element("Type", if p.managed { "managed" } else { "custom" });
869            w.open("OriginRequestPolicy");
870            w.element("Id", &p.id);
871            w.element("LastModifiedTime", &iso8601(&p.last_modified_time));
872            w.open("OriginRequestPolicyConfig");
873            write_origin_request_policy_body(&mut w, &p.config);
874            w.close("OriginRequestPolicyConfig");
875            w.close("OriginRequestPolicy");
876            w.close("OriginRequestPolicySummary");
877        }
878        w.close("Items");
879    }
880    w.close("OriginRequestPolicyList");
881    w.finish()
882}
883
884/// Serialize a response-headers policy.
885#[must_use]
886pub fn response_headers_policy_xml(p: &ResponseHeadersPolicy) -> String {
887    let mut w = XmlWriter::new();
888    w.declaration();
889    w.open_root("ResponseHeadersPolicy", Some(CLOUDFRONT_XML_NAMESPACE));
890    w.element("Id", &p.id);
891    w.element("LastModifiedTime", &iso8601(&p.last_modified_time));
892    w.open("ResponseHeadersPolicyConfig");
893    write_response_headers_policy_body(&mut w, &p.config);
894    w.close("ResponseHeadersPolicyConfig");
895    w.close("ResponseHeadersPolicy");
896    w.finish()
897}
898
899/// Serialize RHP config.
900#[must_use]
901pub fn response_headers_policy_config_xml(cfg: &ResponseHeadersPolicyConfig) -> String {
902    let mut w = XmlWriter::new();
903    w.declaration();
904    w.open_root(
905        "ResponseHeadersPolicyConfig",
906        Some(CLOUDFRONT_XML_NAMESPACE),
907    );
908    write_response_headers_policy_body(&mut w, cfg);
909    w.close("ResponseHeadersPolicyConfig");
910    w.finish()
911}
912
913fn write_response_headers_policy_body(w: &mut XmlWriter, cfg: &ResponseHeadersPolicyConfig) {
914    w.optional_str("Comment", &cfg.comment);
915    w.element("Name", &cfg.name);
916    // Emit a minimal but complete shape. Details omitted for brevity; managed
917    // policies still round-trip correctly.
918    w.open("CorsConfig");
919    if let Some(c) = &cfg.cors_config {
920        w.bool(
921            "AccessControlAllowCredentials",
922            c.access_control_allow_credentials,
923        );
924        write_string_list(
925            w,
926            "AccessControlAllowHeaders",
927            "Header",
928            &c.access_control_allow_headers,
929        );
930        write_string_list(
931            w,
932            "AccessControlAllowMethods",
933            "Method",
934            &c.access_control_allow_methods,
935        );
936        write_string_list(
937            w,
938            "AccessControlAllowOrigins",
939            "Origin",
940            &c.access_control_allow_origins,
941        );
942        write_string_list(
943            w,
944            "AccessControlExposeHeaders",
945            "Header",
946            &c.access_control_expose_headers,
947        );
948        w.element_display("AccessControlMaxAgeSec", c.access_control_max_age_sec);
949        w.bool("OriginOverride", c.origin_override);
950    } else {
951        w.bool("AccessControlAllowCredentials", false);
952        w.element_display("AccessControlMaxAgeSec", 0);
953        w.bool("OriginOverride", false);
954    }
955    w.close("CorsConfig");
956    w.open("SecurityHeadersConfig");
957    w.close("SecurityHeadersConfig");
958    w.open("ServerTimingHeadersConfig");
959    if let Some(s) = &cfg.server_timing_headers_config {
960        w.bool("Enabled", s.enabled);
961        w.element_display("SamplingRate", s.sampling_rate);
962    } else {
963        w.bool("Enabled", false);
964    }
965    w.close("ServerTimingHeadersConfig");
966    write_string_list(
967        w,
968        "RemoveHeadersConfig",
969        "ResponseHeadersPolicyRemoveHeader",
970        &cfg.remove_headers_config,
971    );
972    w.open("CustomHeadersConfig");
973    w.element_display("Quantity", cfg.custom_headers_config.len());
974    if !cfg.custom_headers_config.is_empty() {
975        w.open("Items");
976        for h in &cfg.custom_headers_config {
977            w.open("ResponseHeadersPolicyCustomHeader");
978            w.element("Header", &h.header);
979            w.element("Value", &h.value);
980            w.bool("Override", h.override_upstream);
981            w.close("ResponseHeadersPolicyCustomHeader");
982        }
983        w.close("Items");
984    }
985    w.close("CustomHeadersConfig");
986}
987
988/// Serialize RHP list.
989#[must_use]
990pub fn response_headers_policy_list_xml(items: &[ResponseHeadersPolicy], max_items: i32) -> String {
991    let mut w = XmlWriter::new();
992    w.declaration();
993    w.open_root("ResponseHeadersPolicyList", Some(CLOUDFRONT_XML_NAMESPACE));
994    w.element("Marker", "");
995    w.element_display("MaxItems", max_items);
996    w.bool("IsTruncated", false);
997    w.element_display("Quantity", items.len());
998    if !items.is_empty() {
999        w.open("Items");
1000        for p in items {
1001            w.open("ResponseHeadersPolicySummary");
1002            w.element("Type", if p.managed { "managed" } else { "custom" });
1003            w.open("ResponseHeadersPolicy");
1004            w.element("Id", &p.id);
1005            w.element("LastModifiedTime", &iso8601(&p.last_modified_time));
1006            w.open("ResponseHeadersPolicyConfig");
1007            write_response_headers_policy_body(&mut w, &p.config);
1008            w.close("ResponseHeadersPolicyConfig");
1009            w.close("ResponseHeadersPolicy");
1010            w.close("ResponseHeadersPolicySummary");
1011        }
1012        w.close("Items");
1013    }
1014    w.close("ResponseHeadersPolicyList");
1015    w.finish()
1016}
1017
1018// ---------------------------------------------------------------------------
1019// Key group / public key / function (minimal summaries)
1020// ---------------------------------------------------------------------------
1021
1022/// Serialize a key group.
1023#[must_use]
1024pub fn key_group_xml(kg: &KeyGroup) -> String {
1025    let mut w = XmlWriter::new();
1026    w.declaration();
1027    w.open_root("KeyGroup", Some(CLOUDFRONT_XML_NAMESPACE));
1028    w.element("Id", &kg.id);
1029    w.element("LastModifiedTime", &iso8601(&kg.last_modified_time));
1030    w.open("KeyGroupConfig");
1031    w.element("Name", &kg.config.name);
1032    write_string_list(&mut w, "Items", "PublicKey", &kg.config.items);
1033    w.element("Comment", &kg.config.comment);
1034    w.close("KeyGroupConfig");
1035    w.close("KeyGroup");
1036    w.finish()
1037}
1038
1039/// Serialize a key group list.
1040#[must_use]
1041pub fn key_group_list_xml(items: &[KeyGroup], max_items: i32) -> String {
1042    let mut w = XmlWriter::new();
1043    w.declaration();
1044    w.open_root("KeyGroupList", Some(CLOUDFRONT_XML_NAMESPACE));
1045    w.element_display("MaxItems", max_items);
1046    w.element_display("Quantity", items.len());
1047    if !items.is_empty() {
1048        w.open("Items");
1049        for kg in items {
1050            w.open("KeyGroupSummary");
1051            w.open("KeyGroup");
1052            w.element("Id", &kg.id);
1053            w.element("LastModifiedTime", &iso8601(&kg.last_modified_time));
1054            w.open("KeyGroupConfig");
1055            w.element("Name", &kg.config.name);
1056            write_string_list(&mut w, "Items", "PublicKey", &kg.config.items);
1057            w.element("Comment", &kg.config.comment);
1058            w.close("KeyGroupConfig");
1059            w.close("KeyGroup");
1060            w.close("KeyGroupSummary");
1061        }
1062        w.close("Items");
1063    }
1064    w.close("KeyGroupList");
1065    w.finish()
1066}
1067
1068/// Serialize a public key.
1069#[must_use]
1070pub fn public_key_xml(k: &PublicKey) -> String {
1071    let mut w = XmlWriter::new();
1072    w.declaration();
1073    w.open_root("PublicKey", Some(CLOUDFRONT_XML_NAMESPACE));
1074    w.element("Id", &k.id);
1075    w.element("CreatedTime", &iso8601(&k.created_time));
1076    w.open("PublicKeyConfig");
1077    w.element("CallerReference", &k.config.caller_reference);
1078    w.element("Name", &k.config.name);
1079    w.element("EncodedKey", &k.config.encoded_key);
1080    w.element("Comment", &k.config.comment);
1081    w.close("PublicKeyConfig");
1082    w.close("PublicKey");
1083    w.finish()
1084}
1085
1086/// Serialize public key list.
1087#[must_use]
1088pub fn public_key_list_xml(items: &[PublicKey], max_items: i32) -> String {
1089    let mut w = XmlWriter::new();
1090    w.declaration();
1091    w.open_root("PublicKeyList", Some(CLOUDFRONT_XML_NAMESPACE));
1092    w.element_display("MaxItems", max_items);
1093    w.element_display("Quantity", items.len());
1094    if !items.is_empty() {
1095        w.open("Items");
1096        for k in items {
1097            w.open("PublicKeySummary");
1098            w.element("Id", &k.id);
1099            w.element("Name", &k.config.name);
1100            w.element("CreatedTime", &iso8601(&k.created_time));
1101            w.element("EncodedKey", &k.config.encoded_key);
1102            w.element("Comment", &k.config.comment);
1103            w.close("PublicKeySummary");
1104        }
1105        w.close("Items");
1106    }
1107    w.close("PublicKeyList");
1108    w.finish()
1109}
1110
1111/// Serialize a function (metadata-only response).
1112#[must_use]
1113pub fn function_xml(f: &CloudFrontFunction) -> String {
1114    let mut w = XmlWriter::new();
1115    w.declaration();
1116    w.open_root("FunctionSummary", Some(CLOUDFRONT_XML_NAMESPACE));
1117    w.element("Name", &f.name);
1118    w.element("Status", &f.status);
1119    write_function_metadata(&mut w, f);
1120    write_function_config(&mut w, &f.config);
1121    w.close("FunctionSummary");
1122    w.finish()
1123}
1124
1125fn write_function_metadata(w: &mut XmlWriter, f: &CloudFrontFunction) {
1126    w.open("FunctionMetadata");
1127    w.element("FunctionARN", &f.metadata.function_arn);
1128    w.element("Stage", &f.metadata.stage);
1129    w.element("CreatedTime", &iso8601(&f.metadata.created_time));
1130    w.element("LastModifiedTime", &iso8601(&f.metadata.last_modified_time));
1131    w.close("FunctionMetadata");
1132}
1133
1134fn write_function_config(w: &mut XmlWriter, cfg: &FunctionConfig) {
1135    w.open("FunctionConfig");
1136    w.element("Comment", &cfg.comment);
1137    w.element("Runtime", &cfg.runtime);
1138    w.close("FunctionConfig");
1139}
1140
1141/// Serialize function list.
1142#[must_use]
1143pub fn function_list_xml(items: &[CloudFrontFunction], max_items: i32) -> String {
1144    let mut w = XmlWriter::new();
1145    w.declaration();
1146    w.open_root("FunctionList", Some(CLOUDFRONT_XML_NAMESPACE));
1147    w.element_display("MaxItems", max_items);
1148    w.element_display("Quantity", items.len());
1149    if !items.is_empty() {
1150        w.open("Items");
1151        for f in items {
1152            w.open("FunctionSummary");
1153            w.element("Name", &f.name);
1154            w.element("Status", &f.status);
1155            write_function_metadata(&mut w, f);
1156            write_function_config(&mut w, &f.config);
1157            w.close("FunctionSummary");
1158        }
1159        w.close("Items");
1160    }
1161    w.close("FunctionList");
1162    w.finish()
1163}
1164
1165// ---------------------------------------------------------------------------
1166// FLE / KVS / RealtimeLog (minimal)
1167// ---------------------------------------------------------------------------
1168
1169/// Serialize FLE config.
1170#[must_use]
1171pub fn fle_xml(f: &FieldLevelEncryption) -> String {
1172    let mut w = XmlWriter::new();
1173    w.declaration();
1174    w.open_root("FieldLevelEncryption", Some(CLOUDFRONT_XML_NAMESPACE));
1175    w.element("Id", &f.id);
1176    w.element("LastModifiedTime", &iso8601(&f.last_modified_time));
1177    w.open("FieldLevelEncryptionConfig");
1178    w.element("CallerReference", &f.config.caller_reference);
1179    w.element("Comment", &f.config.comment);
1180    w.close("FieldLevelEncryptionConfig");
1181    w.close("FieldLevelEncryption");
1182    w.finish()
1183}
1184
1185/// Serialize FLE profile.
1186#[must_use]
1187pub fn fle_profile_xml(f: &FieldLevelEncryptionProfile) -> String {
1188    let mut w = XmlWriter::new();
1189    w.declaration();
1190    w.open_root(
1191        "FieldLevelEncryptionProfile",
1192        Some(CLOUDFRONT_XML_NAMESPACE),
1193    );
1194    w.element("Id", &f.id);
1195    w.element("LastModifiedTime", &iso8601(&f.last_modified_time));
1196    w.open("FieldLevelEncryptionProfileConfig");
1197    w.element("Name", &f.config.name);
1198    w.element("CallerReference", &f.config.caller_reference);
1199    w.element("Comment", &f.config.comment);
1200    w.close("FieldLevelEncryptionProfileConfig");
1201    w.close("FieldLevelEncryptionProfile");
1202    w.finish()
1203}
1204
1205/// Serialize KVS.
1206#[must_use]
1207pub fn kvs_xml(k: &KeyValueStore) -> String {
1208    let mut w = XmlWriter::new();
1209    w.declaration();
1210    w.open_root("KeyValueStore", Some(CLOUDFRONT_XML_NAMESPACE));
1211    w.element("Name", &k.name);
1212    w.element("Id", &k.id);
1213    w.element("Comment", &k.comment);
1214    w.element("ARN", &k.arn);
1215    w.element("Status", &k.status);
1216    w.element("LastModifiedTime", &iso8601(&k.last_modified_time));
1217    w.close("KeyValueStore");
1218    w.finish()
1219}
1220
1221/// Serialize realtime log config.
1222#[must_use]
1223pub fn realtime_log_config_xml(r: &RealtimeLogConfig) -> String {
1224    let mut w = XmlWriter::new();
1225    w.declaration();
1226    w.open_root("RealtimeLogConfig", Some(CLOUDFRONT_XML_NAMESPACE));
1227    w.element("ARN", &r.arn);
1228    w.element("Name", &r.name);
1229    w.element_display("SamplingRate", r.sampling_rate);
1230    write_string_list(&mut w, "Fields", "Field", &r.fields);
1231    w.open("EndPoints");
1232    for ep in &r.end_points {
1233        w.open("EndPoint");
1234        w.element("StreamType", &ep.stream_type);
1235        w.open("KinesisStreamConfig");
1236        w.element("RoleARN", &ep.kinesis_stream_config.role_arn);
1237        w.element("StreamARN", &ep.kinesis_stream_config.stream_arn);
1238        w.close("KinesisStreamConfig");
1239        w.close("EndPoint");
1240    }
1241    w.close("EndPoints");
1242    w.close("RealtimeLogConfig");
1243    w.finish()
1244}
1245
1246// ---------------------------------------------------------------------------
1247// Tagging
1248// ---------------------------------------------------------------------------
1249
1250/// Serialize `<Tags>` block.
1251#[must_use]
1252pub fn tags_xml(tags: &TagSet) -> String {
1253    let mut w = XmlWriter::new();
1254    w.declaration();
1255    w.open_root("Tags", Some(CLOUDFRONT_XML_NAMESPACE));
1256    w.open("Items");
1257    for t in tags {
1258        w.open("Tag");
1259        w.element("Key", &t.key);
1260        w.element("Value", &t.value);
1261        w.close("Tag");
1262    }
1263    w.close("Items");
1264    w.close("Tags");
1265    w.finish()
1266}
1267
1268// ---------------------------------------------------------------------------
1269// Error
1270// ---------------------------------------------------------------------------
1271
1272/// Serialize a CloudFront error response (wrapped in `<ErrorResponse>`).
1273#[must_use]
1274pub fn error_xml(code: &str, message: &str, request_id: &str) -> String {
1275    let mut w = XmlWriter::new();
1276    w.declaration();
1277    w.open_root("ErrorResponse", Some(CLOUDFRONT_XML_NAMESPACE));
1278    w.open("Error");
1279    w.element("Type", "Sender");
1280    w.element("Code", code);
1281    w.element("Message", message);
1282    w.close("Error");
1283    w.element("RequestId", request_id);
1284    w.close("ErrorResponse");
1285    w.finish()
1286}
1287
1288// ---------------------------------------------------------------------------
1289// Helper used by distribution_config_xml
1290// ---------------------------------------------------------------------------
1291
1292impl XmlWriter {
1293    /// Open the root element with the CloudFront namespace.
1294    pub fn buf_mut_push_namespace_open(&mut self, name: &str) {
1295        self.open_root(name, Some(CLOUDFRONT_XML_NAMESPACE));
1296    }
1297}
1298
1299fn write_distribution_config_body(w: &mut XmlWriter, cfg: &DistributionConfig) {
1300    w.element("CallerReference", &cfg.caller_reference);
1301    write_string_list(w, "Aliases", "CNAME", &cfg.aliases);
1302    w.element("DefaultRootObject", &cfg.default_root_object);
1303    write_origins(w, &cfg.origins);
1304    write_origin_groups(w, &cfg.origin_groups);
1305    write_default_cache_behavior(w, &cfg.default_cache_behavior);
1306    write_cache_behaviors(w, &cfg.cache_behaviors);
1307    write_custom_error_responses(w, &cfg.custom_error_responses);
1308    w.element("Comment", &cfg.comment);
1309    write_logging(w, cfg);
1310    w.optional_str("PriceClass", &cfg.price_class);
1311    w.bool("Enabled", cfg.enabled);
1312    write_viewer_certificate(w, cfg);
1313    write_restrictions(w, cfg);
1314    w.optional_str("WebACLId", &cfg.web_acl_id);
1315    w.optional_str("HttpVersion", &cfg.http_version);
1316    w.bool("IsIPV6Enabled", cfg.is_ipv6_enabled);
1317    w.bool("Staging", cfg.staging);
1318}