1use base64::Engine;
6use chrono::Utc;
7use http::header::ETAG;
8use http::{HeaderMap, StatusCode};
9use uuid::Uuid;
10
11use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError};
12
13use crate::functions::{
14 CloudFrontOriginAccessIdentityConfig, FunctionConfig, ImportSource, KeyGroupConfig,
15 MonitoringSubscriptionBody, PublicKeyConfig, StoredFunction, StoredKeyGroup,
16 StoredKeyValueStore, StoredMonitoringSubscription, StoredOriginAccessIdentity, StoredPublicKey,
17};
18use crate::policies::{
19 not_found, precondition_failed, require_if_match, rfc3339, route_id, xml_with_etag,
20};
21use crate::router::Route;
22use crate::service::{
23 aws_error, esc, generate_id_with_prefix, invalid_argument, xml_response, CloudFrontService,
24 DEFAULT_ACCOUNT,
25};
26use crate::xml_io;
27
28const NS: &str = crate::NAMESPACE;
29const XML_DECL: &str = r#"<?xml version="1.0" encoding="UTF-8"?>"#;
30
31impl CloudFrontService {
34 pub(crate) fn create_function(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
35 let parsed: CreateFunctionRequest = xml_io::from_xml_root(&req.body)
37 .map_err(|e| invalid_argument(format!("invalid CreateFunctionRequest XML: {e}")))?;
38 if parsed.name.is_empty() {
39 return Err(invalid_argument("CreateFunctionRequest.Name is required"));
40 }
41
42 let mut state = self.state.write();
43 let account = state
44 .accounts
45 .entry(DEFAULT_ACCOUNT.to_string())
46 .or_default();
47 if account.functions.contains_key(&parsed.name) {
48 return Err(aws_error(
49 StatusCode::CONFLICT,
50 "FunctionAlreadyExists",
51 format!("Function {} already exists", parsed.name),
52 ));
53 }
54 let now = Utc::now();
55 let etag = generate_id_with_prefix("E");
56 let function_arn = format!(
57 "arn:aws:cloudfront::{}:function/{}",
58 DEFAULT_ACCOUNT, parsed.name
59 );
60 let stored = StoredFunction {
61 name: parsed.name.clone(),
62 etag: etag.clone(),
63 status: "UNPUBLISHED".to_string(),
64 stage: "DEVELOPMENT".to_string(),
65 function_arn: function_arn.clone(),
66 created_time: now,
67 last_modified_time: now,
68 config: parsed.function_config,
69 function_code: parsed.function_code,
70 live_function_code: None,
72 };
73 account
74 .functions
75 .insert(parsed.name.clone(), stored.clone());
76 drop(state);
77
78 let body = render_function_summary(&stored, "CreateFunctionResult");
79 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, None))
80 }
81
82 pub(crate) fn describe_function(
83 &self,
84 req: &AwsRequest,
85 route: &Route,
86 ) -> Result<AwsResponse, AwsServiceError> {
87 let name = route_id(route, "Function")?;
88 let stage = parse_stage_query(&req.raw_query);
89 let state = self.state.read();
90 let f = state
91 .accounts
92 .get(DEFAULT_ACCOUNT)
93 .and_then(|a| a.functions.get(&name).cloned())
94 .ok_or_else(|| not_found("Function", &name))?;
95 drop(state);
96 let view = stage_view(&f, &stage);
97 let body = render_function_summary(&view, "DescribeFunctionResult");
98 Ok(xml_with_etag(StatusCode::OK, body, &view.etag, None))
99 }
100
101 pub(crate) fn get_function(
102 &self,
103 req: &AwsRequest,
104 route: &Route,
105 ) -> Result<AwsResponse, AwsServiceError> {
106 let name = route_id(route, "Function")?;
107 let stage = parse_stage_query(&req.raw_query);
108 let state = self.state.read();
109 let f = state
110 .accounts
111 .get(DEFAULT_ACCOUNT)
112 .and_then(|a| a.functions.get(&name).cloned())
113 .ok_or_else(|| not_found("Function", &name))?;
114 drop(state);
115 let view = stage_view(&f, &stage);
116 let mut headers = HeaderMap::new();
117 headers.insert(
118 ETAG,
119 view.etag
120 .parse()
121 .unwrap_or_else(|_| http::HeaderValue::from_static("")),
122 );
123 let bytes = base64::engine::general_purpose::STANDARD
124 .decode(view.function_code.as_bytes())
125 .unwrap_or_default();
126 Ok(AwsResponse {
127 status: StatusCode::OK,
128 headers,
129 content_type: "application/octet-stream".to_string(),
130 body: fakecloud_core::service::ResponseBody::Bytes(bytes::Bytes::from(bytes)),
131 })
132 }
133
134 pub(crate) fn update_function(
135 &self,
136 req: &AwsRequest,
137 route: &Route,
138 ) -> Result<AwsResponse, AwsServiceError> {
139 let name = route_id(route, "Function")?;
140 let if_match = require_if_match(req)?;
141 let parsed: UpdateFunctionRequest = xml_io::from_xml_root(&req.body)
142 .map_err(|e| invalid_argument(format!("invalid UpdateFunctionRequest XML: {e}")))?;
143 let mut state = self.state.write();
144 let account = state
145 .accounts
146 .get_mut(DEFAULT_ACCOUNT)
147 .ok_or_else(|| not_found("Function", &name))?;
148 let f = account
149 .functions
150 .get_mut(&name)
151 .ok_or_else(|| not_found("Function", &name))?;
152 if f.etag != if_match {
153 return Err(precondition_failed());
154 }
155 f.config = parsed.function_config;
156 f.function_code = parsed.function_code;
157 f.etag = generate_id_with_prefix("E");
158 f.last_modified_time = Utc::now();
159 f.status = "UNPUBLISHED".to_string();
160 f.stage = "DEVELOPMENT".to_string();
161 let snap = f.clone();
162 drop(state);
163 let body = render_function_summary(&snap, "UpdateFunctionResult");
164 let mut headers = HeaderMap::new();
168 if let Ok(v) = http::HeaderValue::from_str(&snap.etag) {
169 headers.insert(ETAG, v.clone());
170 headers.insert("ETtag", v);
171 }
172 Ok(xml_response(StatusCode::OK, body, headers))
173 }
174
175 pub(crate) fn delete_function(
176 &self,
177 req: &AwsRequest,
178 route: &Route,
179 ) -> Result<AwsResponse, AwsServiceError> {
180 let name = route_id(route, "Function")?;
181 let if_match = require_if_match(req)?;
182 let mut state = self.state.write();
183 let account = state
184 .accounts
185 .get_mut(DEFAULT_ACCOUNT)
186 .ok_or_else(|| not_found("Function", &name))?;
187 let f = account
188 .functions
189 .get(&name)
190 .ok_or_else(|| not_found("Function", &name))?;
191 if f.etag != if_match {
192 return Err(precondition_failed());
193 }
194 account.functions.remove(&name);
195 drop(state);
196 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
197 }
198
199 pub(crate) fn list_functions(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
200 let stage = validate_stage_query(&req.raw_query)?;
201 let state = self.state.read();
202 let mut items: Vec<StoredFunction> = state
203 .accounts
204 .get(DEFAULT_ACCOUNT)
205 .map(|a| a.functions.values().cloned().collect())
206 .unwrap_or_default();
207 drop(state);
208 items.sort_by(|a, b| a.name.cmp(&b.name));
209
210 let mut body = String::with_capacity(512);
211 body.push_str(XML_DECL);
212 body.push_str(&format!("<FunctionList xmlns=\"{NS}\">"));
213 body.push_str("<Marker></Marker>");
214 body.push_str("<MaxItems>100</MaxItems>");
215 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
216 body.push_str("<Items>");
217 for f in &items {
218 let view = stage_view(f, &stage);
219 body.push_str(&render_function_summary_inner(&view));
220 }
221 body.push_str("</Items>");
222 body.push_str("</FunctionList>");
223 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
224 }
225
226 pub(crate) fn publish_function(
227 &self,
228 req: &AwsRequest,
229 route: &Route,
230 ) -> Result<AwsResponse, AwsServiceError> {
231 let name = route_id(route, "Function")?;
232 let if_match = require_if_match(req)?;
233 let mut state = self.state.write();
234 let account = state
235 .accounts
236 .get_mut(DEFAULT_ACCOUNT)
237 .ok_or_else(|| not_found("Function", &name))?;
238 let f = account
239 .functions
240 .get_mut(&name)
241 .ok_or_else(|| not_found("Function", &name))?;
242 if f.etag != if_match {
243 return Err(precondition_failed());
244 }
245 f.status = "DEPLOYED".to_string();
246 f.stage = "LIVE".to_string();
247 f.last_modified_time = Utc::now();
248 f.live_function_code = Some(f.function_code.clone());
253 let snap = f.clone();
254 drop(state);
255 let body = render_function_summary(&snap, "PublishFunctionResult");
256 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
257 }
258
259 pub(crate) fn test_function(
260 &self,
261 req: &AwsRequest,
262 route: &Route,
263 ) -> Result<AwsResponse, AwsServiceError> {
264 let name = route_id(route, "Function")?;
265 let if_match = require_if_match(req)?;
266 let parsed: TestFunctionRequest = xml_io::from_xml_root(&req.body)
267 .map_err(|e| invalid_argument(format!("invalid TestFunctionRequest XML: {e}")))?;
268 let event_bytes = base64::engine::general_purpose::STANDARD
269 .decode(parsed.event_object.trim().as_bytes())
270 .map_err(|e| invalid_argument(format!("EventObject is not valid base64: {e}")))?;
271
272 let state = self.state.read();
273 let f = state
274 .accounts
275 .get(DEFAULT_ACCOUNT)
276 .and_then(|a| a.functions.get(&name).cloned())
277 .ok_or_else(|| {
278 aws_error(
279 StatusCode::NOT_FOUND,
280 "NoSuchFunctionExists",
281 format!("The specified function does not exist: {name}"),
282 )
283 })?;
284 drop(state);
285 if f.etag != if_match {
286 return Err(precondition_failed());
287 }
288
289 let stage = parsed.stage.as_deref().unwrap_or("DEVELOPMENT");
296 let source_b64 = if stage.eq_ignore_ascii_case("LIVE") {
297 f.live_function_code.as_deref().unwrap_or(&f.function_code)
298 } else {
299 f.function_code.as_str()
300 };
301 let code_bytes = base64::engine::general_purpose::STANDARD
302 .decode(source_b64.as_bytes())
303 .unwrap_or_else(|_| source_b64.as_bytes().to_vec());
304 let code = String::from_utf8(code_bytes)
305 .map_err(|e| invalid_argument(format!("function code is not valid UTF-8: {e}")))?;
306 let exec = crate::js_runtime::run_handler(&code, &event_bytes);
307
308 let mut body = String::with_capacity(1024);
309 body.push_str(XML_DECL);
310 body.push_str(&format!("<TestResult xmlns=\"{NS}\">"));
311 body.push_str(&render_function_summary_inner(&f));
312 body.push_str(&format!(
313 "<ComputeUtilization>{}</ComputeUtilization>",
314 exec.compute_utilization
315 ));
316 body.push_str("<FunctionExecutionLogs>");
317 for line in &exec.logs {
318 body.push_str(&format!("<member>{}</member>", esc(line)));
319 }
320 body.push_str("</FunctionExecutionLogs>");
321 body.push_str(&format!(
322 "<FunctionErrorMessage>{}</FunctionErrorMessage>",
323 esc(exec.error.as_deref().unwrap_or(""))
324 ));
325 body.push_str(&format!(
326 "<FunctionOutput>{}</FunctionOutput>",
327 esc(exec.output.as_deref().unwrap_or(""))
328 ));
329 body.push_str("</TestResult>");
330 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
331 }
332}
333
334impl CloudFrontService {
337 pub(crate) fn create_public_key(
338 &self,
339 req: &AwsRequest,
340 ) -> Result<AwsResponse, AwsServiceError> {
341 let cfg: PublicKeyConfig = xml_io::from_xml_root(&req.body)
342 .map_err(|e| invalid_argument(format!("invalid PublicKeyConfig XML: {e}")))?;
343 if cfg.name.is_empty() {
344 return Err(invalid_argument("PublicKeyConfig.Name is required"));
345 }
346 if cfg.encoded_key.is_empty() {
347 return Err(invalid_argument("PublicKeyConfig.EncodedKey is required"));
348 }
349 let mut state = self.state.write();
350 let account = state
351 .accounts
352 .entry(DEFAULT_ACCOUNT.to_string())
353 .or_default();
354 if let Some(existing) = account
355 .public_keys
356 .values()
357 .find(|p| p.config.caller_reference == cfg.caller_reference)
358 {
359 return Err(aws_error(
360 StatusCode::CONFLICT,
361 "PublicKeyAlreadyExists",
362 format!(
363 "PublicKey with same CallerReference exists: {}",
364 existing.id
365 ),
366 ));
367 }
368 let id = generate_id_with_prefix("K");
369 let etag = generate_id_with_prefix("E");
370 let stored = StoredPublicKey {
371 id: id.clone(),
372 etag: etag.clone(),
373 created_time: Utc::now(),
374 config: cfg,
375 };
376 account.public_keys.insert(id.clone(), stored.clone());
377 drop(state);
378 let body = render_public_key(&stored, "PublicKey");
379 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
380 }
381
382 pub(crate) fn get_public_key(&self, route: &Route) -> Result<AwsResponse, AwsServiceError> {
383 let id = route_id(route, "PublicKey")?;
384 let state = self.state.read();
385 let p = state
386 .accounts
387 .get(DEFAULT_ACCOUNT)
388 .and_then(|a| a.public_keys.get(&id).cloned())
389 .ok_or_else(|| not_found("PublicKey", &id))?;
390 drop(state);
391 let body = render_public_key(&p, "PublicKey");
392 Ok(xml_with_etag(StatusCode::OK, body, &p.etag, None))
393 }
394
395 pub(crate) fn get_public_key_config(
396 &self,
397 route: &Route,
398 ) -> Result<AwsResponse, AwsServiceError> {
399 let id = route_id(route, "PublicKey")?;
400 let state = self.state.read();
401 let p = state
402 .accounts
403 .get(DEFAULT_ACCOUNT)
404 .and_then(|a| a.public_keys.get(&id).cloned())
405 .ok_or_else(|| not_found("PublicKey", &id))?;
406 drop(state);
407 let body = config_xml("PublicKeyConfig", &p.config)?;
408 Ok(xml_with_etag(StatusCode::OK, body, &p.etag, None))
409 }
410
411 pub(crate) fn update_public_key(
412 &self,
413 req: &AwsRequest,
414 route: &Route,
415 ) -> Result<AwsResponse, AwsServiceError> {
416 let id = route_id(route, "PublicKey")?;
417 let if_match = require_if_match(req)?;
418 let cfg: PublicKeyConfig = xml_io::from_xml_root(&req.body)
419 .map_err(|e| invalid_argument(format!("invalid PublicKeyConfig XML: {e}")))?;
420 if cfg.name.is_empty() {
421 return Err(invalid_argument("PublicKeyConfig.Name is required"));
422 }
423 let mut state = self.state.write();
424 let account = state
425 .accounts
426 .get_mut(DEFAULT_ACCOUNT)
427 .ok_or_else(|| not_found("PublicKey", &id))?;
428 let p = account
429 .public_keys
430 .get_mut(&id)
431 .ok_or_else(|| not_found("PublicKey", &id))?;
432 if p.etag != if_match {
433 return Err(precondition_failed());
434 }
435 if p.config.caller_reference != cfg.caller_reference {
437 return Err(invalid_argument(
438 "CallerReference cannot change on UpdatePublicKey",
439 ));
440 }
441 p.config = cfg;
442 p.etag = generate_id_with_prefix("E");
443 let snap = p.clone();
444 drop(state);
445 let body = render_public_key(&snap, "PublicKey");
446 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
447 }
448
449 pub(crate) fn delete_public_key(
450 &self,
451 req: &AwsRequest,
452 route: &Route,
453 ) -> Result<AwsResponse, AwsServiceError> {
454 let id = route_id(route, "PublicKey")?;
455 let if_match = require_if_match(req)?;
456 let mut state = self.state.write();
457 let account = state
458 .accounts
459 .get_mut(DEFAULT_ACCOUNT)
460 .ok_or_else(|| not_found("PublicKey", &id))?;
461 let p = account
462 .public_keys
463 .get(&id)
464 .ok_or_else(|| not_found("PublicKey", &id))?;
465 if p.etag != if_match {
466 return Err(precondition_failed());
467 }
468 account.public_keys.remove(&id);
469 drop(state);
470 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
471 }
472
473 pub(crate) fn list_public_keys(
474 &self,
475 _req: &AwsRequest,
476 ) -> Result<AwsResponse, AwsServiceError> {
477 let state = self.state.read();
478 let mut items: Vec<StoredPublicKey> = state
479 .accounts
480 .get(DEFAULT_ACCOUNT)
481 .map(|a| a.public_keys.values().cloned().collect())
482 .unwrap_or_default();
483 drop(state);
484 items.sort_by(|a, b| a.id.cmp(&b.id));
485
486 let mut body = String::with_capacity(512);
487 body.push_str(XML_DECL);
488 body.push_str(&format!("<PublicKeyList xmlns=\"{NS}\">"));
489 body.push_str("<Marker></Marker>");
490 body.push_str("<MaxItems>100</MaxItems>");
491 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
492 body.push_str("<Items>");
493 for p in &items {
494 body.push_str("<PublicKeySummary>");
495 body.push_str(&format!("<Id>{}</Id>", esc(&p.id)));
496 body.push_str(&format!("<Name>{}</Name>", esc(&p.config.name)));
497 body.push_str(&format!(
498 "<CreatedTime>{}</CreatedTime>",
499 rfc3339(&p.created_time)
500 ));
501 body.push_str(&format!(
502 "<EncodedKey>{}</EncodedKey>",
503 esc(&p.config.encoded_key)
504 ));
505 if let Some(c) = &p.config.comment {
506 body.push_str(&format!("<Comment>{}</Comment>", esc(c)));
507 }
508 body.push_str("</PublicKeySummary>");
509 }
510 body.push_str("</Items>");
511 body.push_str("</PublicKeyList>");
512 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
513 }
514}
515
516impl CloudFrontService {
519 pub(crate) fn create_key_group(
520 &self,
521 req: &AwsRequest,
522 ) -> Result<AwsResponse, AwsServiceError> {
523 let cfg: KeyGroupConfig = xml_io::from_xml_root(&req.body)
524 .map_err(|e| invalid_argument(format!("invalid KeyGroupConfig XML: {e}")))?;
525 if cfg.name.is_empty() {
526 return Err(invalid_argument("KeyGroupConfig.Name is required"));
527 }
528 let mut state = self.state.write();
529 let account = state
530 .accounts
531 .entry(DEFAULT_ACCOUNT.to_string())
532 .or_default();
533 let id = generate_id_with_prefix("K");
534 let etag = generate_id_with_prefix("E");
535 let stored = StoredKeyGroup {
536 id: id.clone(),
537 etag: etag.clone(),
538 last_modified_time: Utc::now(),
539 config: cfg,
540 };
541 account.key_groups.insert(id.clone(), stored.clone());
542 drop(state);
543 let body = render_key_group(&stored, "KeyGroup");
544 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
545 }
546
547 pub(crate) fn get_key_group(&self, route: &Route) -> Result<AwsResponse, AwsServiceError> {
548 let id = route_id(route, "KeyGroup")?;
549 let state = self.state.read();
550 let g = state
551 .accounts
552 .get(DEFAULT_ACCOUNT)
553 .and_then(|a| a.key_groups.get(&id).cloned())
554 .ok_or_else(|| not_found("KeyGroup", &id))?;
555 drop(state);
556 let body = render_key_group(&g, "KeyGroup");
557 Ok(xml_with_etag(StatusCode::OK, body, &g.etag, None))
558 }
559
560 pub(crate) fn get_key_group_config(
561 &self,
562 route: &Route,
563 ) -> Result<AwsResponse, AwsServiceError> {
564 let id = route_id(route, "KeyGroup")?;
565 let state = self.state.read();
566 let g = state
567 .accounts
568 .get(DEFAULT_ACCOUNT)
569 .and_then(|a| a.key_groups.get(&id).cloned())
570 .ok_or_else(|| not_found("KeyGroup", &id))?;
571 drop(state);
572 let body = config_xml("KeyGroupConfig", &g.config)?;
573 Ok(xml_with_etag(StatusCode::OK, body, &g.etag, None))
574 }
575
576 pub(crate) fn update_key_group(
577 &self,
578 req: &AwsRequest,
579 route: &Route,
580 ) -> Result<AwsResponse, AwsServiceError> {
581 let id = route_id(route, "KeyGroup")?;
582 let if_match = require_if_match(req)?;
583 let cfg: KeyGroupConfig = xml_io::from_xml_root(&req.body)
584 .map_err(|e| invalid_argument(format!("invalid KeyGroupConfig XML: {e}")))?;
585 if cfg.name.is_empty() {
586 return Err(invalid_argument("KeyGroupConfig.Name is required"));
587 }
588 let mut state = self.state.write();
589 let account = state
590 .accounts
591 .get_mut(DEFAULT_ACCOUNT)
592 .ok_or_else(|| not_found("KeyGroup", &id))?;
593 let g = account
594 .key_groups
595 .get_mut(&id)
596 .ok_or_else(|| not_found("KeyGroup", &id))?;
597 if g.etag != if_match {
598 return Err(precondition_failed());
599 }
600 g.config = cfg;
601 g.etag = generate_id_with_prefix("E");
602 g.last_modified_time = Utc::now();
603 let snap = g.clone();
604 drop(state);
605 let body = render_key_group(&snap, "KeyGroup");
606 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
607 }
608
609 pub(crate) fn delete_key_group(
610 &self,
611 req: &AwsRequest,
612 route: &Route,
613 ) -> Result<AwsResponse, AwsServiceError> {
614 let id = route_id(route, "KeyGroup")?;
615 let if_match = require_if_match(req)?;
616 let mut state = self.state.write();
617 let account = state
618 .accounts
619 .get_mut(DEFAULT_ACCOUNT)
620 .ok_or_else(|| not_found("KeyGroup", &id))?;
621 let g = account
622 .key_groups
623 .get(&id)
624 .ok_or_else(|| not_found("KeyGroup", &id))?;
625 if g.etag != if_match {
626 return Err(precondition_failed());
627 }
628 account.key_groups.remove(&id);
629 drop(state);
630 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
631 }
632
633 pub(crate) fn list_key_groups(
634 &self,
635 _req: &AwsRequest,
636 ) -> Result<AwsResponse, AwsServiceError> {
637 let state = self.state.read();
638 let mut items: Vec<StoredKeyGroup> = state
639 .accounts
640 .get(DEFAULT_ACCOUNT)
641 .map(|a| a.key_groups.values().cloned().collect())
642 .unwrap_or_default();
643 drop(state);
644 items.sort_by(|a, b| a.config.name.cmp(&b.config.name));
645
646 let mut body = String::with_capacity(512);
647 body.push_str(XML_DECL);
648 body.push_str(&format!("<KeyGroupList xmlns=\"{NS}\">"));
649 body.push_str("<Marker></Marker>");
650 body.push_str("<MaxItems>100</MaxItems>");
651 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
652 body.push_str("<Items>");
653 for g in &items {
654 body.push_str("<KeyGroupSummary>");
655 body.push_str("<KeyGroup>");
656 push_key_group_inner(&mut body, g);
657 body.push_str("</KeyGroup>");
658 body.push_str("</KeyGroupSummary>");
659 }
660 body.push_str("</Items>");
661 body.push_str("</KeyGroupList>");
662 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
663 }
664}
665
666impl CloudFrontService {
669 pub(crate) fn create_key_value_store(
670 &self,
671 req: &AwsRequest,
672 ) -> Result<AwsResponse, AwsServiceError> {
673 let parsed: CreateKeyValueStoreRequest = xml_io::from_xml_root(&req.body)
674 .map_err(|e| invalid_argument(format!("invalid CreateKeyValueStore XML: {e}")))?;
675 if parsed.name.is_empty() {
676 return Err(invalid_argument("Name is required"));
677 }
678 let mut state = self.state.write();
679 let account = state
680 .accounts
681 .entry(DEFAULT_ACCOUNT.to_string())
682 .or_default();
683 if account.key_value_stores.contains_key(&parsed.name) {
684 return Err(aws_error(
685 StatusCode::CONFLICT,
686 "EntityAlreadyExists",
687 format!("KeyValueStore {} already exists", parsed.name),
688 ));
689 }
690 let now = Utc::now();
691 let id = Uuid::new_v4().to_string();
692 let etag = generate_id_with_prefix("E");
693 let arn = format!(
694 "arn:aws:cloudfront::{}:key-value-store/{}",
695 DEFAULT_ACCOUNT, id
696 );
697 let stored = StoredKeyValueStore {
698 name: parsed.name.clone(),
699 id,
700 etag: etag.clone(),
701 arn,
702 status: "READY".to_string(),
703 created_time: now,
704 last_modified_time: now,
705 comment: parsed.comment,
706 import_source: parsed.import_source,
707 };
708 account
709 .key_value_stores
710 .insert(parsed.name.clone(), stored.clone());
711 drop(state);
712 let body = render_key_value_store(&stored, "CreateKeyValueStoreResult");
713 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, None))
714 }
715
716 pub(crate) fn describe_key_value_store(
717 &self,
718 route: &Route,
719 ) -> Result<AwsResponse, AwsServiceError> {
720 let name = route_id(route, "KeyValueStore")?;
721 let state = self.state.read();
722 let kvs = state
723 .accounts
724 .get(DEFAULT_ACCOUNT)
725 .and_then(|a| a.key_value_stores.get(&name).cloned())
726 .ok_or_else(|| not_found("KeyValueStore", &name))?;
727 drop(state);
728 let body = render_key_value_store(&kvs, "DescribeKeyValueStoreResult");
729 Ok(xml_with_etag(StatusCode::OK, body, &kvs.etag, None))
730 }
731
732 pub(crate) fn update_key_value_store(
733 &self,
734 req: &AwsRequest,
735 route: &Route,
736 ) -> Result<AwsResponse, AwsServiceError> {
737 let name = route_id(route, "KeyValueStore")?;
738 let if_match = require_if_match(req)?;
739 let parsed: UpdateKeyValueStoreRequest = xml_io::from_xml_root(&req.body)
740 .map_err(|e| invalid_argument(format!("invalid UpdateKeyValueStore XML: {e}")))?;
741 let mut state = self.state.write();
742 let account = state
743 .accounts
744 .get_mut(DEFAULT_ACCOUNT)
745 .ok_or_else(|| not_found("KeyValueStore", &name))?;
746 let kvs = account
747 .key_value_stores
748 .get_mut(&name)
749 .ok_or_else(|| not_found("KeyValueStore", &name))?;
750 if kvs.etag != if_match {
751 return Err(precondition_failed());
752 }
753 kvs.comment = Some(parsed.comment);
754 kvs.etag = generate_id_with_prefix("E");
755 kvs.last_modified_time = Utc::now();
756 let snap = kvs.clone();
757 drop(state);
758 let body = render_key_value_store(&snap, "UpdateKeyValueStoreResult");
759 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
760 }
761
762 pub(crate) fn delete_key_value_store(
763 &self,
764 req: &AwsRequest,
765 route: &Route,
766 ) -> Result<AwsResponse, AwsServiceError> {
767 let name = route_id(route, "KeyValueStore")?;
768 let if_match = require_if_match(req)?;
769 let mut state = self.state.write();
770 let account = state
771 .accounts
772 .get_mut(DEFAULT_ACCOUNT)
773 .ok_or_else(|| not_found("KeyValueStore", &name))?;
774 let kvs = account
775 .key_value_stores
776 .get(&name)
777 .ok_or_else(|| not_found("KeyValueStore", &name))?;
778 if kvs.etag != if_match {
779 return Err(precondition_failed());
780 }
781 account.key_value_stores.remove(&name);
782 drop(state);
783 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
784 }
785
786 pub(crate) fn list_key_value_stores(
787 &self,
788 _req: &AwsRequest,
789 ) -> Result<AwsResponse, AwsServiceError> {
790 let state = self.state.read();
791 let mut items: Vec<StoredKeyValueStore> = state
792 .accounts
793 .get(DEFAULT_ACCOUNT)
794 .map(|a| a.key_value_stores.values().cloned().collect())
795 .unwrap_or_default();
796 drop(state);
797 items.sort_by(|a, b| a.name.cmp(&b.name));
798
799 let mut body = String::with_capacity(512);
800 body.push_str(XML_DECL);
801 body.push_str(&format!("<KeyValueStoreList xmlns=\"{NS}\">"));
802 body.push_str("<NextMarker></NextMarker>");
803 body.push_str("<MaxItems>100</MaxItems>");
804 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
805 body.push_str("<Items>");
806 for kvs in &items {
807 body.push_str("<KeyValueStore>");
808 push_kvs_inner(&mut body, kvs);
809 body.push_str("</KeyValueStore>");
810 }
811 body.push_str("</Items>");
812 body.push_str("</KeyValueStoreList>");
813 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
814 }
815}
816
817impl CloudFrontService {
820 pub(crate) fn create_oai(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
821 let cfg: CloudFrontOriginAccessIdentityConfig = xml_io::from_xml_root(&req.body)
822 .map_err(|e| invalid_argument(format!("invalid OAI config XML: {e}")))?;
823 if cfg.caller_reference.is_empty() {
824 return Err(invalid_argument("CallerReference is required"));
825 }
826 let mut state = self.state.write();
827 let account = state
828 .accounts
829 .entry(DEFAULT_ACCOUNT.to_string())
830 .or_default();
831 if let Some(existing) = account
832 .origin_access_identities
833 .values()
834 .find(|o| o.config.caller_reference == cfg.caller_reference)
835 {
836 return Err(aws_error(
837 StatusCode::CONFLICT,
838 "CloudFrontOriginAccessIdentityAlreadyExists",
839 format!("OAI with same CallerReference exists: {}", existing.id),
840 ));
841 }
842 let id = format!(
843 "E{}",
844 Uuid::new_v4()
845 .simple()
846 .to_string()
847 .to_uppercase()
848 .chars()
849 .take(13)
850 .collect::<String>()
851 );
852 let etag = generate_id_with_prefix("E");
853 let canonical = Uuid::new_v4().simple().to_string();
854 let stored = StoredOriginAccessIdentity {
855 id: id.clone(),
856 etag: etag.clone(),
857 s3_canonical_user_id: canonical,
858 config: cfg,
859 };
860 account
861 .origin_access_identities
862 .insert(id.clone(), stored.clone());
863 drop(state);
864 let body = render_oai(&stored, "CloudFrontOriginAccessIdentity");
865 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
866 }
867
868 pub(crate) fn get_oai(&self, route: &Route) -> Result<AwsResponse, AwsServiceError> {
869 let id = route_id(route, "CloudFrontOriginAccessIdentity")?;
870 let state = self.state.read();
871 let oai = state
872 .accounts
873 .get(DEFAULT_ACCOUNT)
874 .and_then(|a| a.origin_access_identities.get(&id).cloned())
875 .ok_or_else(|| not_found("CloudFrontOriginAccessIdentity", &id))?;
876 drop(state);
877 let body = render_oai(&oai, "CloudFrontOriginAccessIdentity");
878 Ok(xml_with_etag(StatusCode::OK, body, &oai.etag, None))
879 }
880
881 pub(crate) fn get_oai_config(&self, route: &Route) -> Result<AwsResponse, AwsServiceError> {
882 let id = route_id(route, "CloudFrontOriginAccessIdentity")?;
883 let state = self.state.read();
884 let oai = state
885 .accounts
886 .get(DEFAULT_ACCOUNT)
887 .and_then(|a| a.origin_access_identities.get(&id).cloned())
888 .ok_or_else(|| not_found("CloudFrontOriginAccessIdentity", &id))?;
889 drop(state);
890 let body = config_xml("CloudFrontOriginAccessIdentityConfig", &oai.config)?;
891 Ok(xml_with_etag(StatusCode::OK, body, &oai.etag, None))
892 }
893
894 pub(crate) fn update_oai(
895 &self,
896 req: &AwsRequest,
897 route: &Route,
898 ) -> Result<AwsResponse, AwsServiceError> {
899 let id = route_id(route, "CloudFrontOriginAccessIdentity")?;
900 let if_match = require_if_match(req)?;
901 let cfg: CloudFrontOriginAccessIdentityConfig = xml_io::from_xml_root(&req.body)
902 .map_err(|e| invalid_argument(format!("invalid OAI config XML: {e}")))?;
903 let mut state = self.state.write();
904 let account = state
905 .accounts
906 .get_mut(DEFAULT_ACCOUNT)
907 .ok_or_else(|| not_found("CloudFrontOriginAccessIdentity", &id))?;
908 let oai = account
909 .origin_access_identities
910 .get_mut(&id)
911 .ok_or_else(|| not_found("CloudFrontOriginAccessIdentity", &id))?;
912 if oai.etag != if_match {
913 return Err(precondition_failed());
914 }
915 if oai.config.caller_reference != cfg.caller_reference {
916 return Err(invalid_argument(
917 "CallerReference cannot change on UpdateCloudFrontOriginAccessIdentity",
918 ));
919 }
920 oai.config = cfg;
921 oai.etag = generate_id_with_prefix("E");
922 let snap = oai.clone();
923 drop(state);
924 let body = render_oai(&snap, "CloudFrontOriginAccessIdentity");
925 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
926 }
927
928 pub(crate) fn delete_oai(
929 &self,
930 req: &AwsRequest,
931 route: &Route,
932 ) -> Result<AwsResponse, AwsServiceError> {
933 let id = route_id(route, "CloudFrontOriginAccessIdentity")?;
934 let if_match = require_if_match(req)?;
935 let mut state = self.state.write();
936 let account = state
937 .accounts
938 .get_mut(DEFAULT_ACCOUNT)
939 .ok_or_else(|| not_found("CloudFrontOriginAccessIdentity", &id))?;
940 let oai = account
941 .origin_access_identities
942 .get(&id)
943 .ok_or_else(|| not_found("CloudFrontOriginAccessIdentity", &id))?;
944 if oai.etag != if_match {
945 return Err(precondition_failed());
946 }
947 account.origin_access_identities.remove(&id);
948 drop(state);
949 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
950 }
951
952 pub(crate) fn list_oai(&self, _req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
953 let state = self.state.read();
954 let mut items: Vec<StoredOriginAccessIdentity> = state
955 .accounts
956 .get(DEFAULT_ACCOUNT)
957 .map(|a| a.origin_access_identities.values().cloned().collect())
958 .unwrap_or_default();
959 drop(state);
960 items.sort_by(|a, b| a.id.cmp(&b.id));
961
962 let mut body = String::with_capacity(512);
963 body.push_str(XML_DECL);
964 body.push_str(&format!(
965 "<CloudFrontOriginAccessIdentityList xmlns=\"{NS}\">"
966 ));
967 body.push_str("<Marker></Marker>");
968 body.push_str("<MaxItems>100</MaxItems>");
969 body.push_str("<IsTruncated>false</IsTruncated>");
970 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
971 body.push_str("<Items>");
972 for oai in &items {
973 body.push_str("<CloudFrontOriginAccessIdentitySummary>");
974 body.push_str(&format!("<Id>{}</Id>", esc(&oai.id)));
975 body.push_str(&format!(
976 "<S3CanonicalUserId>{}</S3CanonicalUserId>",
977 esc(&oai.s3_canonical_user_id)
978 ));
979 body.push_str(&format!("<Comment>{}</Comment>", esc(&oai.config.comment)));
980 body.push_str("</CloudFrontOriginAccessIdentitySummary>");
981 }
982 body.push_str("</Items>");
983 body.push_str("</CloudFrontOriginAccessIdentityList>");
984 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
985 }
986}
987
988impl CloudFrontService {
991 pub(crate) fn create_monitoring_subscription(
992 &self,
993 req: &AwsRequest,
994 route: &Route,
995 ) -> Result<AwsResponse, AwsServiceError> {
996 let dist_id = route_id(route, "Distribution")?;
997 {
1002 let state = self.state.read();
1003 let has_dist = state
1004 .accounts
1005 .get(DEFAULT_ACCOUNT)
1006 .is_some_and(|a| a.distributions.contains_key(&dist_id));
1007 if !has_dist {
1008 return Err(not_found("Distribution", &dist_id));
1009 }
1010 }
1011 let parsed: MonitoringSubscriptionBody = xml_io::from_xml_root(&req.body)
1012 .map_err(|e| invalid_argument(format!("invalid MonitoringSubscription XML: {e}")))?;
1013 let mut state = self.state.write();
1014 let account = state
1015 .accounts
1016 .entry(DEFAULT_ACCOUNT.to_string())
1017 .or_default();
1018 if !account.distributions.contains_key(&dist_id) {
1019 return Err(not_found("Distribution", &dist_id));
1020 }
1021 let stored = StoredMonitoringSubscription {
1022 distribution_id: dist_id.clone(),
1023 config: parsed.realtime_metrics_subscription_config,
1024 };
1025 account
1026 .monitoring_subscriptions
1027 .insert(dist_id.clone(), stored.clone());
1028 drop(state);
1029 let body = render_monitoring(&stored);
1030 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
1031 }
1032
1033 pub(crate) fn get_monitoring_subscription(
1034 &self,
1035 route: &Route,
1036 ) -> Result<AwsResponse, AwsServiceError> {
1037 let dist_id = route_id(route, "Distribution")?;
1038 let state = self.state.read();
1039 let m = state
1040 .accounts
1041 .get(DEFAULT_ACCOUNT)
1042 .and_then(|a| a.monitoring_subscriptions.get(&dist_id).cloned())
1043 .ok_or_else(|| {
1044 aws_error(
1045 StatusCode::NOT_FOUND,
1046 "NoSuchMonitoringSubscription",
1047 format!("No monitoring subscription for distribution {dist_id}"),
1048 )
1049 })?;
1050 drop(state);
1051 let body = render_monitoring(&m);
1052 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
1053 }
1054
1055 pub(crate) fn delete_monitoring_subscription(
1056 &self,
1057 route: &Route,
1058 ) -> Result<AwsResponse, AwsServiceError> {
1059 let dist_id = route_id(route, "Distribution")?;
1060 let mut state = self.state.write();
1061 let account = state
1062 .accounts
1063 .get_mut(DEFAULT_ACCOUNT)
1064 .ok_or_else(|| not_found("Distribution", &dist_id))?;
1065 if account.monitoring_subscriptions.remove(&dist_id).is_none() {
1066 return Err(aws_error(
1067 StatusCode::NOT_FOUND,
1068 "NoSuchMonitoringSubscription",
1069 format!("No monitoring subscription for distribution {dist_id}"),
1070 ));
1071 }
1072 drop(state);
1073 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
1074 }
1075}
1076
1077#[derive(Debug, serde::Deserialize)]
1080#[serde(rename_all = "PascalCase")]
1081struct CreateFunctionRequest {
1082 name: String,
1083 function_config: FunctionConfig,
1084 function_code: String,
1086}
1087
1088#[derive(Debug, serde::Deserialize)]
1089#[serde(rename_all = "PascalCase")]
1090struct UpdateFunctionRequest {
1091 function_config: FunctionConfig,
1092 function_code: String,
1093}
1094
1095#[derive(Debug, serde::Deserialize)]
1096#[serde(rename_all = "PascalCase")]
1097struct TestFunctionRequest {
1098 #[serde(default)]
1099 event_object: String,
1100 #[serde(default)]
1101 stage: Option<String>,
1102}
1103
1104#[derive(Debug, serde::Deserialize)]
1105#[serde(rename_all = "PascalCase")]
1106struct CreateKeyValueStoreRequest {
1107 name: String,
1108 #[serde(default)]
1109 comment: Option<String>,
1110 #[serde(default)]
1111 import_source: Option<ImportSource>,
1112}
1113
1114#[derive(Debug, serde::Deserialize)]
1115#[serde(rename_all = "PascalCase")]
1116struct UpdateKeyValueStoreRequest {
1117 comment: String,
1118}
1119
1120fn config_xml<T: serde::Serialize>(root: &str, cfg: &T) -> Result<String, AwsServiceError> {
1121 let inner = quick_xml::se::to_string_with_root(root, cfg).map_err(|e| {
1122 aws_error(
1123 StatusCode::INTERNAL_SERVER_ERROR,
1124 "InternalError",
1125 format!("xml encode failed: {e}"),
1126 )
1127 })?;
1128 let stamped = inner.replacen(
1129 &format!("<{root}>"),
1130 &format!("<{root} xmlns=\"{NS}\">", NS = crate::NAMESPACE),
1131 1,
1132 );
1133 Ok(format!("{XML_DECL}{stamped}"))
1134}
1135
1136fn parse_stage_query(query: &str) -> Option<String> {
1137 use std::collections::HashMap;
1138 let pairs: HashMap<&str, &str> = query.split('&').filter_map(|p| p.split_once('=')).collect();
1139 pairs.get("Stage").map(|s| s.to_string())
1140}
1141
1142pub(crate) fn validate_stage_query(
1146 query: &str,
1147) -> Result<Option<String>, fakecloud_core::service::AwsServiceError> {
1148 let stage = parse_stage_query(query);
1149 if let Some(ref v) = stage {
1150 if v != "DEVELOPMENT" && v != "LIVE" {
1151 return Err(crate::service::invalid_argument(format!(
1152 "Stage must be one of 'DEVELOPMENT' or 'LIVE', got '{v}'"
1153 )));
1154 }
1155 }
1156 Ok(stage)
1157}
1158
1159fn stage_view(f: &StoredFunction, stage: &Option<String>) -> StoredFunction {
1160 let mut clone = f.clone();
1161 if stage.as_deref() == Some("LIVE") {
1162 clone.stage = "LIVE".into();
1163 if let Some(live) = &f.live_function_code {
1168 clone.function_code = live.clone();
1169 }
1170 }
1171 clone
1172}
1173
1174fn render_function_summary(f: &StoredFunction, _root: &str) -> String {
1175 let mut out = String::with_capacity(512);
1178 out.push_str(XML_DECL);
1179 out.push_str(&render_function_summary_inner_with_ns(f));
1180 out
1181}
1182
1183fn render_function_summary_inner_with_ns(f: &StoredFunction) -> String {
1184 let mut out = String::with_capacity(512);
1185 out.push_str(&format!("<FunctionSummary xmlns=\"{NS}\">"));
1186 out.push_str(&render_function_summary_body(f));
1187 out.push_str("</FunctionSummary>");
1188 out
1189}
1190
1191fn render_function_summary_inner(f: &StoredFunction) -> String {
1192 let mut out = String::with_capacity(512);
1193 out.push_str("<FunctionSummary>");
1194 out.push_str(&render_function_summary_body(f));
1195 out.push_str("</FunctionSummary>");
1196 out
1197}
1198
1199fn render_function_summary_body(f: &StoredFunction) -> String {
1200 let mut out = String::with_capacity(512);
1201 out.push_str(&format!("<Name>{}</Name>", esc(&f.name)));
1202 out.push_str(&format!("<Status>{}</Status>", esc(&f.status)));
1203 out.push_str("<FunctionConfig>");
1204 if let Some(c) = &f.config.comment {
1205 out.push_str(&format!("<Comment>{}</Comment>", esc(c)));
1206 } else {
1207 out.push_str("<Comment></Comment>");
1208 }
1209 out.push_str(&format!("<Runtime>{}</Runtime>", esc(&f.config.runtime)));
1210 if let Some(kvsa) = &f.config.key_value_store_associations {
1211 out.push_str("<KeyValueStoreAssociations>");
1212 out.push_str(&format!("<Quantity>{}</Quantity>", kvsa.quantity));
1213 if let Some(items) = &kvsa.items {
1214 out.push_str("<Items>");
1215 for a in &items.key_value_store_association {
1216 out.push_str("<KeyValueStoreAssociation>");
1217 out.push_str(&format!(
1218 "<KeyValueStoreARN>{}</KeyValueStoreARN>",
1219 esc(&a.key_value_store_arn)
1220 ));
1221 out.push_str("</KeyValueStoreAssociation>");
1222 }
1223 out.push_str("</Items>");
1224 }
1225 out.push_str("</KeyValueStoreAssociations>");
1226 }
1227 out.push_str("</FunctionConfig>");
1228 out.push_str("<FunctionMetadata>");
1229 out.push_str(&format!(
1230 "<FunctionARN>{}</FunctionARN>",
1231 esc(&f.function_arn)
1232 ));
1233 out.push_str(&format!("<Stage>{}</Stage>", esc(&f.stage)));
1234 out.push_str(&format!(
1235 "<CreatedTime>{}</CreatedTime>",
1236 rfc3339(&f.created_time)
1237 ));
1238 out.push_str(&format!(
1239 "<LastModifiedTime>{}</LastModifiedTime>",
1240 rfc3339(&f.last_modified_time)
1241 ));
1242 out.push_str("</FunctionMetadata>");
1243 out
1244}
1245
1246fn render_public_key(p: &StoredPublicKey, root: &str) -> String {
1247 let mut out = String::with_capacity(512);
1248 out.push_str(XML_DECL);
1249 out.push_str(&format!("<{root} xmlns=\"{NS}\">"));
1250 out.push_str(&format!("<Id>{}</Id>", esc(&p.id)));
1251 out.push_str(&format!(
1252 "<CreatedTime>{}</CreatedTime>",
1253 rfc3339(&p.created_time)
1254 ));
1255 out.push_str("<PublicKeyConfig>");
1256 out.push_str(&format!(
1257 "<CallerReference>{}</CallerReference>",
1258 esc(&p.config.caller_reference)
1259 ));
1260 out.push_str(&format!("<Name>{}</Name>", esc(&p.config.name)));
1261 out.push_str(&format!(
1262 "<EncodedKey>{}</EncodedKey>",
1263 esc(&p.config.encoded_key)
1264 ));
1265 if let Some(c) = &p.config.comment {
1266 out.push_str(&format!("<Comment>{}</Comment>", esc(c)));
1267 }
1268 out.push_str("</PublicKeyConfig>");
1269 out.push_str(&format!("</{root}>"));
1270 out
1271}
1272
1273fn push_key_group_inner(out: &mut String, g: &StoredKeyGroup) {
1274 out.push_str(&format!("<Id>{}</Id>", esc(&g.id)));
1275 out.push_str(&format!(
1276 "<LastModifiedTime>{}</LastModifiedTime>",
1277 rfc3339(&g.last_modified_time)
1278 ));
1279 out.push_str("<KeyGroupConfig>");
1280 out.push_str(&format!("<Name>{}</Name>", esc(&g.config.name)));
1281 out.push_str("<Items>");
1282 for k in &g.config.items.public_key {
1283 out.push_str(&format!("<PublicKey>{}</PublicKey>", esc(k)));
1284 }
1285 out.push_str("</Items>");
1286 if let Some(c) = &g.config.comment {
1287 out.push_str(&format!("<Comment>{}</Comment>", esc(c)));
1288 }
1289 out.push_str("</KeyGroupConfig>");
1290}
1291
1292fn render_key_group(g: &StoredKeyGroup, root: &str) -> String {
1293 let mut out = String::with_capacity(512);
1294 out.push_str(XML_DECL);
1295 out.push_str(&format!("<{root} xmlns=\"{NS}\">"));
1296 push_key_group_inner(&mut out, g);
1297 out.push_str(&format!("</{root}>"));
1298 out
1299}
1300
1301fn push_kvs_inner(out: &mut String, kvs: &StoredKeyValueStore) {
1302 out.push_str(&format!("<Name>{}</Name>", esc(&kvs.name)));
1303 out.push_str(&format!("<Id>{}</Id>", esc(&kvs.id)));
1304 out.push_str(&format!(
1305 "<Comment>{}</Comment>",
1306 esc(kvs.comment.as_deref().unwrap_or(""))
1307 ));
1308 out.push_str(&format!("<ARN>{}</ARN>", esc(&kvs.arn)));
1309 out.push_str(&format!("<Status>{}</Status>", esc(&kvs.status)));
1310 out.push_str(&format!(
1311 "<LastModifiedTime>{}</LastModifiedTime>",
1312 rfc3339(&kvs.last_modified_time)
1313 ));
1314}
1315
1316fn render_key_value_store(kvs: &StoredKeyValueStore, _root: &str) -> String {
1317 let mut out = String::with_capacity(512);
1319 out.push_str(XML_DECL);
1320 out.push_str(&format!("<KeyValueStore xmlns=\"{NS}\">"));
1321 push_kvs_inner(&mut out, kvs);
1322 out.push_str("</KeyValueStore>");
1323 out
1324}
1325
1326fn render_oai(oai: &StoredOriginAccessIdentity, root: &str) -> String {
1327 let mut out = String::with_capacity(512);
1328 out.push_str(XML_DECL);
1329 out.push_str(&format!("<{root} xmlns=\"{NS}\">"));
1330 out.push_str(&format!("<Id>{}</Id>", esc(&oai.id)));
1331 out.push_str(&format!(
1332 "<S3CanonicalUserId>{}</S3CanonicalUserId>",
1333 esc(&oai.s3_canonical_user_id)
1334 ));
1335 out.push_str("<CloudFrontOriginAccessIdentityConfig>");
1336 out.push_str(&format!(
1337 "<CallerReference>{}</CallerReference>",
1338 esc(&oai.config.caller_reference)
1339 ));
1340 out.push_str(&format!("<Comment>{}</Comment>", esc(&oai.config.comment)));
1341 out.push_str("</CloudFrontOriginAccessIdentityConfig>");
1342 out.push_str(&format!("</{root}>"));
1343 out
1344}
1345
1346fn render_monitoring(m: &StoredMonitoringSubscription) -> String {
1347 let mut out = String::with_capacity(256);
1348 out.push_str(XML_DECL);
1349 out.push_str(&format!("<MonitoringSubscription xmlns=\"{NS}\">"));
1350 out.push_str("<RealtimeMetricsSubscriptionConfig>");
1351 out.push_str(&format!(
1352 "<RealtimeMetricsSubscriptionStatus>{}</RealtimeMetricsSubscriptionStatus>",
1353 esc(&m.config.realtime_metrics_subscription_status)
1354 ));
1355 out.push_str("</RealtimeMetricsSubscriptionConfig>");
1356 out.push_str("</MonitoringSubscription>");
1357 out
1358}
1359
1360#[cfg(test)]
1361mod tests {
1362 use super::*;
1363 use crate::service::CloudFrontService;
1364 use crate::state::CloudFrontAccounts;
1365 use bytes::Bytes;
1366 use fakecloud_core::service::AwsService;
1367 use http::HeaderValue;
1368 use parking_lot::RwLock;
1369 use std::sync::Arc;
1370
1371 fn svc() -> CloudFrontService {
1372 CloudFrontService::new(Arc::new(RwLock::new(CloudFrontAccounts::new())))
1373 }
1374
1375 fn req(method: http::Method, path: &str, body: &str, if_match: Option<&str>) -> AwsRequest {
1376 let mut headers = HeaderMap::new();
1377 if let Some(v) = if_match {
1378 headers.insert(http::header::IF_MATCH, HeaderValue::from_str(v).unwrap());
1379 }
1380 AwsRequest {
1381 service: "cloudfront".into(),
1382 action: String::new(),
1383 region: "us-east-1".into(),
1384 account_id: DEFAULT_ACCOUNT.into(),
1385 request_id: uuid::Uuid::new_v4().to_string(),
1386 headers,
1387 query_params: std::collections::HashMap::new(),
1388 body_stream: parking_lot::Mutex::new(None),
1389 body: Bytes::from(body.to_string()),
1390 path_segments: path
1391 .split('/')
1392 .filter(|s| !s.is_empty())
1393 .map(String::from)
1394 .collect(),
1395 raw_path: path.into(),
1396 raw_query: String::new(),
1397 method,
1398 is_query_protocol: false,
1399 access_key_id: None,
1400 principal: None,
1401 }
1402 }
1403
1404 async fn create_function(svc: &CloudFrontService, name: &str, code: &str) -> String {
1405 let code_b64 = base64::engine::general_purpose::STANDARD.encode(code.as_bytes());
1406 let body = format!(
1407 r#"<?xml version="1.0"?>
1408<CreateFunctionRequest xmlns="{NS}">
1409 <Name>{name}</Name>
1410 <FunctionConfig>
1411 <Comment>t</Comment>
1412 <Runtime>cloudfront-js-2.0</Runtime>
1413 </FunctionConfig>
1414 <FunctionCode>{code_b64}</FunctionCode>
1415</CreateFunctionRequest>"#
1416 );
1417 let resp = svc
1418 .handle(req(http::Method::POST, "/2020-05-31/function", &body, None))
1419 .await
1420 .unwrap();
1421 assert_eq!(resp.status, StatusCode::CREATED);
1422 resp.headers
1423 .get(http::header::ETAG)
1424 .unwrap()
1425 .to_str()
1426 .unwrap()
1427 .to_string()
1428 }
1429
1430 fn test_function_request_xml(event_json: &str) -> String {
1431 test_function_request_xml_with_stage(event_json, "DEVELOPMENT")
1432 }
1433
1434 fn test_function_request_xml_with_stage(event_json: &str, stage: &str) -> String {
1435 let event_b64 = base64::engine::general_purpose::STANDARD.encode(event_json.as_bytes());
1436 format!(
1437 r#"<?xml version="1.0"?>
1438<TestFunctionRequest xmlns="{NS}">
1439 <Stage>{stage}</Stage>
1440 <EventObject>{event_b64}</EventObject>
1441</TestFunctionRequest>"#
1442 )
1443 }
1444
1445 async fn update_function(
1446 svc: &CloudFrontService,
1447 name: &str,
1448 code: &str,
1449 if_match: &str,
1450 ) -> String {
1451 let code_b64 = base64::engine::general_purpose::STANDARD.encode(code.as_bytes());
1452 let body = format!(
1453 r#"<?xml version="1.0"?>
1454<UpdateFunctionRequest xmlns="{NS}">
1455 <FunctionConfig>
1456 <Comment>t</Comment>
1457 <Runtime>cloudfront-js-2.0</Runtime>
1458 </FunctionConfig>
1459 <FunctionCode>{code_b64}</FunctionCode>
1460</UpdateFunctionRequest>"#
1461 );
1462 let resp = svc
1463 .handle(req(
1464 http::Method::PUT,
1465 &format!("/2020-05-31/function/{name}"),
1466 &body,
1467 Some(if_match),
1468 ))
1469 .await
1470 .unwrap();
1471 assert_eq!(resp.status, StatusCode::OK);
1472 resp.headers
1473 .get(http::header::ETAG)
1474 .unwrap()
1475 .to_str()
1476 .unwrap()
1477 .to_string()
1478 }
1479
1480 async fn publish_function(svc: &CloudFrontService, name: &str, if_match: &str) -> String {
1481 let resp = svc
1482 .handle(req(
1483 http::Method::POST,
1484 &format!("/2020-05-31/function/{name}/publish"),
1485 "",
1486 Some(if_match),
1487 ))
1488 .await
1489 .unwrap();
1490 assert_eq!(resp.status, StatusCode::OK);
1491 resp.headers
1492 .get(http::header::ETAG)
1493 .unwrap()
1494 .to_str()
1495 .unwrap()
1496 .to_string()
1497 }
1498
1499 #[tokio::test]
1500 async fn test_function_executes_handler_and_returns_result() {
1501 let svc = svc();
1502 let etag = create_function(
1503 &svc,
1504 "fn-ok",
1505 r#"function handler(event) { event.headers.x = "y"; return event; }"#,
1506 )
1507 .await;
1508 let body = test_function_request_xml(r#"{"headers":{}}"#);
1509 let resp = svc
1510 .handle(req(
1511 http::Method::POST,
1512 "/2020-05-31/function/fn-ok/test",
1513 &body,
1514 Some(&etag),
1515 ))
1516 .await
1517 .unwrap();
1518 assert_eq!(resp.status, StatusCode::OK);
1519 let xml = std::str::from_utf8(resp.body.expect_bytes()).unwrap();
1520 assert!(
1521 xml.contains(""x":"y""),
1522 "expected x:y in FunctionOutput, got {xml}"
1523 );
1524 assert!(xml.contains("<FunctionErrorMessage></FunctionErrorMessage>"));
1525 }
1526
1527 #[tokio::test]
1528 async fn test_function_propagates_js_error_into_message() {
1529 let svc = svc();
1530 let etag = create_function(
1531 &svc,
1532 "fn-err",
1533 r#"function handler() { throw new Error("boom"); }"#,
1534 )
1535 .await;
1536 let body = test_function_request_xml("{}");
1537 let resp = svc
1538 .handle(req(
1539 http::Method::POST,
1540 "/2020-05-31/function/fn-err/test",
1541 &body,
1542 Some(&etag),
1543 ))
1544 .await
1545 .unwrap();
1546 assert_eq!(resp.status, StatusCode::OK);
1547 let xml = std::str::from_utf8(resp.body.expect_bytes()).unwrap();
1548 assert!(
1549 xml.contains("boom"),
1550 "expected boom in error msg, got {xml}"
1551 );
1552 assert!(xml.contains("<FunctionOutput></FunctionOutput>"));
1553 }
1554
1555 #[tokio::test]
1556 async fn test_function_unknown_name_returns_error() {
1557 let svc = svc();
1558 let body = test_function_request_xml("{}");
1559 let err = match svc
1560 .handle(req(
1561 http::Method::POST,
1562 "/2020-05-31/function/missing/test",
1563 &body,
1564 Some("E0"),
1565 ))
1566 .await
1567 {
1568 Err(e) => e,
1569 Ok(_) => panic!("expected NoSuchFunctionExists, got Ok"),
1570 };
1571 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1572 assert_eq!(err.code(), "NoSuchFunctionExists");
1573 }
1574
1575 #[tokio::test]
1576 async fn test_function_modifies_aws_request_shape() {
1577 let svc = svc();
1581 let etag = create_function(
1582 &svc,
1583 "fn-aws-shape",
1584 r#"function handler(event) { event.request.headers["x-foo"] = {value: "bar"}; return event.request; }"#,
1585 )
1586 .await;
1587 let body = test_function_request_xml(
1588 r#"{"version":"1.0","context":{},"viewer":{},"request":{"method":"GET","uri":"/","querystring":{},"headers":{},"cookies":{}}}"#,
1589 );
1590 let resp = svc
1591 .handle(req(
1592 http::Method::POST,
1593 "/2020-05-31/function/fn-aws-shape/test",
1594 &body,
1595 Some(&etag),
1596 ))
1597 .await
1598 .unwrap();
1599 assert_eq!(resp.status, StatusCode::OK);
1600 let xml = std::str::from_utf8(resp.body.expect_bytes()).unwrap();
1601 assert!(
1602 xml.contains("x-foo"),
1603 "expected request header rewrite in output, got {xml}"
1604 );
1605 assert!(
1606 xml.contains("bar"),
1607 "expected header value in output, got {xml}"
1608 );
1609 assert!(
1610 xml.contains("<FunctionErrorMessage></FunctionErrorMessage>"),
1611 "expected empty error, got {xml}"
1612 );
1613 }
1614
1615 #[tokio::test]
1616 async fn test_function_logs_error_and_marks_compute_over_100() {
1617 let svc = svc();
1618 let etag = create_function(
1619 &svc,
1620 "fn-throws",
1621 r#"function handler() { throw new Error("kaboom"); }"#,
1622 )
1623 .await;
1624 let body = test_function_request_xml("{}");
1625 let resp = svc
1626 .handle(req(
1627 http::Method::POST,
1628 "/2020-05-31/function/fn-throws/test",
1629 &body,
1630 Some(&etag),
1631 ))
1632 .await
1633 .unwrap();
1634 assert_eq!(resp.status, StatusCode::OK);
1635 let xml = std::str::from_utf8(resp.body.expect_bytes()).unwrap();
1636 assert!(xml.contains("kaboom"), "expected kaboom in body, got {xml}");
1638 assert!(
1639 xml.contains("<FunctionExecutionLogs>")
1640 && xml.contains("<member>ERROR: ")
1641 && xml.contains("kaboom"),
1642 "expected error log line, got {xml}"
1643 );
1644 let cu_open = xml.find("<ComputeUtilization>").unwrap() + "<ComputeUtilization>".len();
1647 let cu_close = xml.find("</ComputeUtilization>").unwrap();
1648 let pct: u32 = xml[cu_open..cu_close].parse().unwrap();
1649 assert!(pct > 100, "expected pct > 100 on error, got {pct}");
1650 }
1651
1652 #[tokio::test]
1653 async fn test_function_stage_selects_published_or_development_code() {
1654 let svc = svc();
1659 let etag =
1660 create_function(&svc, "fn-stage", r#"function handler() { return "v1"; }"#).await;
1661 let pub_etag = publish_function(&svc, "fn-stage", &etag).await;
1662 let _new_etag = update_function(
1663 &svc,
1664 "fn-stage",
1665 r#"function handler() { return "v2"; }"#,
1666 &pub_etag,
1667 )
1668 .await;
1669
1670 let dev_body = test_function_request_xml_with_stage("{}", "DEVELOPMENT");
1672 let resp = svc
1673 .handle(req(
1674 http::Method::POST,
1675 "/2020-05-31/function/fn-stage/test",
1676 &dev_body,
1677 Some("E_NOT_MATCHING"),
1678 ))
1679 .await;
1680 assert!(resp.is_err(), "stale If-Match must be rejected");
1684 let described = svc
1685 .handle(req(
1686 http::Method::GET,
1687 "/2020-05-31/function/fn-stage",
1688 "",
1689 None,
1690 ))
1691 .await
1692 .unwrap();
1693 let live_etag = described
1694 .headers
1695 .get(http::header::ETAG)
1696 .unwrap()
1697 .to_str()
1698 .unwrap()
1699 .to_string();
1700
1701 let dev_resp = svc
1702 .handle(req(
1703 http::Method::POST,
1704 "/2020-05-31/function/fn-stage/test",
1705 &dev_body,
1706 Some(&live_etag),
1707 ))
1708 .await
1709 .unwrap();
1710 let dev_xml = std::str::from_utf8(dev_resp.body.expect_bytes()).unwrap();
1711 assert!(
1712 dev_xml.contains(""v2""),
1713 "DEVELOPMENT should run latest update (v2), got {dev_xml}"
1714 );
1715
1716 let live_body = test_function_request_xml_with_stage("{}", "LIVE");
1719 let live_resp = svc
1720 .handle(req(
1721 http::Method::POST,
1722 "/2020-05-31/function/fn-stage/test",
1723 &live_body,
1724 Some(&live_etag),
1725 ))
1726 .await
1727 .unwrap();
1728 let live_xml = std::str::from_utf8(live_resp.body.expect_bytes()).unwrap();
1729 assert!(
1730 live_xml.contains(""v1""),
1731 "LIVE should run published snapshot (v1), got {live_xml}"
1732 );
1733 }
1734
1735 #[tokio::test]
1736 async fn test_function_infinite_loop_is_killed() {
1737 let svc = svc();
1738 let etag = create_function(&svc, "fn-loop", r#"function handler() { while(1){} }"#).await;
1739 let body = test_function_request_xml("{}");
1740 let resp = svc
1741 .handle(req(
1742 http::Method::POST,
1743 "/2020-05-31/function/fn-loop/test",
1744 &body,
1745 Some(&etag),
1746 ))
1747 .await
1748 .unwrap();
1749 assert_eq!(resp.status, StatusCode::OK);
1750 let xml = std::str::from_utf8(resp.body.expect_bytes()).unwrap();
1751 assert!(
1752 xml.contains("<FunctionOutput></FunctionOutput>"),
1753 "expected empty output, got {xml}"
1754 );
1755 assert!(
1756 xml.contains("ERROR:") && xml.contains("limit"),
1757 "expected timeout/limit error in logs, got {xml}"
1758 );
1759 let cu_open = xml.find("<ComputeUtilization>").unwrap() + "<ComputeUtilization>".len();
1760 let cu_close = xml.find("</ComputeUtilization>").unwrap();
1761 let pct: u32 = xml[cu_open..cu_close].parse().unwrap();
1762 assert!(pct > 100, "expected pct > 100 after kill, got {pct}");
1763 }
1764
1765 fn req_q(method: http::Method, path: &str, query: &str) -> AwsRequest {
1766 let mut r = req(method, path, "", None);
1767 r.raw_query = query.to_string();
1768 r
1769 }
1770
1771 #[tokio::test]
1772 async fn get_function_live_stage_returns_published_code_not_dev() {
1773 let svc = svc();
1778 let e1 = create_function(&svc, "stage-fn", "CODE_V1").await;
1779 let e2 = publish_function(&svc, "stage-fn", &e1).await;
1780 update_function(&svc, "stage-fn", "CODE_V2", &e2).await;
1781
1782 let live = svc
1783 .handle(req_q(
1784 http::Method::GET,
1785 "/2020-05-31/function/stage-fn",
1786 "Stage=LIVE",
1787 ))
1788 .await
1789 .unwrap();
1790 let live_code = String::from_utf8(live.body.expect_bytes().to_vec()).unwrap();
1791 assert_eq!(live_code, "CODE_V1", "LIVE stage must serve published code");
1792
1793 let dev = svc
1794 .handle(req_q(
1795 http::Method::GET,
1796 "/2020-05-31/function/stage-fn",
1797 "Stage=DEVELOPMENT",
1798 ))
1799 .await
1800 .unwrap();
1801 let dev_code = String::from_utf8(dev.body.expect_bytes().to_vec()).unwrap();
1802 assert_eq!(dev_code, "CODE_V2", "DEVELOPMENT stage serves working copy");
1803 }
1804
1805 #[tokio::test]
1806 async fn function_config_round_trips_key_value_store_associations() {
1807 let svc = svc();
1810 let arn = "arn:aws:cloudfront::123456789012:key-value-store/kvs-1";
1811 let code_b64 = base64::engine::general_purpose::STANDARD.encode(b"CODE");
1812 let body = format!(
1813 r#"<?xml version="1.0"?>
1814<CreateFunctionRequest xmlns="{NS}">
1815 <Name>kvs-fn</Name>
1816 <FunctionConfig>
1817 <Comment>t</Comment>
1818 <Runtime>cloudfront-js-2.0</Runtime>
1819 <KeyValueStoreAssociations>
1820 <Quantity>1</Quantity>
1821 <Items>
1822 <KeyValueStoreAssociation>
1823 <KeyValueStoreARN>{arn}</KeyValueStoreARN>
1824 </KeyValueStoreAssociation>
1825 </Items>
1826 </KeyValueStoreAssociations>
1827 </FunctionConfig>
1828 <FunctionCode>{code_b64}</FunctionCode>
1829</CreateFunctionRequest>"#
1830 );
1831 let create = svc
1832 .handle(req(http::Method::POST, "/2020-05-31/function", &body, None))
1833 .await
1834 .unwrap();
1835 assert_eq!(create.status, StatusCode::CREATED);
1836 let create_xml = std::str::from_utf8(create.body.expect_bytes()).unwrap();
1837 assert!(
1838 create_xml.contains(&format!("<KeyValueStoreARN>{arn}</KeyValueStoreARN>")),
1839 "create response dropped KVS association: {create_xml}"
1840 );
1841
1842 let describe = svc
1843 .handle(req(
1844 http::Method::GET,
1845 "/2020-05-31/function/kvs-fn/describe",
1846 "",
1847 None,
1848 ))
1849 .await
1850 .unwrap();
1851 let describe_xml = std::str::from_utf8(describe.body.expect_bytes()).unwrap();
1852 assert!(
1853 describe_xml.contains(&format!("<KeyValueStoreARN>{arn}</KeyValueStoreARN>")),
1854 "describe response dropped KVS association: {describe_xml}"
1855 );
1856 assert!(describe_xml.contains("<Quantity>1</Quantity>"));
1857 }
1858}