1use chrono::Utc;
5use http::{HeaderMap, StatusCode};
6
7use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError};
8
9use crate::fle::{
10 CreateRealtimeLogConfigRequest, FieldLevelEncryptionConfig, FieldLevelEncryptionProfileConfig,
11 GetOrDeleteRealtimeLogConfigRequest, StoredFieldLevelEncryption,
12 StoredFieldLevelEncryptionProfile, StoredRealtimeLogConfig, UpdateRealtimeLogConfigRequest,
13};
14use crate::policies::{
15 not_found, precondition_failed, require_if_match, rfc3339, route_id, xml_with_etag,
16};
17use crate::router::Route;
18use crate::service::{
19 aws_error, esc, generate_id_with_prefix, invalid_argument, xml_response, CloudFrontService,
20 DEFAULT_ACCOUNT,
21};
22use crate::xml_io;
23
24const NS: &str = crate::NAMESPACE;
25const XML_DECL: &str = r#"<?xml version="1.0" encoding="UTF-8"?>"#;
26
27impl CloudFrontService {
30 pub(crate) fn create_field_level_encryption_config(
31 &self,
32 req: &AwsRequest,
33 ) -> Result<AwsResponse, AwsServiceError> {
34 let cfg: FieldLevelEncryptionConfig = xml_io::from_xml_root(&req.body).map_err(|e| {
35 invalid_argument(format!("invalid FieldLevelEncryptionConfig XML: {e}"))
36 })?;
37 if cfg.caller_reference.is_empty() {
38 return Err(invalid_argument("CallerReference is required"));
39 }
40 let mut state = self.state.write();
41 let account = state
42 .accounts
43 .entry(DEFAULT_ACCOUNT.to_string())
44 .or_default();
45 if let Some(existing) = account
46 .field_level_encryptions
47 .values()
48 .find(|f| f.config.caller_reference == cfg.caller_reference)
49 {
50 return Err(aws_error(
51 StatusCode::CONFLICT,
52 "FieldLevelEncryptionConfigAlreadyExists",
53 format!(
54 "FieldLevelEncryption with same CallerReference exists: {}",
55 existing.id
56 ),
57 ));
58 }
59 let id = generate_id_with_prefix("F");
60 let etag = generate_id_with_prefix("E");
61 let stored = StoredFieldLevelEncryption {
62 id: id.clone(),
63 etag: etag.clone(),
64 last_modified_time: Utc::now(),
65 config: cfg,
66 };
67 account
68 .field_level_encryptions
69 .insert(id.clone(), stored.clone());
70 drop(state);
71 let body = render_fle(&stored);
72 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
73 }
74
75 pub(crate) fn get_field_level_encryption(
76 &self,
77 route: &Route,
78 ) -> Result<AwsResponse, AwsServiceError> {
79 let id = route_id(route, "FieldLevelEncryption")?;
80 let state = self.state.read();
81 let f = state
82 .accounts
83 .get(DEFAULT_ACCOUNT)
84 .and_then(|a| a.field_level_encryptions.get(&id).cloned())
85 .ok_or_else(|| not_found("FieldLevelEncryption", &id))?;
86 drop(state);
87 let body = render_fle(&f);
88 Ok(xml_with_etag(StatusCode::OK, body, &f.etag, None))
89 }
90
91 pub(crate) fn get_field_level_encryption_config(
92 &self,
93 route: &Route,
94 ) -> Result<AwsResponse, AwsServiceError> {
95 let id = route_id(route, "FieldLevelEncryption")?;
96 let state = self.state.read();
97 let f = state
98 .accounts
99 .get(DEFAULT_ACCOUNT)
100 .and_then(|a| a.field_level_encryptions.get(&id).cloned())
101 .ok_or_else(|| not_found("FieldLevelEncryption", &id))?;
102 drop(state);
103 let body = render_fle_config(&f.config);
104 Ok(xml_with_etag(StatusCode::OK, body, &f.etag, None))
105 }
106
107 pub(crate) fn update_field_level_encryption_config(
108 &self,
109 req: &AwsRequest,
110 route: &Route,
111 ) -> Result<AwsResponse, AwsServiceError> {
112 let id = route_id(route, "FieldLevelEncryption")?;
113 let if_match = require_if_match(req)?;
114 let cfg: FieldLevelEncryptionConfig = xml_io::from_xml_root(&req.body).map_err(|e| {
115 invalid_argument(format!("invalid FieldLevelEncryptionConfig XML: {e}"))
116 })?;
117 let mut state = self.state.write();
118 let account = state
119 .accounts
120 .get_mut(DEFAULT_ACCOUNT)
121 .ok_or_else(|| not_found("FieldLevelEncryption", &id))?;
122 let f = account
123 .field_level_encryptions
124 .get_mut(&id)
125 .ok_or_else(|| not_found("FieldLevelEncryption", &id))?;
126 if f.etag != if_match {
127 return Err(precondition_failed());
128 }
129 if f.config.caller_reference != cfg.caller_reference {
130 return Err(invalid_argument(
131 "CallerReference cannot change on UpdateFieldLevelEncryptionConfig",
132 ));
133 }
134 f.config = cfg;
135 f.etag = generate_id_with_prefix("E");
136 f.last_modified_time = Utc::now();
137 let snap = f.clone();
138 drop(state);
139 let body = render_fle(&snap);
140 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
141 }
142
143 pub(crate) fn delete_field_level_encryption_config(
144 &self,
145 req: &AwsRequest,
146 route: &Route,
147 ) -> Result<AwsResponse, AwsServiceError> {
148 let id = route_id(route, "FieldLevelEncryption")?;
149 let if_match = require_if_match(req)?;
150 let mut state = self.state.write();
151 let account = state
152 .accounts
153 .get_mut(DEFAULT_ACCOUNT)
154 .ok_or_else(|| not_found("FieldLevelEncryption", &id))?;
155 let f = account
156 .field_level_encryptions
157 .get(&id)
158 .ok_or_else(|| not_found("FieldLevelEncryption", &id))?;
159 if f.etag != if_match {
160 return Err(precondition_failed());
161 }
162 account.field_level_encryptions.remove(&id);
163 drop(state);
164 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
165 }
166
167 pub(crate) fn list_field_level_encryption_configs(
168 &self,
169 _req: &AwsRequest,
170 ) -> Result<AwsResponse, AwsServiceError> {
171 let state = self.state.read();
172 let mut items: Vec<StoredFieldLevelEncryption> = state
173 .accounts
174 .get(DEFAULT_ACCOUNT)
175 .map(|a| a.field_level_encryptions.values().cloned().collect())
176 .unwrap_or_default();
177 drop(state);
178 items.sort_by(|a, b| a.id.cmp(&b.id));
179
180 let mut body = String::with_capacity(512);
181 body.push_str(XML_DECL);
182 body.push_str(&format!("<FieldLevelEncryptionList xmlns=\"{NS}\">"));
183 body.push_str("<NextMarker></NextMarker>");
184 body.push_str("<MaxItems>100</MaxItems>");
185 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
186 body.push_str("<Items>");
187 for f in &items {
188 body.push_str("<FieldLevelEncryptionSummary>");
189 body.push_str(&format!("<Id>{}</Id>", esc(&f.id)));
190 body.push_str(&format!(
191 "<LastModifiedTime>{}</LastModifiedTime>",
192 rfc3339(&f.last_modified_time)
193 ));
194 if let Some(c) = &f.config.comment {
195 body.push_str(&format!("<Comment>{}</Comment>", esc(c)));
196 }
197 body.push_str(&render_query_arg_profile_config(
198 &f.config.query_arg_profile_config,
199 ));
200 body.push_str(&render_content_type_profile_config(
201 &f.config.content_type_profile_config,
202 ));
203 body.push_str("</FieldLevelEncryptionSummary>");
204 }
205 body.push_str("</Items>");
206 body.push_str("</FieldLevelEncryptionList>");
207 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
208 }
209}
210
211impl CloudFrontService {
214 pub(crate) fn create_field_level_encryption_profile(
215 &self,
216 req: &AwsRequest,
217 ) -> Result<AwsResponse, AwsServiceError> {
218 let cfg: FieldLevelEncryptionProfileConfig =
219 xml_io::from_xml_root(&req.body).map_err(|e| {
220 invalid_argument(format!(
221 "invalid FieldLevelEncryptionProfileConfig XML: {e}"
222 ))
223 })?;
224 if cfg.name.is_empty() {
225 return Err(invalid_argument(
226 "FieldLevelEncryptionProfileConfig.Name is required",
227 ));
228 }
229 if cfg.caller_reference.is_empty() {
230 return Err(invalid_argument("CallerReference is required"));
231 }
232 let mut state = self.state.write();
233 let account = state
234 .accounts
235 .entry(DEFAULT_ACCOUNT.to_string())
236 .or_default();
237 if let Some(existing) = account
238 .field_level_encryption_profiles
239 .values()
240 .find(|p| p.config.caller_reference == cfg.caller_reference)
241 {
242 return Err(aws_error(
243 StatusCode::CONFLICT,
244 "FieldLevelEncryptionProfileAlreadyExists",
245 format!(
246 "FieldLevelEncryptionProfile with same CallerReference exists: {}",
247 existing.id
248 ),
249 ));
250 }
251 let id = generate_id_with_prefix("FP");
252 let etag = generate_id_with_prefix("E");
253 let stored = StoredFieldLevelEncryptionProfile {
254 id: id.clone(),
255 etag: etag.clone(),
256 last_modified_time: Utc::now(),
257 config: cfg,
258 };
259 account
260 .field_level_encryption_profiles
261 .insert(id.clone(), stored.clone());
262 drop(state);
263 let body = render_fle_profile(&stored);
264 Ok(xml_with_etag(StatusCode::CREATED, body, &etag, Some(&id)))
265 }
266
267 pub(crate) fn get_field_level_encryption_profile(
268 &self,
269 route: &Route,
270 ) -> Result<AwsResponse, AwsServiceError> {
271 let id = route_id(route, "FieldLevelEncryptionProfile")?;
272 let state = self.state.read();
273 let p = state
274 .accounts
275 .get(DEFAULT_ACCOUNT)
276 .and_then(|a| a.field_level_encryption_profiles.get(&id).cloned())
277 .ok_or_else(|| not_found("FieldLevelEncryptionProfile", &id))?;
278 drop(state);
279 let body = render_fle_profile(&p);
280 Ok(xml_with_etag(StatusCode::OK, body, &p.etag, None))
281 }
282
283 pub(crate) fn get_field_level_encryption_profile_config(
284 &self,
285 route: &Route,
286 ) -> Result<AwsResponse, AwsServiceError> {
287 let id = route_id(route, "FieldLevelEncryptionProfile")?;
288 let state = self.state.read();
289 let p = state
290 .accounts
291 .get(DEFAULT_ACCOUNT)
292 .and_then(|a| a.field_level_encryption_profiles.get(&id).cloned())
293 .ok_or_else(|| not_found("FieldLevelEncryptionProfile", &id))?;
294 drop(state);
295 let body = render_fle_profile_config(&p.config);
296 Ok(xml_with_etag(StatusCode::OK, body, &p.etag, None))
297 }
298
299 pub(crate) fn update_field_level_encryption_profile(
300 &self,
301 req: &AwsRequest,
302 route: &Route,
303 ) -> Result<AwsResponse, AwsServiceError> {
304 let id = route_id(route, "FieldLevelEncryptionProfile")?;
305 let if_match = require_if_match(req)?;
306 let cfg: FieldLevelEncryptionProfileConfig =
307 xml_io::from_xml_root(&req.body).map_err(|e| {
308 invalid_argument(format!(
309 "invalid FieldLevelEncryptionProfileConfig XML: {e}"
310 ))
311 })?;
312 if cfg.name.is_empty() {
313 return Err(invalid_argument(
314 "FieldLevelEncryptionProfileConfig.Name is required",
315 ));
316 }
317 let mut state = self.state.write();
318 let account = state
319 .accounts
320 .get_mut(DEFAULT_ACCOUNT)
321 .ok_or_else(|| not_found("FieldLevelEncryptionProfile", &id))?;
322 let p = account
323 .field_level_encryption_profiles
324 .get_mut(&id)
325 .ok_or_else(|| not_found("FieldLevelEncryptionProfile", &id))?;
326 if p.etag != if_match {
327 return Err(precondition_failed());
328 }
329 if p.config.caller_reference != cfg.caller_reference {
330 return Err(invalid_argument(
331 "CallerReference cannot change on UpdateFieldLevelEncryptionProfile",
332 ));
333 }
334 p.config = cfg;
335 p.etag = generate_id_with_prefix("E");
336 p.last_modified_time = Utc::now();
337 let snap = p.clone();
338 drop(state);
339 let body = render_fle_profile(&snap);
340 Ok(xml_with_etag(StatusCode::OK, body, &snap.etag, None))
341 }
342
343 pub(crate) fn delete_field_level_encryption_profile(
344 &self,
345 req: &AwsRequest,
346 route: &Route,
347 ) -> Result<AwsResponse, AwsServiceError> {
348 let id = route_id(route, "FieldLevelEncryptionProfile")?;
349 let if_match = require_if_match(req)?;
350 let mut state = self.state.write();
351 let account = state
352 .accounts
353 .get_mut(DEFAULT_ACCOUNT)
354 .ok_or_else(|| not_found("FieldLevelEncryptionProfile", &id))?;
355 let p = account
356 .field_level_encryption_profiles
357 .get(&id)
358 .ok_or_else(|| not_found("FieldLevelEncryptionProfile", &id))?;
359 if p.etag != if_match {
360 return Err(precondition_failed());
361 }
362 account.field_level_encryption_profiles.remove(&id);
363 drop(state);
364 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
365 }
366
367 pub(crate) fn list_field_level_encryption_profiles(
368 &self,
369 _req: &AwsRequest,
370 ) -> Result<AwsResponse, AwsServiceError> {
371 let state = self.state.read();
372 let mut items: Vec<StoredFieldLevelEncryptionProfile> = state
373 .accounts
374 .get(DEFAULT_ACCOUNT)
375 .map(|a| {
376 a.field_level_encryption_profiles
377 .values()
378 .cloned()
379 .collect()
380 })
381 .unwrap_or_default();
382 drop(state);
383 items.sort_by(|a, b| a.config.name.cmp(&b.config.name));
384
385 let mut body = String::with_capacity(512);
386 body.push_str(XML_DECL);
387 body.push_str(&format!("<FieldLevelEncryptionProfileList xmlns=\"{NS}\">"));
388 body.push_str("<NextMarker></NextMarker>");
389 body.push_str("<MaxItems>100</MaxItems>");
390 body.push_str(&format!("<Quantity>{}</Quantity>", items.len()));
391 body.push_str("<Items>");
392 for p in &items {
393 body.push_str("<FieldLevelEncryptionProfileSummary>");
394 body.push_str(&format!("<Id>{}</Id>", esc(&p.id)));
395 body.push_str(&format!(
396 "<LastModifiedTime>{}</LastModifiedTime>",
397 rfc3339(&p.last_modified_time)
398 ));
399 body.push_str(&format!("<Name>{}</Name>", esc(&p.config.name)));
400 body.push_str(&render_encryption_entities(&p.config.encryption_entities));
401 if let Some(c) = &p.config.comment {
402 body.push_str(&format!("<Comment>{}</Comment>", esc(c)));
403 }
404 body.push_str("</FieldLevelEncryptionProfileSummary>");
405 }
406 body.push_str("</Items>");
407 body.push_str("</FieldLevelEncryptionProfileList>");
408 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
409 }
410}
411
412impl CloudFrontService {
415 pub(crate) fn create_realtime_log_config(
416 &self,
417 req: &AwsRequest,
418 ) -> Result<AwsResponse, AwsServiceError> {
419 let parsed: CreateRealtimeLogConfigRequest =
420 xml_io::from_xml_root(&req.body).map_err(|e| {
421 invalid_argument(format!("invalid CreateRealtimeLogConfigRequest XML: {e}"))
422 })?;
423 if parsed.name.is_empty() {
424 return Err(invalid_argument("Name is required"));
425 }
426 let mut state = self.state.write();
427 let account = state
428 .accounts
429 .entry(DEFAULT_ACCOUNT.to_string())
430 .or_default();
431 let arn = format!(
432 "arn:aws:cloudfront::{}:realtime-log-config/{}",
433 DEFAULT_ACCOUNT, parsed.name
434 );
435 if account.realtime_log_configs.contains_key(&arn) {
436 return Err(aws_error(
437 StatusCode::CONFLICT,
438 "RealtimeLogConfigAlreadyExists",
439 format!("RealtimeLogConfig {} already exists", parsed.name),
440 ));
441 }
442 let stored = StoredRealtimeLogConfig {
443 arn: arn.clone(),
444 name: parsed.name,
445 sampling_rate: parsed.sampling_rate,
446 end_points: parsed.end_points,
447 fields: parsed.fields,
448 };
449 account
450 .realtime_log_configs
451 .insert(arn.clone(), stored.clone());
452 drop(state);
453 let body = render_realtime_log(&stored, "CreateRealtimeLogConfigResult");
454 Ok(xml_response(StatusCode::CREATED, body, HeaderMap::new()))
455 }
456
457 pub(crate) fn get_realtime_log_config(
458 &self,
459 req: &AwsRequest,
460 ) -> Result<AwsResponse, AwsServiceError> {
461 let parsed: GetOrDeleteRealtimeLogConfigRequest = xml_io::from_xml_root(&req.body)
462 .map_err(|e| {
463 invalid_argument(format!("invalid GetRealtimeLogConfigRequest XML: {e}"))
464 })?;
465 let key = self.resolve_rtl_key(&parsed)?;
466 let state = self.state.read();
467 let r = state
468 .accounts
469 .get(DEFAULT_ACCOUNT)
470 .and_then(|a| a.realtime_log_configs.get(&key).cloned())
471 .ok_or_else(|| not_found("RealtimeLogConfig", &key))?;
472 drop(state);
473 let body = render_realtime_log(&r, "GetRealtimeLogConfigResult");
474 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
475 }
476
477 pub(crate) fn update_realtime_log_config(
478 &self,
479 req: &AwsRequest,
480 ) -> Result<AwsResponse, AwsServiceError> {
481 let parsed: UpdateRealtimeLogConfigRequest =
482 xml_io::from_xml_root(&req.body).map_err(|e| {
483 invalid_argument(format!("invalid UpdateRealtimeLogConfigRequest XML: {e}"))
484 })?;
485 if parsed.arn.is_empty() {
486 return Err(invalid_argument("ARN is required"));
487 }
488 let mut state = self.state.write();
489 let account = state
490 .accounts
491 .get_mut(DEFAULT_ACCOUNT)
492 .ok_or_else(|| not_found("RealtimeLogConfig", &parsed.arn))?;
493 let r = account
494 .realtime_log_configs
495 .get_mut(&parsed.arn)
496 .ok_or_else(|| not_found("RealtimeLogConfig", &parsed.arn))?;
497 r.sampling_rate = parsed.sampling_rate;
498 r.end_points = parsed.end_points;
499 r.fields = parsed.fields;
500 let snap = r.clone();
501 drop(state);
502 let body = render_realtime_log(&snap, "UpdateRealtimeLogConfigResult");
503 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
504 }
505
506 pub(crate) fn delete_realtime_log_config(
507 &self,
508 req: &AwsRequest,
509 ) -> Result<AwsResponse, AwsServiceError> {
510 let parsed: GetOrDeleteRealtimeLogConfigRequest = xml_io::from_xml_root(&req.body)
511 .map_err(|e| {
512 invalid_argument(format!("invalid DeleteRealtimeLogConfigRequest XML: {e}"))
513 })?;
514 let key = self.resolve_rtl_key(&parsed)?;
515 let mut state = self.state.write();
516 let account = state
517 .accounts
518 .get_mut(DEFAULT_ACCOUNT)
519 .ok_or_else(|| not_found("RealtimeLogConfig", &key))?;
520 if account.realtime_log_configs.remove(&key).is_none() {
521 return Err(not_found("RealtimeLogConfig", &key));
522 }
523 drop(state);
524 Ok(crate::policies::empty(StatusCode::NO_CONTENT))
525 }
526
527 pub(crate) fn list_realtime_log_configs(
528 &self,
529 _req: &AwsRequest,
530 ) -> Result<AwsResponse, AwsServiceError> {
531 let state = self.state.read();
532 let mut items: Vec<StoredRealtimeLogConfig> = state
533 .accounts
534 .get(DEFAULT_ACCOUNT)
535 .map(|a| a.realtime_log_configs.values().cloned().collect())
536 .unwrap_or_default();
537 drop(state);
538 items.sort_by(|a, b| a.name.cmp(&b.name));
539
540 let mut body = String::with_capacity(512);
541 body.push_str(XML_DECL);
542 body.push_str(&format!("<RealtimeLogConfigs xmlns=\"{NS}\">"));
543 body.push_str("<MaxItems>100</MaxItems>");
544 body.push_str(&format!("<IsTruncated>{}</IsTruncated>", false));
545 body.push_str("<Marker></Marker>");
546 body.push_str("<Items>");
547 for r in &items {
548 body.push_str("<member>");
549 push_realtime_log_inner(&mut body, r);
550 body.push_str("</member>");
551 }
552 body.push_str("</Items>");
553 body.push_str("</RealtimeLogConfigs>");
554 Ok(xml_response(StatusCode::OK, body, HeaderMap::new()))
555 }
556
557 fn resolve_rtl_key(
558 &self,
559 parsed: &GetOrDeleteRealtimeLogConfigRequest,
560 ) -> Result<String, AwsServiceError> {
561 if let Some(arn) = &parsed.arn {
562 if !arn.is_empty() {
563 return Ok(arn.clone());
564 }
565 }
566 if let Some(name) = &parsed.name {
567 if !name.is_empty() {
568 return Ok(format!(
569 "arn:aws:cloudfront::{}:realtime-log-config/{}",
570 DEFAULT_ACCOUNT, name
571 ));
572 }
573 }
574 Err(invalid_argument("Either Name or ARN must be specified"))
575 }
576}
577
578fn render_fle(f: &StoredFieldLevelEncryption) -> String {
581 let mut out = String::with_capacity(512);
582 out.push_str(XML_DECL);
583 out.push_str(&format!("<FieldLevelEncryption xmlns=\"{NS}\">"));
584 out.push_str(&format!("<Id>{}</Id>", esc(&f.id)));
585 out.push_str(&format!(
586 "<LastModifiedTime>{}</LastModifiedTime>",
587 rfc3339(&f.last_modified_time)
588 ));
589 out.push_str(&render_fle_config_inner(&f.config));
590 out.push_str("</FieldLevelEncryption>");
591 out
592}
593
594fn render_fle_config(cfg: &FieldLevelEncryptionConfig) -> String {
595 let mut out = String::with_capacity(512);
596 out.push_str(XML_DECL);
597 out.push_str(&format!("<FieldLevelEncryptionConfig xmlns=\"{NS}\">"));
598 out.push_str(&render_fle_config_body(cfg));
599 out.push_str("</FieldLevelEncryptionConfig>");
600 out
601}
602
603fn render_fle_config_inner(cfg: &FieldLevelEncryptionConfig) -> String {
604 let mut out = String::with_capacity(512);
605 out.push_str("<FieldLevelEncryptionConfig>");
606 out.push_str(&render_fle_config_body(cfg));
607 out.push_str("</FieldLevelEncryptionConfig>");
608 out
609}
610
611fn render_fle_config_body(cfg: &FieldLevelEncryptionConfig) -> String {
612 let mut out = String::with_capacity(512);
613 out.push_str(&format!(
614 "<CallerReference>{}</CallerReference>",
615 esc(&cfg.caller_reference)
616 ));
617 if let Some(c) = &cfg.comment {
618 out.push_str(&format!("<Comment>{}</Comment>", esc(c)));
619 }
620 out.push_str(&render_query_arg_profile_config(
621 &cfg.query_arg_profile_config,
622 ));
623 out.push_str(&render_content_type_profile_config(
624 &cfg.content_type_profile_config,
625 ));
626 out
627}
628
629fn render_query_arg_profile_config(cfg: &crate::fle::QueryArgProfileConfig) -> String {
630 let mut out = String::with_capacity(128);
631 out.push_str("<QueryArgProfileConfig>");
632 out.push_str(&format!(
633 "<ForwardWhenQueryArgProfileIsUnknown>{}</ForwardWhenQueryArgProfileIsUnknown>",
634 cfg.forward_when_query_arg_profile_is_unknown
635 ));
636 if let Some(qp) = &cfg.query_arg_profiles {
637 out.push_str("<QueryArgProfiles>");
638 out.push_str(&format!("<Quantity>{}</Quantity>", qp.quantity));
639 if let Some(items) = &qp.items {
640 out.push_str("<Items>");
641 for q in &items.query_arg_profile {
642 out.push_str("<QueryArgProfile>");
643 out.push_str(&format!("<QueryArg>{}</QueryArg>", esc(&q.query_arg)));
644 out.push_str(&format!("<ProfileId>{}</ProfileId>", esc(&q.profile_id)));
645 out.push_str("</QueryArgProfile>");
646 }
647 out.push_str("</Items>");
648 }
649 out.push_str("</QueryArgProfiles>");
650 }
651 out.push_str("</QueryArgProfileConfig>");
652 out
653}
654
655fn render_content_type_profile_config(cfg: &crate::fle::ContentTypeProfileConfig) -> String {
656 let mut out = String::with_capacity(128);
657 out.push_str("<ContentTypeProfileConfig>");
658 out.push_str(&format!(
659 "<ForwardWhenContentTypeIsUnknown>{}</ForwardWhenContentTypeIsUnknown>",
660 cfg.forward_when_content_type_is_unknown
661 ));
662 if let Some(ct) = &cfg.content_type_profiles {
663 out.push_str("<ContentTypeProfiles>");
664 out.push_str(&format!("<Quantity>{}</Quantity>", ct.quantity));
665 if let Some(items) = &ct.items {
666 out.push_str("<Items>");
667 for c in &items.content_type_profile {
668 out.push_str("<ContentTypeProfile>");
669 out.push_str(&format!("<Format>{}</Format>", esc(&c.format)));
670 if let Some(p) = &c.profile_id {
671 out.push_str(&format!("<ProfileId>{}</ProfileId>", esc(p)));
672 }
673 out.push_str(&format!(
674 "<ContentType>{}</ContentType>",
675 esc(&c.content_type)
676 ));
677 out.push_str("</ContentTypeProfile>");
678 }
679 out.push_str("</Items>");
680 }
681 out.push_str("</ContentTypeProfiles>");
682 }
683 out.push_str("</ContentTypeProfileConfig>");
684 out
685}
686
687fn render_fle_profile(p: &StoredFieldLevelEncryptionProfile) -> String {
688 let mut out = String::with_capacity(512);
689 out.push_str(XML_DECL);
690 out.push_str(&format!("<FieldLevelEncryptionProfile xmlns=\"{NS}\">"));
691 out.push_str(&format!("<Id>{}</Id>", esc(&p.id)));
692 out.push_str(&format!(
693 "<LastModifiedTime>{}</LastModifiedTime>",
694 rfc3339(&p.last_modified_time)
695 ));
696 out.push_str(&render_fle_profile_config_inner(&p.config));
697 out.push_str("</FieldLevelEncryptionProfile>");
698 out
699}
700
701fn render_fle_profile_config(cfg: &FieldLevelEncryptionProfileConfig) -> String {
702 let mut out = String::with_capacity(512);
703 out.push_str(XML_DECL);
704 out.push_str(&format!(
705 "<FieldLevelEncryptionProfileConfig xmlns=\"{NS}\">"
706 ));
707 out.push_str(&render_fle_profile_config_body(cfg));
708 out.push_str("</FieldLevelEncryptionProfileConfig>");
709 out
710}
711
712fn render_fle_profile_config_inner(cfg: &FieldLevelEncryptionProfileConfig) -> String {
713 let mut out = String::with_capacity(512);
714 out.push_str("<FieldLevelEncryptionProfileConfig>");
715 out.push_str(&render_fle_profile_config_body(cfg));
716 out.push_str("</FieldLevelEncryptionProfileConfig>");
717 out
718}
719
720fn render_fle_profile_config_body(cfg: &FieldLevelEncryptionProfileConfig) -> String {
721 let mut out = String::with_capacity(512);
722 out.push_str(&format!("<Name>{}</Name>", esc(&cfg.name)));
723 out.push_str(&format!(
724 "<CallerReference>{}</CallerReference>",
725 esc(&cfg.caller_reference)
726 ));
727 if let Some(c) = &cfg.comment {
728 out.push_str(&format!("<Comment>{}</Comment>", esc(c)));
729 }
730 out.push_str(&render_encryption_entities(&cfg.encryption_entities));
731 out
732}
733
734fn render_encryption_entities(ee: &crate::fle::EncryptionEntities) -> String {
735 let mut out = String::with_capacity(128);
736 out.push_str("<EncryptionEntities>");
737 out.push_str(&format!("<Quantity>{}</Quantity>", ee.quantity));
738 if let Some(items) = &ee.items {
739 out.push_str("<Items>");
740 for e in &items.encryption_entity {
741 out.push_str("<EncryptionEntity>");
742 out.push_str(&format!(
743 "<PublicKeyId>{}</PublicKeyId>",
744 esc(&e.public_key_id)
745 ));
746 out.push_str(&format!("<ProviderId>{}</ProviderId>", esc(&e.provider_id)));
747 out.push_str("<FieldPatterns>");
748 out.push_str(&format!(
749 "<Quantity>{}</Quantity>",
750 e.field_patterns.quantity
751 ));
752 if let Some(it) = &e.field_patterns.items {
753 out.push_str("<Items>");
754 for fp in &it.field_pattern {
755 out.push_str(&format!("<FieldPattern>{}</FieldPattern>", esc(fp)));
756 }
757 out.push_str("</Items>");
758 }
759 out.push_str("</FieldPatterns>");
760 out.push_str("</EncryptionEntity>");
761 }
762 out.push_str("</Items>");
763 }
764 out.push_str("</EncryptionEntities>");
765 out
766}
767
768fn render_realtime_log(r: &StoredRealtimeLogConfig, root: &str) -> String {
769 let mut out = String::with_capacity(512);
770 out.push_str(XML_DECL);
771 out.push_str(&format!("<{root} xmlns=\"{NS}\">"));
772 out.push_str("<RealtimeLogConfig>");
773 push_realtime_log_inner(&mut out, r);
774 out.push_str("</RealtimeLogConfig>");
775 out.push_str(&format!("</{root}>"));
776 out
777}
778
779fn push_realtime_log_inner(out: &mut String, r: &StoredRealtimeLogConfig) {
780 out.push_str(&format!("<ARN>{}</ARN>", esc(&r.arn)));
781 out.push_str(&format!("<Name>{}</Name>", esc(&r.name)));
782 out.push_str(&format!("<SamplingRate>{}</SamplingRate>", r.sampling_rate));
783 out.push_str("<EndPoints>");
784 for ep in &r.end_points.member {
785 out.push_str("<member>");
786 out.push_str(&format!(
787 "<StreamType>{}</StreamType>",
788 esc(&ep.stream_type)
789 ));
790 out.push_str("<KinesisStreamConfig>");
791 out.push_str(&format!(
792 "<RoleARN>{}</RoleARN>",
793 esc(&ep.kinesis_stream_config.role_arn)
794 ));
795 out.push_str(&format!(
796 "<StreamARN>{}</StreamARN>",
797 esc(&ep.kinesis_stream_config.stream_arn)
798 ));
799 out.push_str("</KinesisStreamConfig>");
800 out.push_str("</member>");
801 }
802 out.push_str("</EndPoints>");
803 out.push_str("<Fields>");
804 for f in &r.fields.field {
805 out.push_str(&format!("<Field>{}</Field>", esc(f)));
806 }
807 out.push_str("</Fields>");
808}