1use chrono::{DateTime, Utc};
12use http::header::{ETAG, IF_MATCH, LOCATION};
13use http::{HeaderMap, HeaderValue, StatusCode};
14use serde::{Deserialize, Serialize};
15
16use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError, ResponseBody};
17
18use crate::router::Route;
19use crate::service::{aws_error, esc, invalid_argument, xml_response};
20use crate::state::AccountState;
21
22const XML_DECL: &str = r#"<?xml version="1.0" encoding="UTF-8"?>"#;
23const NS: &str = crate::NAMESPACE;
24
25fn skip_if_none<T>(x: &Option<T>) -> bool {
26 x.is_none()
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32#[serde(rename_all = "PascalCase")]
33pub struct OriginAccessControlConfig {
34 pub name: String,
35 #[serde(default, skip_serializing_if = "skip_if_none")]
36 pub description: Option<String>,
37 pub signing_protocol: String,
38 pub signing_behavior: String,
39 pub origin_access_control_origin_type: String,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct StoredOriginAccessControl {
44 pub id: String,
45 pub etag: String,
46 pub config: OriginAccessControlConfig,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, Default)]
52#[serde(rename_all = "PascalCase")]
53pub struct CachePolicyConfig {
54 #[serde(default, skip_serializing_if = "skip_if_none")]
55 pub comment: Option<String>,
56 pub name: String,
57 #[serde(rename = "DefaultTTL", default, skip_serializing_if = "skip_if_none")]
58 pub default_ttl: Option<i64>,
59 #[serde(rename = "MaxTTL", default, skip_serializing_if = "skip_if_none")]
60 pub max_ttl: Option<i64>,
61 #[serde(rename = "MinTTL")]
62 pub min_ttl: i64,
63 #[serde(default, skip_serializing_if = "skip_if_none")]
64 pub parameters_in_cache_key_and_forwarded_to_origin:
65 Option<ParametersInCacheKeyAndForwardedToOrigin>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, Default)]
69#[serde(rename_all = "PascalCase")]
70pub struct ParametersInCacheKeyAndForwardedToOrigin {
71 pub enable_accept_encoding_gzip: bool,
72 #[serde(default, skip_serializing_if = "skip_if_none")]
73 pub enable_accept_encoding_brotli: Option<bool>,
74 pub headers_config: CachePolicyHeadersConfig,
75 pub cookies_config: CachePolicyCookiesConfig,
76 pub query_strings_config: CachePolicyQueryStringsConfig,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, Default)]
80#[serde(rename_all = "PascalCase")]
81pub struct CachePolicyHeadersConfig {
82 pub header_behavior: String,
83 #[serde(default, skip_serializing_if = "skip_if_none")]
84 pub headers: Option<NameWrapper>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, Default)]
88#[serde(rename_all = "PascalCase")]
89pub struct NameWrapper {
90 pub quantity: i32,
91 #[serde(default, skip_serializing_if = "skip_if_none")]
92 pub items: Option<NameItems>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, Default)]
96#[serde(rename_all = "PascalCase")]
97pub struct NameItems {
98 #[serde(default, rename = "Name")]
99 pub name: Vec<String>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
103#[serde(rename_all = "PascalCase")]
104pub struct CachePolicyCookiesConfig {
105 pub cookie_behavior: String,
106 #[serde(default, skip_serializing_if = "skip_if_none")]
107 pub cookies: Option<NameWrapper>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, Default)]
111#[serde(rename_all = "PascalCase")]
112pub struct CachePolicyQueryStringsConfig {
113 pub query_string_behavior: String,
114 #[serde(default, skip_serializing_if = "skip_if_none")]
115 pub query_strings: Option<NameWrapper>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct StoredCachePolicy {
120 pub id: String,
121 pub etag: String,
122 pub last_modified_time: DateTime<Utc>,
123 pub config: CachePolicyConfig,
124 pub policy_type: String,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131#[serde(rename_all = "PascalCase")]
132pub struct OriginRequestPolicyConfig {
133 #[serde(default, skip_serializing_if = "skip_if_none")]
134 pub comment: Option<String>,
135 pub name: String,
136 pub headers_config: OriginRequestPolicyHeadersConfig,
137 pub cookies_config: OriginRequestPolicyCookiesConfig,
138 pub query_strings_config: OriginRequestPolicyQueryStringsConfig,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, Default)]
142#[serde(rename_all = "PascalCase")]
143pub struct OriginRequestPolicyHeadersConfig {
144 pub header_behavior: String,
145 #[serde(default, skip_serializing_if = "skip_if_none")]
146 pub headers: Option<NameWrapper>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, Default)]
150#[serde(rename_all = "PascalCase")]
151pub struct OriginRequestPolicyCookiesConfig {
152 pub cookie_behavior: String,
153 #[serde(default, skip_serializing_if = "skip_if_none")]
154 pub cookies: Option<NameWrapper>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, Default)]
158#[serde(rename_all = "PascalCase")]
159pub struct OriginRequestPolicyQueryStringsConfig {
160 pub query_string_behavior: String,
161 #[serde(default, skip_serializing_if = "skip_if_none")]
162 pub query_strings: Option<NameWrapper>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct StoredOriginRequestPolicy {
167 pub id: String,
168 pub etag: String,
169 pub last_modified_time: DateTime<Utc>,
170 pub config: OriginRequestPolicyConfig,
171 pub policy_type: String,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize, Default)]
177#[serde(rename_all = "PascalCase")]
178pub struct ResponseHeadersPolicyConfig {
179 #[serde(default, skip_serializing_if = "skip_if_none")]
180 pub comment: Option<String>,
181 pub name: String,
182 #[serde(default, skip_serializing_if = "skip_if_none")]
183 pub cors_config: Option<ResponseHeadersPolicyCorsConfig>,
184 #[serde(default, skip_serializing_if = "skip_if_none")]
185 pub security_headers_config: Option<ResponseHeadersPolicySecurityHeadersConfig>,
186 #[serde(default, skip_serializing_if = "skip_if_none")]
187 pub server_timing_headers_config: Option<ResponseHeadersPolicyServerTimingHeadersConfig>,
188 #[serde(default, skip_serializing_if = "skip_if_none")]
189 pub custom_headers_config: Option<ResponseHeadersPolicyCustomHeadersConfig>,
190 #[serde(default, skip_serializing_if = "skip_if_none")]
191 pub remove_headers_config: Option<ResponseHeadersPolicyRemoveHeadersConfig>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, Default)]
195#[serde(rename_all = "PascalCase")]
196pub struct ResponseHeadersPolicyCorsConfig {
197 pub access_control_allow_origins: NameWrapper,
198 pub access_control_allow_headers: NameWrapper,
199 pub access_control_allow_methods: ResponseHeadersPolicyAccessControlAllowMethods,
200 pub access_control_allow_credentials: bool,
201 #[serde(default, skip_serializing_if = "skip_if_none")]
202 pub access_control_expose_headers: Option<NameWrapper>,
203 #[serde(default, skip_serializing_if = "skip_if_none")]
204 pub access_control_max_age_sec: Option<i32>,
205 pub origin_override: bool,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize, Default)]
209#[serde(rename_all = "PascalCase")]
210pub struct ResponseHeadersPolicyAccessControlAllowMethods {
211 pub quantity: i32,
212 pub items: ResponseHeadersPolicyMethodItems,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize, Default)]
216#[serde(rename_all = "PascalCase")]
217pub struct ResponseHeadersPolicyMethodItems {
218 #[serde(default, rename = "Method")]
219 pub method: Vec<String>,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize, Default)]
223#[serde(rename_all = "PascalCase")]
224pub struct ResponseHeadersPolicySecurityHeadersConfig {
225 #[serde(default, skip_serializing_if = "skip_if_none")]
226 pub xss_protection: Option<XssProtection>,
227 #[serde(default, skip_serializing_if = "skip_if_none")]
228 pub frame_options: Option<FrameOptions>,
229 #[serde(default, skip_serializing_if = "skip_if_none")]
230 pub referrer_policy: Option<ReferrerPolicy>,
231 #[serde(default, skip_serializing_if = "skip_if_none")]
232 pub content_security_policy: Option<ContentSecurityPolicy>,
233 #[serde(default, skip_serializing_if = "skip_if_none")]
234 pub content_type_options: Option<ContentTypeOptions>,
235 #[serde(default, skip_serializing_if = "skip_if_none")]
236 pub strict_transport_security: Option<StrictTransportSecurity>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, Default)]
240#[serde(rename_all = "PascalCase")]
241pub struct XssProtection {
242 #[serde(rename = "Override")]
243 pub override_: bool,
244 pub protection: bool,
245 #[serde(default, skip_serializing_if = "skip_if_none")]
246 pub mode_block: Option<bool>,
247 #[serde(default, skip_serializing_if = "skip_if_none")]
248 pub report_uri: Option<String>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize, Default)]
252#[serde(rename_all = "PascalCase")]
253pub struct FrameOptions {
254 #[serde(rename = "Override")]
255 pub override_: bool,
256 pub frame_option: String,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, Default)]
260#[serde(rename_all = "PascalCase")]
261pub struct ReferrerPolicy {
262 #[serde(rename = "Override")]
263 pub override_: bool,
264 pub referrer_policy: String,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize, Default)]
268#[serde(rename_all = "PascalCase")]
269pub struct ContentSecurityPolicy {
270 #[serde(rename = "Override")]
271 pub override_: bool,
272 pub content_security_policy: String,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize, Default)]
276#[serde(rename_all = "PascalCase")]
277pub struct ContentTypeOptions {
278 #[serde(rename = "Override")]
279 pub override_: bool,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize, Default)]
283#[serde(rename_all = "PascalCase")]
284pub struct StrictTransportSecurity {
285 #[serde(rename = "Override")]
286 pub override_: bool,
287 pub access_control_max_age_sec: i32,
288 #[serde(default, skip_serializing_if = "skip_if_none")]
289 pub include_subdomains: Option<bool>,
290 #[serde(default, skip_serializing_if = "skip_if_none")]
291 pub preload: Option<bool>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, Default)]
295#[serde(rename_all = "PascalCase")]
296pub struct ResponseHeadersPolicyServerTimingHeadersConfig {
297 pub enabled: bool,
298 #[serde(default, skip_serializing_if = "skip_if_none")]
299 pub sampling_rate: Option<f64>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, Default)]
303#[serde(rename_all = "PascalCase")]
304pub struct ResponseHeadersPolicyCustomHeadersConfig {
305 pub quantity: i32,
306 #[serde(default, skip_serializing_if = "skip_if_none")]
307 pub items: Option<ResponseHeadersPolicyCustomHeaderItems>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize, Default)]
311#[serde(rename_all = "PascalCase")]
312pub struct ResponseHeadersPolicyCustomHeaderItems {
313 #[serde(default, rename = "ResponseHeadersPolicyCustomHeader")]
314 pub response_headers_policy_custom_header: Vec<ResponseHeadersPolicyCustomHeader>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, Default)]
318#[serde(rename_all = "PascalCase")]
319pub struct ResponseHeadersPolicyCustomHeader {
320 pub header: String,
321 pub value: String,
322 #[serde(rename = "Override")]
323 pub override_: bool,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize, Default)]
327#[serde(rename_all = "PascalCase")]
328pub struct ResponseHeadersPolicyRemoveHeadersConfig {
329 pub quantity: i32,
330 #[serde(default, skip_serializing_if = "skip_if_none")]
331 pub items: Option<ResponseHeadersPolicyRemoveHeaderItems>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize, Default)]
335#[serde(rename_all = "PascalCase")]
336pub struct ResponseHeadersPolicyRemoveHeaderItems {
337 #[serde(default, rename = "ResponseHeadersPolicyRemoveHeader")]
338 pub response_headers_policy_remove_header: Vec<ResponseHeadersPolicyRemoveHeader>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, Default)]
342#[serde(rename_all = "PascalCase")]
343pub struct ResponseHeadersPolicyRemoveHeader {
344 pub header: String,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct StoredResponseHeadersPolicy {
349 pub id: String,
350 pub etag: String,
351 pub last_modified_time: DateTime<Utc>,
352 pub config: ResponseHeadersPolicyConfig,
353 pub policy_type: String,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize, Default)]
359#[serde(rename_all = "PascalCase")]
360pub struct ContinuousDeploymentPolicyConfig {
361 pub staging_distribution_dns_names: StagingDistributionDnsNames,
362 pub enabled: bool,
363 #[serde(default, skip_serializing_if = "skip_if_none")]
364 pub traffic_config: Option<TrafficConfig>,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize, Default)]
368#[serde(rename_all = "PascalCase")]
369pub struct StagingDistributionDnsNames {
370 pub quantity: i32,
371 #[serde(default, skip_serializing_if = "skip_if_none")]
372 pub items: Option<StagingDistributionDnsNameItems>,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, Default)]
376#[serde(rename_all = "PascalCase")]
377pub struct StagingDistributionDnsNameItems {
378 #[serde(default, rename = "DnsName")]
379 pub dns_name: Vec<String>,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize, Default)]
383#[serde(rename_all = "PascalCase")]
384pub struct TrafficConfig {
385 #[serde(default, skip_serializing_if = "skip_if_none")]
386 pub single_weight_config: Option<ContinuousDeploymentSingleWeightConfig>,
387 #[serde(default, skip_serializing_if = "skip_if_none")]
388 pub single_header_config: Option<ContinuousDeploymentSingleHeaderConfig>,
389 #[serde(rename = "Type")]
390 pub traffic_type: String,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize, Default)]
394#[serde(rename_all = "PascalCase")]
395pub struct ContinuousDeploymentSingleWeightConfig {
396 pub weight: f32,
397 #[serde(default, skip_serializing_if = "skip_if_none")]
398 pub session_stickiness_config: Option<SessionStickinessConfig>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize, Default)]
402#[serde(rename_all = "PascalCase")]
403pub struct SessionStickinessConfig {
404 pub idle_ttl: i32,
405 pub maximum_ttl: i32,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, Default)]
409#[serde(rename_all = "PascalCase")]
410pub struct ContinuousDeploymentSingleHeaderConfig {
411 pub header: String,
412 pub value: String,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct StoredContinuousDeploymentPolicy {
417 pub id: String,
418 pub etag: String,
419 pub last_modified_time: DateTime<Utc>,
420 pub config: ContinuousDeploymentPolicyConfig,
421}
422
423pub fn seed_managed(account: &mut AccountState) {
430 if !account.cache_policies.is_empty() {
431 return;
432 }
433 let now = Utc::now();
434 for (id, name, default_ttl, max_ttl, gzip, brotli) in MANAGED_CACHE_POLICIES {
435 account.cache_policies.insert(
436 (*id).to_string(),
437 StoredCachePolicy {
438 id: (*id).to_string(),
439 etag: format!("MANAGED-{id}"),
440 last_modified_time: now,
441 policy_type: "managed".to_string(),
442 config: CachePolicyConfig {
443 name: (*name).to_string(),
444 comment: Some(format!("AWS managed cache policy {name}")),
445 default_ttl: Some(*default_ttl),
446 max_ttl: Some(*max_ttl),
447 min_ttl: 1,
448 parameters_in_cache_key_and_forwarded_to_origin: Some(
449 ParametersInCacheKeyAndForwardedToOrigin {
450 enable_accept_encoding_gzip: *gzip,
451 enable_accept_encoding_brotli: Some(*brotli),
452 headers_config: CachePolicyHeadersConfig {
453 header_behavior: "none".to_string(),
454 headers: None,
455 },
456 cookies_config: CachePolicyCookiesConfig {
457 cookie_behavior: "none".to_string(),
458 cookies: None,
459 },
460 query_strings_config: CachePolicyQueryStringsConfig {
461 query_string_behavior: "none".to_string(),
462 query_strings: None,
463 },
464 },
465 ),
466 },
467 },
468 );
469 }
470 for (id, name) in MANAGED_ORIGIN_REQUEST_POLICIES {
471 account.origin_request_policies.insert(
472 (*id).to_string(),
473 StoredOriginRequestPolicy {
474 id: (*id).to_string(),
475 etag: format!("MANAGED-{id}"),
476 last_modified_time: now,
477 policy_type: "managed".to_string(),
478 config: OriginRequestPolicyConfig {
479 name: (*name).to_string(),
480 comment: Some(format!("AWS managed origin request policy {name}")),
481 headers_config: OriginRequestPolicyHeadersConfig {
482 header_behavior: "allViewer".to_string(),
483 headers: None,
484 },
485 cookies_config: OriginRequestPolicyCookiesConfig {
486 cookie_behavior: "all".to_string(),
487 cookies: None,
488 },
489 query_strings_config: OriginRequestPolicyQueryStringsConfig {
490 query_string_behavior: "all".to_string(),
491 query_strings: None,
492 },
493 },
494 },
495 );
496 }
497 for (id, name) in MANAGED_RESPONSE_HEADERS_POLICIES {
498 account.response_headers_policies.insert(
499 (*id).to_string(),
500 StoredResponseHeadersPolicy {
501 id: (*id).to_string(),
502 etag: format!("MANAGED-{id}"),
503 last_modified_time: now,
504 policy_type: "managed".to_string(),
505 config: ResponseHeadersPolicyConfig {
506 name: (*name).to_string(),
507 comment: Some(format!("AWS managed response headers policy {name}")),
508 cors_config: None,
509 security_headers_config: None,
510 server_timing_headers_config: None,
511 custom_headers_config: None,
512 remove_headers_config: None,
513 },
514 },
515 );
516 }
517}
518
519const MANAGED_CACHE_POLICIES: &[(&str, &str, i64, i64, bool, bool)] = &[
520 (
521 "658327ea-f89d-4fab-a63d-7e88639e58f6",
522 "Managed-CachingOptimized",
523 86400,
524 31536000,
525 true,
526 true,
527 ),
528 (
529 "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
530 "Managed-CachingDisabled",
531 0,
532 0,
533 false,
534 false,
535 ),
536 (
537 "b2884449-e4de-46a7-ac36-70bc7f1ddd6d",
538 "Managed-CachingOptimizedForUncompressedObjects",
539 86400,
540 31536000,
541 false,
542 false,
543 ),
544 (
545 "08627262-05a9-4f76-9ded-b50ca2e3a84f",
546 "Managed-Elemental-MediaPackage",
547 86400,
548 31536000,
549 true,
550 true,
551 ),
552 (
553 "83da9c7e-98b4-4e11-a168-04f0df8e2c65",
554 "Managed-AmplifyDefault",
555 2,
556 600,
557 true,
558 true,
559 ),
560];
561
562const MANAGED_ORIGIN_REQUEST_POLICIES: &[(&str, &str)] = &[
563 (
564 "88a5eaf4-2fd4-4709-b370-b4c650ea3fcf",
565 "Managed-CORS-S3Origin",
566 ),
567 (
568 "59781a5b-3903-41f3-afcb-af62929ccde1",
569 "Managed-CORS-CustomOrigin",
570 ),
571 ("b689b0a8-53d0-40ab-baf2-68738e2966ac", "Managed-AllViewer"),
572 (
573 "33f36d7e-f396-46d9-90e0-52428a34d9dc",
574 "Managed-UserAgentRefererHeaders",
575 ),
576 (
577 "775133bc-15f2-49f9-abea-afb2e0bf67d2",
578 "Managed-AllViewerExceptHostHeader",
579 ),
580 (
581 "acba4595-bd28-49b8-b9fe-13317c0390fa",
582 "Managed-AllViewerAndCloudFrontHeaders-2022-06",
583 ),
584];
585
586const MANAGED_RESPONSE_HEADERS_POLICIES: &[(&str, &str)] = &[
587 (
588 "5cc3b908-e619-4b99-88e5-2cf7f45965bd",
589 "Managed-CORS-with-preflight",
590 ),
591 (
592 "e61eb60c-9c35-4d20-a928-2b84e02af89c",
593 "Managed-CORS-and-SecurityHeadersPolicy",
594 ),
595 (
596 "67f7725c-6f97-4210-82d7-5512b31e9d03",
597 "Managed-SecurityHeadersPolicy",
598 ),
599 (
600 "eaab4381-ed33-4a86-88ca-d9558dc6cd63",
601 "Managed-CORS-with-preflight-and-SecurityHeadersPolicy",
602 ),
603 ("60669652-455b-4ae9-85a4-c4c02393f86c", "Managed-SimpleCORS"),
604];
605
606use crate::state::SharedCloudFrontState;
611
612pub(crate) fn touch_account(state: &SharedCloudFrontState, account_id: &str) {
613 let mut s = state.write();
614 let needs_seed = s
615 .accounts
616 .get(account_id)
617 .is_none_or(|a| a.cache_policies.is_empty());
618 if needs_seed {
619 let account = s.entry(account_id);
620 seed_managed(account);
621 }
622}
623
624#[derive(Clone)]
625pub(crate) struct PolicyView {
626 pub id: String,
627 pub last_modified_time: DateTime<Utc>,
628 pub config_xml: String,
629}
630
631impl From<StoredCachePolicy> for PolicyView {
632 fn from(p: StoredCachePolicy) -> Self {
633 let config_xml =
634 quick_xml::se::to_string_with_root("CachePolicyConfig", &p.config).unwrap_or_default();
635 Self {
636 id: p.id,
637 last_modified_time: p.last_modified_time,
638 config_xml,
639 }
640 }
641}
642
643impl From<StoredOriginRequestPolicy> for PolicyView {
644 fn from(p: StoredOriginRequestPolicy) -> Self {
645 let config_xml = quick_xml::se::to_string_with_root("OriginRequestPolicyConfig", &p.config)
646 .unwrap_or_default();
647 Self {
648 id: p.id,
649 last_modified_time: p.last_modified_time,
650 config_xml,
651 }
652 }
653}
654
655impl From<StoredResponseHeadersPolicy> for PolicyView {
656 fn from(p: StoredResponseHeadersPolicy) -> Self {
657 let config_xml =
658 quick_xml::se::to_string_with_root("ResponseHeadersPolicyConfig", &p.config)
659 .unwrap_or_default();
660 Self {
661 id: p.id,
662 last_modified_time: p.last_modified_time,
663 config_xml,
664 }
665 }
666}
667
668impl From<StoredContinuousDeploymentPolicy> for PolicyView {
669 fn from(p: StoredContinuousDeploymentPolicy) -> Self {
670 let config_xml =
671 quick_xml::se::to_string_with_root("ContinuousDeploymentPolicyConfig", &p.config)
672 .unwrap_or_default();
673 Self {
674 id: p.id,
675 last_modified_time: p.last_modified_time,
676 config_xml,
677 }
678 }
679}
680
681pub(crate) fn render_simple_policy(p: PolicyView, root: &str) -> String {
682 let mut out = String::with_capacity(512);
683 out.push_str(XML_DECL);
684 out.push_str(&format!("<{root} xmlns=\"{NS}\">"));
685 out.push_str(&format!("<Id>{}</Id>", esc(&p.id)));
686 out.push_str(&format!(
687 "<LastModifiedTime>{}</LastModifiedTime>",
688 rfc3339(&p.last_modified_time)
689 ));
690 out.push_str(&p.config_xml);
691 out.push_str(&format!("</{root}>"));
692 out
693}
694
695pub(crate) fn render_oac(oac: &StoredOriginAccessControl) -> String {
696 let mut out = String::with_capacity(384);
697 out.push_str(XML_DECL);
698 out.push_str(&format!("<OriginAccessControl xmlns=\"{NS}\">"));
699 out.push_str(&format!("<Id>{}</Id>", esc(&oac.id)));
700 out.push_str(
701 &quick_xml::se::to_string_with_root("OriginAccessControlConfig", &oac.config)
702 .unwrap_or_default(),
703 );
704 out.push_str("</OriginAccessControl>");
705 out
706}
707
708pub(crate) fn xml_with_etag(
709 status: StatusCode,
710 body: String,
711 etag: &str,
712 location_id: Option<&str>,
713) -> AwsResponse {
714 let mut headers = HeaderMap::new();
715 if let Ok(v) = HeaderValue::from_str(etag) {
716 headers.insert(ETAG, v);
717 }
718 if let Some(id) = location_id {
719 if let Ok(v) = HeaderValue::from_str(id) {
720 headers.insert(LOCATION, v);
721 }
722 }
723 xml_response(status, body, headers)
724}
725
726pub(crate) fn empty(status: StatusCode) -> AwsResponse {
727 AwsResponse {
728 status,
729 content_type: "text/xml".to_string(),
730 body: ResponseBody::Bytes(bytes::Bytes::new()),
731 headers: HeaderMap::new(),
732 }
733}
734
735pub(crate) fn route_id(route: &Route, what: &str) -> Result<String, AwsServiceError> {
736 route
737 .id
738 .clone()
739 .ok_or_else(|| invalid_argument(format!("missing {what} id")))
740}
741
742pub(crate) fn require_if_match(req: &AwsRequest) -> Result<String, AwsServiceError> {
743 req.headers
744 .get(IF_MATCH)
745 .and_then(|v| v.to_str().ok())
746 .map(|s| s.to_string())
747 .ok_or_else(|| {
748 aws_error(
749 StatusCode::BAD_REQUEST,
750 "InvalidIfMatchVersion",
751 "Missing If-Match header",
752 )
753 })
754}
755
756fn not_found_code(kind: &str) -> &'static str {
767 match kind {
768 "Function" => "NoSuchFunctionExists",
769 "KeyGroup" => "NoSuchResource",
770 "FieldLevelEncryption" => "NoSuchFieldLevelEncryptionConfig",
771 "AnycastIpList" | "ConnectionFunction" | "ConnectionGroup" | "DistributionTenant"
772 | "KeyValueStore" | "ResourcePolicy" | "TrustStore" | "VpcOrigin" => "EntityNotFound",
773 _ => "",
774 }
775}
776
777pub(crate) fn not_found(kind: &str, id: &str) -> AwsServiceError {
778 let code = not_found_code(kind);
779 let code = if code.is_empty() {
780 format!("NoSuch{kind}")
787 } else {
788 code.to_string()
789 };
790 aws_error(
791 StatusCode::NOT_FOUND,
792 code,
793 format!("The specified {kind} does not exist: {id}"),
794 )
795}
796
797pub(crate) fn precondition_failed() -> AwsServiceError {
798 aws_error(
799 StatusCode::PRECONDITION_FAILED,
800 "PreconditionFailed",
801 "If-Match header does not match the current ETag",
802 )
803}
804
805pub(crate) fn rfc3339(t: &DateTime<Utc>) -> String {
806 t.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812 use std::collections::HashSet;
813
814 #[test]
815 fn managed_cache_policy_ids_are_unique() {
816 let mut seen = HashSet::new();
817 for entry in MANAGED_CACHE_POLICIES {
818 let id = entry.0;
819 assert!(seen.insert(id), "duplicate cache policy id: {id}");
820 }
821 }
822
823 #[test]
824 fn managed_origin_request_policy_ids_are_unique() {
825 let mut seen = HashSet::new();
826 for (id, _) in MANAGED_ORIGIN_REQUEST_POLICIES {
827 assert!(seen.insert(*id), "duplicate origin request policy id: {id}");
828 }
829 }
830
831 #[test]
832 fn managed_response_headers_policy_ids_are_unique() {
833 let mut seen = HashSet::new();
834 for (id, _) in MANAGED_RESPONSE_HEADERS_POLICIES {
835 assert!(
836 seen.insert(*id),
837 "duplicate response headers policy id: {id}"
838 );
839 }
840 }
841
842 #[test]
843 fn seeded_response_headers_count_matches_unique_ids() {
844 let mut acc = AccountState::default();
845 seed_managed(&mut acc);
846 assert_eq!(
847 acc.response_headers_policies.len(),
848 MANAGED_RESPONSE_HEADERS_POLICIES.len(),
849 "duplicate IDs would reduce the seeded count"
850 );
851 }
852}