1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use http::StatusCode;
4use serde_json::{json, Value};
5
6use std::collections::{BTreeMap, HashMap};
7use std::sync::Arc;
8
9use tokio::sync::Mutex as AsyncMutex;
10
11use fakecloud_aws::arn::Arn;
12use fakecloud_core::delivery::DeliveryBus;
13use fakecloud_core::pagination::paginate;
14use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
15use fakecloud_core::validation::*;
16use fakecloud_persistence::SnapshotStore;
17
18use fakecloud_lambda::runtime::ContainerRuntime;
19use fakecloud_lambda::{LambdaInvocation, SharedLambdaState};
20use fakecloud_logs::SharedLogsState;
21
22use crate::state::{
23 ApiDestination, Archive, Connection, Endpoint, EventBridgeSnapshot, EventBridgeState, EventBus,
24 EventRule, EventTarget, PartnerEventSource, PutEvent, Replay, SharedEventBridgeState,
25 EVENTBRIDGE_SNAPSHOT_SCHEMA_VERSION,
26};
27
28pub struct EventBridgeService {
29 state: SharedEventBridgeState,
30 delivery: Arc<DeliveryBus>,
31 lambda_state: Option<SharedLambdaState>,
32 logs_state: Option<SharedLogsState>,
33 logs_persist: Option<fakecloud_persistence::SnapshotHook>,
34 container_runtime: Option<Arc<ContainerRuntime>>,
35 snapshot_store: Option<Arc<dyn SnapshotStore>>,
36 snapshot_lock: Arc<AsyncMutex<()>>,
37}
38
39impl EventBridgeService {
40 pub fn new(state: SharedEventBridgeState, delivery: Arc<DeliveryBus>) -> Self {
41 Self {
42 state,
43 delivery,
44 lambda_state: None,
45 logs_state: None,
46 logs_persist: None,
47 container_runtime: None,
48 snapshot_store: None,
49 snapshot_lock: Arc::new(AsyncMutex::new(())),
50 }
51 }
52
53 pub fn with_lambda(mut self, lambda_state: SharedLambdaState) -> Self {
54 self.lambda_state = Some(lambda_state);
55 self
56 }
57
58 pub fn with_logs(mut self, logs_state: SharedLogsState) -> Self {
59 self.logs_state = Some(logs_state);
60 self
61 }
62
63 pub fn with_logs_persist(mut self, hook: fakecloud_persistence::SnapshotHook) -> Self {
67 self.logs_persist = Some(hook);
68 self
69 }
70
71 pub fn with_runtime(mut self, runtime: Arc<ContainerRuntime>) -> Self {
72 self.container_runtime = Some(runtime);
73 self
74 }
75
76 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
77 self.snapshot_store = Some(store);
78 self
79 }
80
81 async fn save_snapshot(&self) {
85 save_eventbridge_snapshot(
86 &self.state,
87 self.snapshot_store.clone(),
88 &self.snapshot_lock,
89 )
90 .await;
91 }
92
93 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
98 let store = self.snapshot_store.clone()?;
99 let state = self.state.clone();
100 let lock = self.snapshot_lock.clone();
101 Some(Arc::new(move || {
102 let state = state.clone();
103 let store = store.clone();
104 let lock = lock.clone();
105 Box::pin(async move {
106 save_eventbridge_snapshot(&state, Some(store), &lock).await;
107 })
108 }))
109 }
110}
111
112pub async fn save_eventbridge_snapshot(
118 state: &SharedEventBridgeState,
119 store: Option<Arc<dyn SnapshotStore>>,
120 lock: &AsyncMutex<()>,
121) {
122 let Some(store) = store else {
123 return;
124 };
125 let _guard = lock.lock().await;
126 let snapshot = EventBridgeSnapshot {
127 schema_version: EVENTBRIDGE_SNAPSHOT_SCHEMA_VERSION,
128 accounts: Some(state.read().clone()),
129 state: None,
130 };
131 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
132 let bytes = serde_json::to_vec(&snapshot)
133 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
134 store.save(&bytes)
135 })
136 .await;
137 match join {
138 Ok(Ok(())) => {}
139 Ok(Err(err)) => tracing::error!(%err, "failed to write eventbridge snapshot"),
140 Err(err) => tracing::error!(%err, "eventbridge snapshot task panicked"),
141 }
142}
143
144#[async_trait]
145impl AwsService for EventBridgeService {
146 fn service_name(&self) -> &str {
147 "events"
148 }
149
150 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
151 let mutates = is_mutating_action(req.action.as_str());
152 let result = match req.action.as_str() {
153 "CreateEventBus" => self.create_event_bus(&req),
154 "DeleteEventBus" => self.delete_event_bus(&req),
155 "ListEventBuses" => self.list_event_buses(&req),
156 "DescribeEventBus" => self.describe_event_bus(&req),
157 "PutRule" => self.put_rule(&req),
158 "DeleteRule" => self.delete_rule(&req),
159 "ListRules" => self.list_rules(&req),
160 "DescribeRule" => self.describe_rule(&req),
161 "EnableRule" => self.enable_rule(&req),
162 "DisableRule" => self.disable_rule(&req),
163 "PutTargets" => self.put_targets(&req),
164 "RemoveTargets" => self.remove_targets(&req),
165 "ListTargetsByRule" => self.list_targets_by_rule(&req),
166 "ListRuleNamesByTarget" => self.list_rule_names_by_target(&req),
167 "PutEvents" => self.put_events(&req),
168 "PutPermission" => self.put_permission(&req),
169 "RemovePermission" => self.remove_permission(&req),
170 "TagResource" => self.tag_resource(&req),
171 "UntagResource" => self.untag_resource(&req),
172 "ListTagsForResource" => self.list_tags_for_resource(&req),
173 "CreateArchive" => self.create_archive(&req),
174 "DescribeArchive" => self.describe_archive(&req),
175 "ListArchives" => self.list_archives(&req),
176 "UpdateArchive" => self.update_archive(&req),
177 "DeleteArchive" => self.delete_archive(&req),
178 "CreateConnection" => self.create_connection(&req),
179 "DescribeConnection" => self.describe_connection(&req),
180 "ListConnections" => self.list_connections(&req),
181 "UpdateConnection" => self.update_connection(&req),
182 "DeleteConnection" => self.delete_connection(&req),
183 "CreateApiDestination" => self.create_api_destination(&req),
184 "DescribeApiDestination" => self.describe_api_destination(&req),
185 "ListApiDestinations" => self.list_api_destinations(&req),
186 "UpdateApiDestination" => self.update_api_destination(&req),
187 "DeleteApiDestination" => self.delete_api_destination(&req),
188 "StartReplay" => self.start_replay(&req),
189 "DescribeReplay" => self.describe_replay(&req),
190 "ListReplays" => self.list_replays(&req),
191 "CancelReplay" => self.cancel_replay(&req),
192 "CreatePartnerEventSource" => self.create_partner_event_source(&req),
193 "DeletePartnerEventSource" => self.delete_partner_event_source(&req),
194 "DescribePartnerEventSource" => self.describe_partner_event_source(&req),
195 "ListPartnerEventSources" => self.list_partner_event_sources(&req),
196 "ListPartnerEventSourceAccounts" => self.list_partner_event_source_accounts(&req),
197 "ActivateEventSource" => self.activate_event_source(&req),
198 "DeactivateEventSource" => self.deactivate_event_source(&req),
199 "DescribeEventSource" => self.describe_event_source(&req),
200 "ListEventSources" => self.list_event_sources(&req),
201 "PutPartnerEvents" => self.put_partner_events(&req),
202 "TestEventPattern" => self.test_event_pattern(&req),
203 "UpdateEventBus" => self.update_event_bus(&req),
204 "CreateEndpoint" => self.create_endpoint(&req),
205 "DeleteEndpoint" => self.delete_endpoint(&req),
206 "DescribeEndpoint" => self.describe_endpoint(&req),
207 "ListEndpoints" => self.list_endpoints(&req),
208 "UpdateEndpoint" => self.update_endpoint(&req),
209 "DeauthorizeConnection" => self.deauthorize_connection(&req),
210 _ => Err(AwsServiceError::action_not_implemented(
211 "events",
212 &req.action,
213 )),
214 };
215 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
216 self.save_snapshot().await;
217 }
218 result
219 }
220
221 fn supported_actions(&self) -> &[&str] {
222 &[
223 "CreateEventBus",
224 "DeleteEventBus",
225 "ListEventBuses",
226 "DescribeEventBus",
227 "PutRule",
228 "DeleteRule",
229 "ListRules",
230 "DescribeRule",
231 "EnableRule",
232 "DisableRule",
233 "PutTargets",
234 "RemoveTargets",
235 "ListTargetsByRule",
236 "ListRuleNamesByTarget",
237 "PutEvents",
238 "PutPermission",
239 "RemovePermission",
240 "TagResource",
241 "UntagResource",
242 "ListTagsForResource",
243 "CreateArchive",
244 "DescribeArchive",
245 "ListArchives",
246 "UpdateArchive",
247 "DeleteArchive",
248 "CreateConnection",
249 "DescribeConnection",
250 "ListConnections",
251 "UpdateConnection",
252 "DeleteConnection",
253 "CreateApiDestination",
254 "DescribeApiDestination",
255 "ListApiDestinations",
256 "UpdateApiDestination",
257 "DeleteApiDestination",
258 "StartReplay",
259 "DescribeReplay",
260 "ListReplays",
261 "CancelReplay",
262 "CreatePartnerEventSource",
263 "DeletePartnerEventSource",
264 "DescribePartnerEventSource",
265 "ListPartnerEventSources",
266 "ListPartnerEventSourceAccounts",
267 "ActivateEventSource",
268 "DeactivateEventSource",
269 "DescribeEventSource",
270 "ListEventSources",
271 "PutPartnerEvents",
272 "TestEventPattern",
273 "UpdateEventBus",
274 "CreateEndpoint",
275 "DeleteEndpoint",
276 "DescribeEndpoint",
277 "ListEndpoints",
278 "UpdateEndpoint",
279 "DeauthorizeConnection",
280 ]
281 }
282}
283
284impl EventBridgeService {
286 fn create_event_bus(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
287 let body = req.json_body();
288 validate_required("Name", &body["Name"])?;
289 let name = body["Name"]
290 .as_str()
291 .ok_or_else(|| missing("Name"))?
292 .to_string();
293 validate_string_length("name", &name, 1, 256)?;
294 validate_optional_string_length(
295 "eventSourceName",
296 body["EventSourceName"].as_str(),
297 1,
298 256,
299 )?;
300 validate_optional_string_length("description", body["Description"].as_str(), 0, 512)?;
301 validate_optional_string_length(
302 "kmsKeyIdentifier",
303 body["KmsKeyIdentifier"].as_str(),
304 0,
305 2048,
306 )?;
307
308 if name.contains('/') && !name.starts_with("aws.partner/") {
310 return Err(AwsServiceError::aws_error(
311 StatusCode::BAD_REQUEST,
312 "ValidationException",
313 "Event bus name must not contain '/'.",
314 ));
315 }
316
317 if name.starts_with("aws.partner/") {
319 let event_source = body["EventSourceName"].as_str().unwrap_or("");
320 let accounts_r = self.state.read();
321 let empty_r = EventBridgeState::new(&req.account_id, &req.region);
322 let state_r = accounts_r.get(&req.account_id).unwrap_or(&empty_r);
323 let has_source = state_r.partner_event_sources.contains_key(event_source);
324 drop(accounts_r);
325 if !has_source {
326 return Err(AwsServiceError::aws_error(
327 StatusCode::BAD_REQUEST,
328 "ResourceNotFoundException",
329 format!("Event source {event_source} does not exist."),
330 ));
331 }
332 }
333
334 let mut accounts = self.state.write();
335 let state = accounts.get_or_create(&req.account_id);
336
337 if state.buses.contains_key(&name) {
338 return Err(AwsServiceError::aws_error(
339 StatusCode::BAD_REQUEST,
340 "ResourceAlreadyExistsException",
341 format!("Event bus {name} already exists."),
342 ));
343 }
344
345 let arn = format!(
346 "arn:aws:events:{}:{}:event-bus/{}",
347 req.region, state.account_id, name
348 );
349 let now = Utc::now();
350 let description = body["Description"].as_str().map(|s| s.to_string());
351 let kms_key_identifier = body["KmsKeyIdentifier"].as_str().map(|s| s.to_string());
352 let dead_letter_config = body.get("DeadLetterConfig").cloned();
353
354 let tags = parse_tags(&body);
355
356 let bus = EventBus {
357 name: name.clone(),
358 arn: arn.clone(),
359 tags,
360 policy: None,
361 description,
362 kms_key_identifier,
363 dead_letter_config,
364 creation_time: now,
365 last_modified_time: now,
366 };
367 state.buses.insert(name, bus);
368
369 Ok(AwsResponse::ok_json(json!({ "EventBusArn": arn })))
370 }
371
372 fn delete_event_bus(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
373 let body = req.json_body();
374 validate_required("Name", &body["Name"])?;
375 let name = body["Name"].as_str().ok_or_else(|| missing("Name"))?;
376 validate_string_length("name", name, 1, 256)?;
377
378 if name == "default" {
379 return Err(AwsServiceError::aws_error(
380 StatusCode::BAD_REQUEST,
381 "ValidationException",
382 format!("Cannot delete event bus {name}."),
383 ));
384 }
385
386 let mut accounts = self.state.write();
387 let state = accounts.get_or_create(&req.account_id);
388 state.buses.remove(name);
389 state.rules.retain(|k, _| k.0 != name);
390
391 Ok(AwsResponse::ok_json(json!({})))
392 }
393
394 fn list_event_buses(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
395 let body = req.json_body();
396 validate_optional_string_length_value("NamePrefix", &body["NamePrefix"], 1, 256)?;
404 validate_optional_string_length_value("NextToken", &body["NextToken"], 1, 2048)?;
405 validate_optional_json_range("Limit", &body["Limit"], 1, 100)?;
406 let name_prefix = body["NamePrefix"].as_str();
407 let limit = body["Limit"].as_i64().unwrap_or(100).clamp(1, 100) as usize;
408
409 let accounts = self.state.read();
410 let empty = EventBridgeState::new(&req.account_id, &req.region);
411 let state = accounts.get(&req.account_id).unwrap_or(&empty);
412 let filtered: Vec<&_> = state
413 .buses
414 .values()
415 .filter(|b| match name_prefix {
416 Some(prefix) => b.name.starts_with(prefix),
417 None => true,
418 })
419 .collect();
420
421 let (page, next_token) = paginate(&filtered, body["NextToken"].as_str(), limit);
422 let buses: Vec<Value> = page
423 .iter()
424 .map(|b| {
425 json!({
429 "Name": b.name,
430 "Arn": arn_with_request_region(&b.arn, &req.region),
431 "CreationTime": b.creation_time.timestamp() as f64,
432 "LastModifiedTime": b.last_modified_time.timestamp() as f64,
433 })
434 })
435 .collect();
436 let mut resp = json!({ "EventBuses": buses });
437 if let Some(token) = next_token {
438 resp["NextToken"] = json!(token);
439 }
440
441 Ok(AwsResponse::ok_json(resp))
442 }
443
444 fn describe_event_bus(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
445 let body = req.json_body();
446 validate_optional_string_length("name", body["Name"].as_str(), 1, 1600)?;
447 let name = body["Name"].as_str().unwrap_or("default");
448
449 let accounts = self.state.read();
450 let empty = EventBridgeState::new(&req.account_id, &req.region);
451 let state = accounts.get(&req.account_id).unwrap_or(&empty);
452 let bus = state.buses.get(name).ok_or_else(|| {
453 AwsServiceError::aws_error(
454 StatusCode::BAD_REQUEST,
455 "ResourceNotFoundException",
456 format!("Event bus {name} does not exist."),
457 )
458 })?;
459
460 let mut resp = json!({
461 "Name": bus.name,
462 "Arn": arn_with_request_region(&bus.arn, &req.region),
463 "CreationTime": bus.creation_time.timestamp() as f64,
464 "LastModifiedTime": bus.last_modified_time.timestamp() as f64,
465 });
466
467 if let Some(ref policy) = bus.policy {
468 resp["Policy"] = Value::String(serde_json::to_string(policy).unwrap());
469 }
470 if let Some(ref desc) = bus.description {
471 resp["Description"] = json!(desc);
472 }
473 if let Some(ref kms) = bus.kms_key_identifier {
474 resp["KmsKeyIdentifier"] = json!(kms);
475 }
476 if let Some(ref dlc) = bus.dead_letter_config {
477 resp["DeadLetterConfig"] = dlc.clone();
478 }
479
480 Ok(AwsResponse::ok_json(resp))
481 }
482
483 fn put_permission(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
486 let body = req.json_body();
487 validate_optional_string_length_value("EventBusName", &body["EventBusName"], 1, 256)?;
494 validate_optional_string_length_value("Action", &body["Action"], 1, 64)?;
495 validate_optional_string_length_value("Principal", &body["Principal"], 1, 12)?;
496 validate_optional_string_length_value("StatementId", &body["StatementId"], 1, 64)?;
497 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
498
499 let mut accounts = self.state.write();
500 let state = accounts.get_or_create(&req.account_id);
501
502 let bus = state.buses.get_mut(event_bus_name).ok_or_else(|| {
503 AwsServiceError::aws_error(
504 StatusCode::BAD_REQUEST,
505 "ResourceNotFoundException",
506 format!("Event bus {event_bus_name} does not exist."),
507 )
508 })?;
509
510 if let Some(policy_str) = body["Policy"].as_str() {
512 if let Ok(policy) = serde_json::from_str::<Value>(policy_str) {
513 bus.policy = Some(policy);
514 return Ok(AwsResponse::ok_json(json!({})));
515 }
516 }
517
518 let action = body["Action"].as_str().unwrap_or("");
525 let principal = body["Principal"].as_str().unwrap_or("");
526 let statement_id = body["StatementId"].as_str().unwrap_or("");
527
528 let principal_value = if principal == "*" {
537 json!("*")
538 } else {
539 json!({ "AWS": Arn::global("iam", principal, "root").to_string() })
540 };
541 let statement = json!({
542 "Sid": statement_id,
543 "Effect": "Allow",
544 "Principal": principal_value,
545 "Action": action,
546 "Resource": arn_with_request_region(&bus.arn, &req.region),
547 });
548
549 let policy = bus.policy.get_or_insert_with(|| {
550 json!({
551 "Version": "2012-10-17",
552 "Statement": [],
553 })
554 });
555
556 if let Some(stmts) = policy["Statement"].as_array_mut() {
557 stmts.retain(|s| s["Sid"].as_str() != Some(statement_id));
561 stmts.push(statement);
562 }
563
564 Ok(AwsResponse::ok_json(json!({})))
565 }
566
567 fn remove_permission(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
568 let body = req.json_body();
569 validate_optional_string_length("statementId", body["StatementId"].as_str(), 1, 64)?;
570 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 256)?;
571 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
572 let statement_id = body["StatementId"].as_str().unwrap_or("");
573 let remove_all = body["RemoveAllPermissions"].as_bool().unwrap_or(false);
574
575 let mut accounts = self.state.write();
576 let state = accounts.get_or_create(&req.account_id);
577
578 let bus = state.buses.get_mut(event_bus_name).ok_or_else(|| {
579 AwsServiceError::aws_error(
580 StatusCode::BAD_REQUEST,
581 "ResourceNotFoundException",
582 format!("Event bus {event_bus_name} does not exist."),
583 )
584 })?;
585
586 if remove_all {
587 bus.policy = None;
588 return Ok(AwsResponse::ok_json(json!({})));
589 }
590
591 let policy = bus.policy.as_mut().ok_or_else(|| {
592 AwsServiceError::aws_error(
593 StatusCode::BAD_REQUEST,
594 "ResourceNotFoundException",
595 "EventBus does not have a policy.",
596 )
597 })?;
598
599 if let Some(stmts) = policy["Statement"].as_array_mut() {
600 let before = stmts.len();
601 stmts.retain(|s| s["Sid"].as_str() != Some(statement_id));
602 if stmts.len() == before {
603 return Err(AwsServiceError::aws_error(
604 StatusCode::BAD_REQUEST,
605 "ResourceNotFoundException",
606 "Statement with the provided id does not exist.",
607 ));
608 }
609 if stmts.is_empty() {
610 bus.policy = None;
611 }
612 }
613
614 Ok(AwsResponse::ok_json(json!({})))
615 }
616
617 fn put_rule(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
620 let body = req.json_body();
621 validate_required("Name", &body["Name"])?;
632 let name = body["Name"]
633 .as_str()
634 .ok_or_else(|| missing("Name"))?
635 .to_string();
636 validate_string_length("Name", &name, 1, 64)?;
637 validate_optional_string_length_value(
638 "ScheduleExpression",
639 &body["ScheduleExpression"],
640 0,
641 256,
642 )?;
643 validate_optional_string_length_value("EventPattern", &body["EventPattern"], 0, 4096)?;
644 if let Some(pattern) = body["EventPattern"].as_str().filter(|s| !s.is_empty()) {
648 validate_event_pattern(pattern)?;
649 }
650 if let Some(schedule) = body["ScheduleExpression"]
654 .as_str()
655 .filter(|s| !s.is_empty())
656 {
657 if !crate::scheduler::is_valid_schedule_expression(schedule) {
658 return Err(AwsServiceError::aws_error(
659 StatusCode::BAD_REQUEST,
660 "ValidationException",
661 format!(
662 "Parameter ScheduleExpression is not valid. Reason: {schedule} is not a valid expression."
663 ),
664 ));
665 }
666 }
667 validate_optional_enum_value(
668 "State",
669 &body["State"],
670 &[
671 "ENABLED",
672 "DISABLED",
673 "ENABLED_WITH_ALL_CLOUDTRAIL_MANAGEMENT_EVENTS",
674 ],
675 )?;
676 validate_optional_string_length_value("Description", &body["Description"], 0, 512)?;
677 validate_optional_string_length_value("RoleArn", &body["RoleArn"], 1, 1600)?;
678 validate_optional_string_length_value("EventBusName", &body["EventBusName"], 1, 1600)?;
679
680 let raw_bus = body["EventBusName"]
681 .as_str()
682 .unwrap_or("default")
683 .to_string();
684
685 let mut accounts = self.state.write();
686 let state = accounts.get_or_create(&req.account_id);
687 let event_bus_name = state.resolve_bus_name(&raw_bus);
688
689 let event_pattern = body["EventPattern"].as_str().and_then(|s| {
690 if s.is_empty() {
691 None
692 } else {
693 Some(s.to_string())
694 }
695 });
696 let schedule_expression = body["ScheduleExpression"].as_str().and_then(|s| {
697 if s.is_empty() {
698 None
699 } else {
700 Some(s.to_string())
701 }
702 });
703 let description = body["Description"].as_str().map(|s| s.to_string());
704 let role_arn = body["RoleArn"].as_str().map(|s| s.to_string());
705 let rule_state = body["State"].as_str().unwrap_or("ENABLED").to_string();
706
707 if !state.buses.contains_key(&event_bus_name) {
713 return Err(AwsServiceError::aws_error(
714 StatusCode::BAD_REQUEST,
715 "ResourceNotFoundException",
716 format!("Event bus {event_bus_name} does not exist."),
717 ));
718 }
719
720 let arn = if event_bus_name == "default" {
721 format!(
722 "arn:aws:events:{}:{}:rule/{}",
723 req.region, state.account_id, name
724 )
725 } else {
726 format!(
727 "arn:aws:events:{}:{}:rule/{}/{}",
728 req.region, state.account_id, event_bus_name, name
729 )
730 };
731
732 let key = (event_bus_name.clone(), name.clone());
733 let (targets, existing_tags, existing_managed_by, existing_created_by) = state
740 .rules
741 .get(&key)
742 .map(|r| {
743 (
744 r.targets.clone(),
745 r.tags.clone(),
746 r.managed_by.clone(),
747 r.created_by.clone(),
748 )
749 })
750 .unwrap_or_default();
751
752 let tags = if body.get("Tags").map(|t| t.is_array()).unwrap_or(false) {
757 parse_tags(&body)
758 } else {
759 existing_tags
760 };
761
762 let rule = EventRule {
763 name: name.clone(),
764 arn: arn.clone(),
765 event_bus_name,
766 event_pattern,
767 schedule_expression,
768 state: rule_state,
769 description,
770 role_arn,
771 managed_by: existing_managed_by,
772 created_by: existing_created_by,
773 targets,
774 tags,
775 last_fired: None,
776 };
777
778 state.rules.insert(key, rule);
779 Ok(AwsResponse::ok_json(json!({ "RuleArn": arn })))
780 }
781
782 fn delete_rule(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
783 let body = req.json_body();
784 validate_required("Name", &body["Name"])?;
785 let name = body["Name"].as_str().ok_or_else(|| missing("Name"))?;
786 validate_string_length("name", name, 1, 64)?;
787 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
788 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
789
790 let mut accounts = self.state.write();
791 let state = accounts.get_or_create(&req.account_id);
792 let bus_name = state.resolve_bus_name(event_bus_name);
793 let key = (bus_name, name.to_string());
794
795 if let Some(rule) = state.rules.get(&key) {
797 if !rule.targets.is_empty() {
798 return Err(AwsServiceError::aws_error(
799 StatusCode::BAD_REQUEST,
800 "ValidationException",
801 "Rule can't be deleted since it has targets.",
802 ));
803 }
804 }
805
806 state.rules.remove(&key);
807 Ok(AwsResponse::ok_json(json!({})))
808 }
809
810 fn list_rules(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
811 let body = req.json_body();
812 validate_optional_string_length("namePrefix", body["NamePrefix"].as_str(), 1, 64)?;
813 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
814 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 2048)?;
815 validate_optional_range_i64("limit", body["Limit"].as_i64(), 1, 100)?;
816 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
817 let name_prefix = body["NamePrefix"].as_str();
818 let limit = body["Limit"].as_u64().map(|n| n as usize);
819 let next_token = body["NextToken"].as_str();
820
821 let accounts = self.state.read();
822 let empty = EventBridgeState::new(&req.account_id, &req.region);
823 let state = accounts.get(&req.account_id).unwrap_or(&empty);
824 let bus_name = state.resolve_bus_name(event_bus_name);
825
826 let mut rules: Vec<&EventRule> = state
827 .rules
828 .values()
829 .filter(|r| r.event_bus_name == bus_name)
830 .filter(|r| match name_prefix {
831 Some(prefix) => r.name.starts_with(prefix),
832 None => true,
833 })
834 .collect();
835 rules.sort_by(|a, b| a.name.cmp(&b.name));
836
837 let start = next_token
839 .and_then(|t| t.parse::<usize>().ok())
840 .unwrap_or(0)
841 .min(rules.len());
842 let rules_slice = &rules[start..];
843
844 let (page, new_next_token) = if let Some(lim) = limit {
845 if rules_slice.len() > lim {
846 (&rules_slice[..lim], Some((start + lim).to_string()))
847 } else {
848 (rules_slice, None)
849 }
850 } else {
851 (rules_slice, None)
852 };
853
854 let rules_json: Vec<Value> = page
855 .iter()
856 .map(|r| {
857 let mut obj = json!({
858 "Name": r.name,
859 "Arn": r.arn,
860 "EventBusName": r.event_bus_name,
861 "State": r.state,
862 });
863 if let Some(ref desc) = r.description {
864 obj["Description"] = json!(desc);
865 }
866 if let Some(ref ep) = r.event_pattern {
867 obj["EventPattern"] = json!(ep);
868 }
869 if let Some(ref se) = r.schedule_expression {
870 obj["ScheduleExpression"] = json!(se);
871 }
872 if let Some(ref role) = r.role_arn {
873 obj["RoleArn"] = json!(role);
874 }
875 if let Some(ref mb) = r.managed_by {
876 obj["ManagedBy"] = json!(mb);
877 }
878 obj
879 })
880 .collect();
881
882 let mut resp = json!({ "Rules": rules_json });
883 if let Some(token) = new_next_token {
884 resp["NextToken"] = json!(token);
885 }
886
887 Ok(AwsResponse::ok_json(resp))
888 }
889
890 fn describe_rule(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
891 let body = req.json_body();
892 validate_required("Name", &body["Name"])?;
893 let name = body["Name"].as_str().ok_or_else(|| missing("Name"))?;
894 validate_string_length("name", name, 1, 64)?;
895 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
896 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
897
898 let accounts = self.state.read();
899 let empty = EventBridgeState::new(&req.account_id, &req.region);
900 let state = accounts.get(&req.account_id).unwrap_or(&empty);
901 let bus_name = state.resolve_bus_name(event_bus_name);
902 let key = (bus_name.clone(), name.to_string());
903
904 let rule = state.rules.get(&key).ok_or_else(|| {
905 AwsServiceError::aws_error(
906 StatusCode::BAD_REQUEST,
907 "ResourceNotFoundException",
908 format!("Rule {name} does not exist."),
909 )
910 })?;
911
912 let mut resp = json!({
913 "Name": rule.name,
914 "Arn": rule.arn,
915 "EventBusName": rule.event_bus_name,
916 "State": rule.state,
917 });
918
919 if let Some(ref desc) = rule.description {
920 resp["Description"] = json!(desc);
921 }
922 if let Some(ref ep) = rule.event_pattern {
923 resp["EventPattern"] = json!(ep);
924 }
925 if let Some(ref se) = rule.schedule_expression {
926 resp["ScheduleExpression"] = json!(se);
927 }
928 if let Some(ref role) = rule.role_arn {
929 resp["RoleArn"] = json!(role);
930 }
931 if let Some(ref mb) = rule.managed_by {
932 resp["ManagedBy"] = json!(mb);
933 }
934 if let Some(ref cb) = rule.created_by {
935 resp["CreatedBy"] = json!(cb);
936 }
937 if rule.event_bus_name != "default" && rule.created_by.is_none() {
939 resp["CreatedBy"] = json!(state.account_id);
940 }
941
942 Ok(AwsResponse::ok_json(resp))
943 }
944
945 fn enable_rule(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
946 let body = req.json_body();
947 validate_required("Name", &body["Name"])?;
948 let name = body["Name"].as_str().ok_or_else(|| missing("Name"))?;
949 validate_string_length("name", name, 1, 64)?;
950 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
951 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
952
953 let mut accounts = self.state.write();
954 let state = accounts.get_or_create(&req.account_id);
955 let bus_name = state.resolve_bus_name(event_bus_name);
956 let key = (bus_name, name.to_string());
957
958 let rule = state.rules.get_mut(&key).ok_or_else(|| {
959 AwsServiceError::aws_error(
960 StatusCode::BAD_REQUEST,
961 "ResourceNotFoundException",
962 format!("Rule {name} does not exist."),
963 )
964 })?;
965
966 rule.state = "ENABLED".to_string();
967 Ok(AwsResponse::ok_json(json!({})))
968 }
969
970 fn disable_rule(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
971 let body = req.json_body();
972 validate_required("Name", &body["Name"])?;
973 let name = body["Name"].as_str().ok_or_else(|| missing("Name"))?;
974 validate_string_length("name", name, 1, 64)?;
975 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
976 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
977
978 let mut accounts = self.state.write();
979 let state = accounts.get_or_create(&req.account_id);
980 let bus_name = state.resolve_bus_name(event_bus_name);
981 let key = (bus_name, name.to_string());
982
983 let rule = state.rules.get_mut(&key).ok_or_else(|| {
984 AwsServiceError::aws_error(
985 StatusCode::BAD_REQUEST,
986 "ResourceNotFoundException",
987 format!("Rule {name} does not exist."),
988 )
989 })?;
990
991 rule.state = "DISABLED".to_string();
992 Ok(AwsResponse::ok_json(json!({})))
993 }
994
995 fn put_targets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
998 let body = req.json_body();
999 validate_required("Rule", &body["Rule"])?;
1006 let rule_name = body["Rule"].as_str().ok_or_else(|| missing("Rule"))?;
1007 validate_string_length("Rule", rule_name, 1, 64)?;
1008 validate_optional_string_length_value("EventBusName", &body["EventBusName"], 1, 1600)?;
1009 validate_required("Targets", &body["Targets"])?;
1014 let targets_array = body["Targets"].as_array().ok_or_else(|| {
1015 AwsServiceError::aws_error(
1016 StatusCode::BAD_REQUEST,
1017 "ValidationException",
1018 "Targets must be a list",
1019 )
1020 })?;
1021 if targets_array.is_empty() || targets_array.len() > 100 {
1022 return Err(AwsServiceError::aws_error(
1023 StatusCode::BAD_REQUEST,
1024 "ValidationException",
1025 "Value at 'Targets' failed to satisfy constraint: \
1026 Member must have length between 1 and 100",
1027 ));
1028 }
1029 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
1030 let targets: Vec<Value> = targets_array.clone();
1031
1032 let mut accounts = self.state.write();
1033 let state = accounts.get_or_create(&req.account_id);
1034 let bus_name = state.resolve_bus_name(event_bus_name);
1035 let key = (bus_name.clone(), rule_name.to_string());
1036
1037 let rule = state.rules.get_mut(&key).ok_or_else(|| {
1038 AwsServiceError::aws_error(
1039 StatusCode::BAD_REQUEST,
1040 "ResourceNotFoundException",
1041 format!("Rule {rule_name} does not exist on EventBus {bus_name}."),
1042 )
1043 })?;
1044
1045 let mut failed_entries: Vec<Value> = Vec::new();
1046 for target in &targets {
1047 let target_id = target["Id"].as_str().unwrap_or("").to_string();
1048 let target_arn = target["Arn"].as_str().unwrap_or("");
1049
1050 if target_arn.ends_with(".fifo") && target.get("SqsParameters").is_none() {
1051 failed_entries.push(json!({
1052 "TargetId": target_id,
1053 "ErrorCode": "ValidationException",
1054 "ErrorMessage": format!(
1055 "Parameter(s) SqsParameters must be specified for target: {target_id}."
1056 ),
1057 }));
1058 continue;
1059 }
1060 if !target_arn.starts_with("arn:") {
1061 failed_entries.push(json!({
1062 "TargetId": target_id,
1063 "ErrorCode": "ValidationException",
1064 "ErrorMessage": format!(
1065 "Parameter {target_arn} is not valid. Reason: Provided Arn is not in correct format."
1066 ),
1067 }));
1068 continue;
1069 }
1070
1071 let input_fields = ["Input", "InputPath", "InputTransformer"]
1078 .iter()
1079 .filter(|k| !target[**k].is_null())
1080 .count();
1081 if input_fields > 1 {
1082 failed_entries.push(json!({
1083 "TargetId": target_id,
1084 "ErrorCode": "ValidationException",
1085 "ErrorMessage": format!(
1086 "Only one of Input, InputPath, or InputTransformer can be \
1087 specified for target: {target_id}."
1088 ),
1089 }));
1090 continue;
1091 }
1092
1093 let et = parse_target(target);
1094 rule.targets.retain(|t| t.id != et.id);
1095 rule.targets.push(et);
1096 }
1097
1098 Ok(AwsResponse::ok_json(json!({
1099 "FailedEntryCount": failed_entries.len(),
1100 "FailedEntries": failed_entries,
1101 })))
1102 }
1103
1104 fn remove_targets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1105 let body = req.json_body();
1106 validate_required("Rule", &body["Rule"])?;
1107 let rule_name = body["Rule"].as_str().ok_or_else(|| missing("Rule"))?;
1108 validate_string_length("rule", rule_name, 1, 64)?;
1109 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
1110 validate_required("Ids", &body["Ids"])?;
1111 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
1112 let ids = body["Ids"].as_array().ok_or_else(|| missing("Ids"))?;
1113
1114 let target_ids: Vec<String> = ids
1115 .iter()
1116 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1117 .collect();
1118
1119 let mut accounts = self.state.write();
1120 let state = accounts.get_or_create(&req.account_id);
1121 let bus_name = state.resolve_bus_name(event_bus_name);
1122 let key = (bus_name.clone(), rule_name.to_string());
1123
1124 let rule = state.rules.get_mut(&key).ok_or_else(|| {
1125 AwsServiceError::aws_error(
1126 StatusCode::BAD_REQUEST,
1127 "ResourceNotFoundException",
1128 format!("Rule {rule_name} does not exist on EventBus {bus_name}."),
1129 )
1130 })?;
1131
1132 rule.targets.retain(|t| !target_ids.contains(&t.id));
1133
1134 Ok(AwsResponse::ok_json(json!({
1135 "FailedEntryCount": 0,
1136 "FailedEntries": [],
1137 })))
1138 }
1139
1140 fn list_targets_by_rule(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1141 let body = req.json_body();
1142 validate_required("Rule", &body["Rule"])?;
1143 let rule_name = body["Rule"].as_str().ok_or_else(|| missing("Rule"))?;
1144 validate_string_length("rule", rule_name, 1, 64)?;
1145 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
1146 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 2048)?;
1147 validate_optional_range_i64("limit", body["Limit"].as_i64(), 1, 100)?;
1148 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
1149 let limit = body["Limit"].as_u64().map(|n| n as usize);
1150 let next_token = body["NextToken"].as_str();
1151
1152 let accounts = self.state.read();
1153 let empty = EventBridgeState::new(&req.account_id, &req.region);
1154 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1155 let bus_name = state.resolve_bus_name(event_bus_name);
1156 let key = (bus_name, rule_name.to_string());
1157
1158 let rule = state.rules.get(&key).ok_or_else(|| {
1159 AwsServiceError::aws_error(
1160 StatusCode::BAD_REQUEST,
1161 "ResourceNotFoundException",
1162 format!("Rule {rule_name} does not exist."),
1163 )
1164 })?;
1165
1166 let all_targets = &rule.targets;
1167 let start = next_token
1168 .and_then(|t| t.parse::<usize>().ok())
1169 .unwrap_or(0)
1170 .min(all_targets.len());
1171 let slice = &all_targets[start..];
1172
1173 let (page, new_next_token) = if let Some(lim) = limit {
1174 if slice.len() > lim {
1175 (&slice[..lim], Some((start + lim).to_string()))
1176 } else {
1177 (slice, None)
1178 }
1179 } else {
1180 (slice, None)
1181 };
1182
1183 let targets: Vec<Value> = page.iter().map(target_to_json).collect();
1184
1185 let mut resp = json!({ "Targets": targets });
1186 if let Some(token) = new_next_token {
1187 resp["NextToken"] = json!(token);
1188 }
1189
1190 Ok(AwsResponse::ok_json(resp))
1191 }
1192
1193 fn list_rule_names_by_target(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1194 let body = req.json_body();
1195 validate_required("TargetArn", &body["TargetArn"])?;
1196 let target_arn = body["TargetArn"]
1197 .as_str()
1198 .ok_or_else(|| missing("TargetArn"))?;
1199 validate_string_length("targetArn", target_arn, 1, 1600)?;
1200 validate_optional_string_length("eventBusName", body["EventBusName"].as_str(), 1, 1600)?;
1201 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 2048)?;
1202 validate_optional_range_i64("limit", body["Limit"].as_i64(), 1, 100)?;
1203 let event_bus_name = body["EventBusName"].as_str().unwrap_or("default");
1204 let limit = body["Limit"].as_u64().map(|n| n as usize);
1205 let next_token = body["NextToken"].as_str();
1206
1207 let accounts = self.state.read();
1208 let empty = EventBridgeState::new(&req.account_id, &req.region);
1209 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1210 let bus_name = state.resolve_bus_name(event_bus_name);
1211
1212 let mut rule_names: Vec<String> = Vec::new();
1214 for rule in state.rules.values() {
1215 if rule.event_bus_name == bus_name
1216 && rule.targets.iter().any(|t| t.arn == target_arn)
1217 && !rule_names.contains(&rule.name)
1218 {
1219 rule_names.push(rule.name.clone());
1220 }
1221 }
1222 rule_names.sort();
1223
1224 let start = next_token
1225 .and_then(|t| t.parse::<usize>().ok())
1226 .unwrap_or(0)
1227 .min(rule_names.len());
1228 let slice = &rule_names[start..];
1229
1230 let (page, new_next_token) = if let Some(lim) = limit {
1231 if slice.len() > lim {
1232 (&slice[..lim], Some((start + lim).to_string()))
1233 } else {
1234 (slice, None)
1235 }
1236 } else {
1237 (slice, None)
1238 };
1239
1240 let mut resp = json!({ "RuleNames": page });
1241 if let Some(token) = new_next_token {
1242 resp["NextToken"] = json!(token);
1243 }
1244
1245 Ok(AwsResponse::ok_json(resp))
1246 }
1247
1248 fn test_event_pattern(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1251 let body = req.json_body();
1252 validate_required("EventPattern", &body["EventPattern"])?;
1253 validate_required("Event", &body["Event"])?;
1254 let event_pattern = body["EventPattern"]
1255 .as_str()
1256 .ok_or_else(|| missing("EventPattern"))?;
1257 let event_str = body["Event"].as_str().ok_or_else(|| missing("Event"))?;
1258
1259 let event: Value = serde_json::from_str(event_str).map_err(|_| {
1261 AwsServiceError::aws_error(
1262 StatusCode::BAD_REQUEST,
1263 "InvalidEventPatternException",
1264 "Event is not valid JSON.",
1265 )
1266 })?;
1267
1268 let pattern: Value = serde_json::from_str(event_pattern).map_err(|_| {
1270 AwsServiceError::aws_error(
1271 StatusCode::BAD_REQUEST,
1272 "InvalidEventPatternException",
1273 "Event pattern is not valid JSON.",
1274 )
1275 })?;
1276
1277 validate_pattern_values(&pattern, "")?;
1281
1282 let result = matches_value(&pattern, &event);
1286
1287 Ok(AwsResponse::ok_json(json!({ "Result": result })))
1288 }
1289
1290 fn update_event_bus(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1293 let body = req.json_body();
1294 validate_optional_string_length("description", body["Description"].as_str(), 0, 512)?;
1295 validate_optional_string_length(
1296 "kmsKeyIdentifier",
1297 body["KmsKeyIdentifier"].as_str(),
1298 0,
1299 2048,
1300 )?;
1301 let name = body["Name"].as_str().unwrap_or("default");
1302
1303 let mut accounts = self.state.write();
1304 let state = accounts.get_or_create(&req.account_id);
1305 let bus = state.buses.get_mut(name).ok_or_else(|| {
1306 AwsServiceError::aws_error(
1307 StatusCode::BAD_REQUEST,
1308 "ResourceNotFoundException",
1309 format!("Event bus {name} does not exist."),
1310 )
1311 })?;
1312
1313 if let Some(desc) = body["Description"].as_str() {
1314 bus.description = Some(desc.to_string());
1315 }
1316 if let Some(kms) = body["KmsKeyIdentifier"].as_str() {
1317 bus.kms_key_identifier = Some(kms.to_string());
1318 }
1319 if let Some(dlc) = body.get("DeadLetterConfig") {
1320 bus.dead_letter_config = Some(dlc.clone());
1321 }
1322 bus.last_modified_time = Utc::now();
1323
1324 let arn = arn_with_request_region(&bus.arn, &req.region);
1325 let bus_name = bus.name.clone();
1326
1327 Ok(AwsResponse::ok_json(json!({
1328 "Arn": arn,
1329 "Name": bus_name,
1330 })))
1331 }
1332
1333 fn put_events(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1336 let body = req.json_body();
1337 validate_optional_string_length_value("EndpointId", &body["EndpointId"], 1, 50)?;
1341 validate_required("Entries", &body["Entries"])?;
1342 let entries_array = body["Entries"].as_array().ok_or_else(|| {
1343 AwsServiceError::aws_error(
1344 StatusCode::BAD_REQUEST,
1345 "ValidationException",
1346 "Entries must be a list",
1347 )
1348 })?;
1349 if entries_array.is_empty() || entries_array.len() > 10 {
1350 return Err(AwsServiceError::aws_error(
1351 StatusCode::BAD_REQUEST,
1352 "ValidationException",
1353 "Value at 'Entries' failed to satisfy constraint: \
1354 Member must have length between 1 and 10",
1355 ));
1356 }
1357 let entries: Vec<Value> = entries_array.clone();
1358 let entries = &entries;
1359
1360 let mut accounts = self.state.write();
1361 let state = accounts.get_or_create(&req.account_id);
1362 let mut result_entries = Vec::new();
1363 let mut events_to_deliver = Vec::new();
1364 let mut failed_count = 0;
1365
1366 for entry in entries {
1367 let source = entry["Source"].as_str().unwrap_or("").to_string();
1368 let detail_type = entry["DetailType"].as_str().unwrap_or("").to_string();
1369 let detail = entry["Detail"].as_str().unwrap_or("").to_string();
1370
1371 if let Err(error) = validate_put_events_entry(&source, &detail_type, &detail) {
1372 failed_count += 1;
1373 result_entries.push(error);
1374 continue;
1375 }
1376
1377 let event_id = uuid::Uuid::new_v4().to_string();
1378 let raw_bus = entry["EventBusName"]
1379 .as_str()
1380 .unwrap_or("default")
1381 .to_string();
1382 let event_bus_name = state.resolve_bus_name(&raw_bus);
1383
1384 let caller_account = req
1390 .principal
1391 .as_ref()
1392 .map(|p| p.account_id.as_str())
1393 .unwrap_or(req.account_id.as_str());
1394 if caller_account != req.account_id {
1395 let bus_policy_value = state
1396 .buses
1397 .get(&event_bus_name)
1398 .and_then(|b| b.policy.clone());
1399 if let Some(policy_value) = bus_policy_value {
1400 let policy_json = serde_json::to_string(&policy_value).unwrap_or_default();
1401 let policy_doc = fakecloud_iam::evaluator::PolicyDocument::parse(&policy_json);
1402 let bus_arn = state
1403 .buses
1404 .get(&event_bus_name)
1405 .map(|b| b.arn.clone())
1406 .unwrap_or_default();
1407 let principal =
1408 req.principal
1409 .clone()
1410 .unwrap_or_else(|| fakecloud_core::auth::Principal {
1411 arn: Arn::global("iam", caller_account, "root").to_string(),
1412 user_id: caller_account.to_string(),
1413 account_id: caller_account.to_string(),
1414 principal_type: fakecloud_core::auth::PrincipalType::Root,
1415 source_identity: None,
1416 tags: None,
1417 });
1418 let context = fakecloud_iam::evaluator::RequestContext {
1419 aws_principal_arn: Some(principal.arn.clone()),
1420 aws_principal_account: Some(principal.account_id.clone()),
1421 ..Default::default()
1422 };
1423 let eval_req = fakecloud_iam::evaluator::EvalRequest {
1424 principal: &principal,
1425 action: "events:PutEvents".to_string(),
1426 resource: bus_arn,
1427 context,
1428 };
1429 let decision = fakecloud_iam::evaluator::evaluate_resource_policy_only(
1430 &policy_doc,
1431 &eval_req,
1432 );
1433 if !matches!(decision, fakecloud_iam::evaluator::Decision::Allow) {
1434 failed_count += 1;
1435 result_entries.push(json!({
1436 "ErrorCode": "AccessDeniedException",
1437 "ErrorMessage": format!(
1438 "User '{}' is not authorized to put events on event bus '{}'",
1439 principal.arn, event_bus_name
1440 ),
1441 }));
1442 continue;
1443 }
1444 }
1445 }
1446
1447 let time = parse_put_events_time(&entry["Time"]);
1448 let resources: Vec<String> = entry["Resources"]
1449 .as_array()
1450 .map(|arr| {
1451 arr.iter()
1452 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1453 .collect()
1454 })
1455 .unwrap_or_default();
1456
1457 let event = PutEvent {
1458 event_id: event_id.clone(),
1459 source: source.clone(),
1460 detail_type: detail_type.clone(),
1461 detail: detail.clone(),
1462 event_bus_name: event_bus_name.clone(),
1463 time,
1464 resources: resources.clone(),
1465 };
1466
1467 archive_matching_event(
1468 state,
1469 &event,
1470 &event_bus_name,
1471 &source,
1472 &detail_type,
1473 &detail,
1474 &req.account_id,
1475 &req.region,
1476 &resources,
1477 );
1478
1479 state.events.push(event);
1480
1481 let matching_targets: Vec<(String, EventTarget)> = state
1484 .rules
1485 .values()
1486 .filter(|r| {
1487 r.event_bus_name == event_bus_name
1488 && r.state == "ENABLED"
1489 && matches_pattern(
1490 r.event_pattern.as_deref(),
1491 &source,
1492 &detail_type,
1493 &detail,
1494 &req.account_id,
1495 &req.region,
1496 &resources,
1497 &event_id,
1498 &time.to_rfc3339(),
1499 )
1500 })
1501 .flat_map(|r| r.targets.iter().map(|t| (r.arn.clone(), t.clone())))
1502 .collect();
1503
1504 if !matching_targets.is_empty() {
1505 events_to_deliver.push((
1506 event_id.clone(),
1507 source,
1508 detail_type,
1509 detail,
1510 time,
1511 resources,
1512 matching_targets,
1513 ));
1514 }
1515
1516 result_entries.push(json!({ "EventId": event_id }));
1517 }
1518
1519 drop(accounts);
1521
1522 for (event_id, source, detail_type, detail, time, resources, targets) in events_to_deliver {
1527 let detail_value: Value = serde_json::from_str(&detail).unwrap_or(json!({}));
1528 let event_json = json!({
1529 "version": "0",
1530 "id": event_id,
1531 "source": source,
1532 "account": req.account_id,
1533 "detail-type": detail_type,
1534 "detail": detail_value,
1535 "time": time.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
1536 "region": req.region,
1537 "resources": resources,
1538 });
1539
1540 let ctx = EventDispatchContext {
1541 state: &self.state,
1542 delivery: &self.delivery,
1543 lambda_state: self.lambda_state.as_ref(),
1544 logs_state: self.logs_state.as_ref(),
1545 logs_persist: self.logs_persist.as_ref(),
1546 container_runtime: &self.container_runtime,
1547 account_id: &req.account_id,
1548 region: &req.region,
1549 };
1550 for (rule_arn, target) in targets {
1551 dispatch_event_target(
1552 &ctx,
1553 &target,
1554 &event_json,
1555 &event_id,
1556 &detail_type,
1557 Some(&rule_arn),
1558 );
1559 }
1560 }
1561
1562 let resp = json!({
1563 "FailedEntryCount": failed_count,
1564 "Entries": result_entries,
1565 });
1566
1567 Ok(AwsResponse::ok_json(resp))
1568 }
1569
1570 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1573 let body = req.json_body();
1574 validate_required("ResourceARN", &body["ResourceARN"])?;
1575 let arn = body["ResourceARN"]
1576 .as_str()
1577 .ok_or_else(|| missing("ResourceARN"))?;
1578 validate_string_length("resourceARN", arn, 1, 1600)?;
1579 validate_required("Tags", &body["Tags"])?;
1580
1581 let mut accounts = self.state.write();
1582 let state = accounts.get_or_create(&req.account_id);
1583 let tag_map = find_tags_mut(state, arn)?;
1584
1585 fakecloud_core::tags::apply_tags(tag_map, &body, "Tags", "Key", "Value").map_err(|f| {
1586 AwsServiceError::aws_error(
1587 StatusCode::BAD_REQUEST,
1588 "ValidationException",
1589 format!("{f} must be a list"),
1590 )
1591 })?;
1592
1593 Ok(AwsResponse::ok_json(json!({})))
1594 }
1595
1596 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1597 let body = req.json_body();
1598 validate_required("ResourceARN", &body["ResourceARN"])?;
1599 let arn = body["ResourceARN"]
1600 .as_str()
1601 .ok_or_else(|| missing("ResourceARN"))?;
1602 validate_string_length("resourceARN", arn, 1, 1600)?;
1603 validate_required("TagKeys", &body["TagKeys"])?;
1604
1605 let mut accounts = self.state.write();
1606 let state = accounts.get_or_create(&req.account_id);
1607 let tag_map = find_tags_mut(state, arn)?;
1608
1609 fakecloud_core::tags::remove_tags(tag_map, &body, "TagKeys").map_err(|f| {
1610 AwsServiceError::aws_error(
1611 StatusCode::BAD_REQUEST,
1612 "ValidationException",
1613 format!("{f} must be a list"),
1614 )
1615 })?;
1616
1617 Ok(AwsResponse::ok_json(json!({})))
1618 }
1619
1620 fn list_tags_for_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1621 let body = req.json_body();
1622 validate_required("ResourceARN", &body["ResourceARN"])?;
1623 let arn = body["ResourceARN"]
1624 .as_str()
1625 .ok_or_else(|| missing("ResourceARN"))?;
1626 validate_string_length("resourceARN", arn, 1, 1600)?;
1627
1628 let accounts = self.state.read();
1629 let empty = EventBridgeState::new(&req.account_id, &req.region);
1630 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1631 let tag_map = find_tags(state, arn)?;
1632
1633 let tags = fakecloud_core::tags::tags_to_json(tag_map, "Key", "Value");
1634
1635 Ok(AwsResponse::ok_json(json!({ "Tags": tags })))
1636 }
1637
1638 }
1640
1641struct StartReplayInput {
1651 name: String,
1652 description: Option<String>,
1653 event_source_arn: String,
1654 destination: Value,
1655 destination_arn: String,
1656 event_start_time: DateTime<Utc>,
1657 event_end_time: DateTime<Utc>,
1658}
1659
1660impl StartReplayInput {
1661 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
1662 let name = body["ReplayName"].as_str().unwrap_or("").to_string();
1670 let description = body["Description"].as_str().map(|s| s.to_string());
1671 let event_source_arn = body["EventSourceArn"].as_str().unwrap_or("").to_string();
1672 let destination = body["Destination"].clone();
1673
1674 let event_start_time = body["EventStartTime"]
1675 .as_f64()
1676 .and_then(|f| DateTime::from_timestamp(f as i64, 0))
1677 .unwrap_or_else(Utc::now);
1678 let event_end_time = body["EventEndTime"]
1679 .as_f64()
1680 .and_then(|f| DateTime::from_timestamp(f as i64, 0))
1681 .unwrap_or_else(Utc::now);
1682
1683 let destination_arn = destination["Arn"].as_str().unwrap_or("").to_string();
1684 if !destination_arn.contains(":event-bus/") {
1685 return Err(AwsServiceError::aws_error(
1689 StatusCode::BAD_REQUEST,
1690 "ResourceNotFoundException",
1691 format!("Destination.Arn {destination_arn} does not point to an event bus."),
1692 ));
1693 }
1694
1695 Ok(Self {
1696 name,
1697 description,
1698 event_source_arn,
1699 destination,
1700 destination_arn,
1701 event_start_time,
1702 event_end_time,
1703 })
1704 }
1705}
1706
1707#[path = "service_archives_replays.rs"]
1708mod service_archives_replays;
1709#[path = "service_connections_apidests.rs"]
1710mod service_connections_apidests;
1711#[path = "service_endpoints.rs"]
1712mod service_endpoints;
1713#[path = "service_partner_sources.rs"]
1714mod service_partner_sources;
1715
1716#[path = "helpers.rs"]
1717pub(crate) mod helpers;
1718pub(crate) use helpers::*;
1719
1720#[cfg(test)]
1721#[path = "service_tests.rs"]
1722mod tests;
1723
1724#[cfg(test)]
1725mod pagination_reject_test {
1726 #[test]
1727 fn paginate_checked_rejects_invalid_token() {
1728 use fakecloud_core::pagination::paginate_checked;
1729 let items: Vec<i32> = (0..5).collect();
1730 assert!(paginate_checked(&items, Some("bad"), 3).is_err());
1731 assert!(paginate_checked(&items, Some("2"), 3).is_ok());
1732 }
1733}