Skip to main content

winterbaume_networkfirewall/
state.rs

1use std::collections::HashMap;
2
3use crate::types::*;
4
5/// In-memory state for the Network Firewall service.
6#[derive(Debug, Default)]
7pub struct NetworkFirewallState {
8    /// Firewalls keyed by ARN.
9    pub firewalls: HashMap<String, Firewall>,
10    /// Logging configurations keyed by firewall ARN.
11    pub logging_configs: HashMap<String, serde_json::Value>,
12    /// Rule groups keyed by ARN.
13    pub rule_groups: HashMap<String, RuleGroup>,
14    /// Firewall policies keyed by ARN.
15    pub firewall_policies: HashMap<String, FirewallPolicy>,
16    /// Resource policies keyed by resource ARN.
17    pub resource_policies: HashMap<String, ResourcePolicy>,
18    /// TLS inspection configurations keyed by ARN.
19    pub tls_inspection_configurations: HashMap<String, TlsInspectionConfiguration>,
20    /// VPC endpoint associations keyed by ARN.
21    pub vpc_endpoint_associations: HashMap<String, VpcEndpointAssociation>,
22    /// Availability zone mappings keyed by firewall ARN.
23    pub availability_zone_mappings: HashMap<String, Vec<AvailabilityZoneMapping>>,
24    /// Transit gateway attachments keyed by attachment ID.
25    pub transit_gateway_attachments: HashMap<String, TransitGatewayAttachment>,
26    /// Proxies keyed by ARN.
27    pub proxies: HashMap<String, NfwProxy>,
28    /// Proxy configurations keyed by ARN.
29    pub proxy_configurations: HashMap<String, NfwProxyConfiguration>,
30    /// Proxy rule groups keyed by ARN.
31    pub proxy_rule_groups: HashMap<String, NfwProxyRuleGroup>,
32    /// Flow operations keyed by operation ID.
33    pub flow_operations: HashMap<String, FlowOperation>,
34    /// Analysis reports keyed by report ID.
35    pub analysis_reports: HashMap<String, AnalysisReport>,
36    /// Encryption configurations keyed by firewall ARN.
37    pub encryption_configs: HashMap<String, EncryptionConfig>,
38    /// Enabled analysis types keyed by firewall ARN.
39    pub analysis_settings: HashMap<String, Vec<String>>,
40}
41
42/// Error type for Network Firewall operations.
43#[derive(Debug, thiserror::Error)]
44pub enum NfwError {
45    #[error("A resource with the specified name already exists: {name}")]
46    InvalidRequestDuplicateName { name: String },
47    #[error("Either FirewallArn or FirewallName must be specified")]
48    InvalidRequestFirewallIdentifier,
49    #[error("Either RuleGroupArn or RuleGroupName must be specified")]
50    InvalidRequestRuleGroupIdentifier,
51    #[error("Either FirewallPolicyArn or FirewallPolicyName must be specified")]
52    InvalidRequestFirewallPolicyIdentifier,
53    #[error(
54        "Either TLSInspectionConfigurationArn or TLSInspectionConfigurationName must be specified"
55    )]
56    InvalidRequestTlsInspectionConfigIdentifier,
57    #[error("Either ProxyArn or ProxyName must be specified")]
58    InvalidRequestProxyIdentifier,
59    #[error("Either ProxyConfigurationArn or ProxyConfigurationName must be specified")]
60    InvalidRequestProxyConfigIdentifier,
61    #[error("Either ProxyRuleGroupArn or ProxyRuleGroupName must be specified")]
62    InvalidRequestProxyRuleGroupIdentifier,
63    #[error("Resource '{identifier}' not found")]
64    ResourceNotFound { identifier: String },
65}
66
67impl NetworkFirewallState {
68    pub fn create_firewall(
69        &mut self,
70        firewall_name: &str,
71        firewall_policy_arn: &str,
72        vpc_id: &str,
73        subnet_mappings: Vec<SubnetMapping>,
74        description: Option<&str>,
75        delete_protection: bool,
76        tags: Vec<(String, String)>,
77        account_id: &str,
78        region: &str,
79    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
80        if self
81            .firewalls
82            .values()
83            .any(|f| f.firewall_name == firewall_name)
84        {
85            return Err(NfwError::InvalidRequestDuplicateName {
86                name: firewall_name.to_string(),
87            });
88        }
89
90        let firewall_id = uuid::Uuid::new_v4().to_string();
91        let arn =
92            format!("arn:aws:network-firewall:{region}:{account_id}:firewall/{firewall_name}");
93
94        let firewall = Firewall {
95            firewall_name: firewall_name.to_string(),
96            firewall_arn: arn.clone(),
97            firewall_id,
98            firewall_policy_arn: firewall_policy_arn.to_string(),
99            vpc_id: vpc_id.to_string(),
100            subnet_mappings,
101            delete_protection,
102            subnet_change_protection: false,
103            firewall_policy_change_protection: false,
104            availability_zone_change_protection: false,
105            description: description.map(|s| s.to_string()),
106            tags,
107            encryption_configuration: None,
108        };
109
110        self.firewalls.insert(arn.clone(), firewall);
111        let fw = self.firewalls.get(&arn).unwrap();
112        Ok((fw, FirewallStatus::default()))
113    }
114
115    pub fn describe_firewall(
116        &self,
117        firewall_name: Option<&str>,
118        firewall_arn: Option<&str>,
119    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
120        let fw = if let Some(arn) = firewall_arn {
121            self.firewalls.get(arn)
122        } else if let Some(name) = firewall_name {
123            self.firewalls.values().find(|f| f.firewall_name == name)
124        } else {
125            return Err(NfwError::InvalidRequestFirewallIdentifier);
126        };
127
128        match fw {
129            Some(f) => Ok((f, FirewallStatus::default())),
130            None => Err(resource_not_found_error(
131                firewall_arn.or(firewall_name).unwrap_or("unknown"),
132            )),
133        }
134    }
135
136    pub fn delete_firewall(
137        &mut self,
138        firewall_name: Option<&str>,
139        firewall_arn: Option<&str>,
140    ) -> Result<(Firewall, FirewallStatus), NfwError> {
141        let arn = if let Some(arn) = firewall_arn {
142            arn.to_string()
143        } else if let Some(name) = firewall_name {
144            match self.firewalls.values().find(|f| f.firewall_name == name) {
145                Some(f) => f.firewall_arn.clone(),
146                None => return Err(resource_not_found_error(name)),
147            }
148        } else {
149            return Err(NfwError::InvalidRequestFirewallIdentifier);
150        };
151
152        match self.firewalls.remove(&arn) {
153            Some(fw) => Ok((
154                fw,
155                FirewallStatus {
156                    status: "DELETING".to_string(),
157                    configuration_sync_state_summary: "IN_SYNC".to_string(),
158                },
159            )),
160            None => Err(resource_not_found_error(&arn)),
161        }
162    }
163
164    pub fn list_firewalls(&self) -> Vec<FirewallMetadata> {
165        self.firewalls
166            .values()
167            .map(|f| FirewallMetadata {
168                firewall_name: f.firewall_name.clone(),
169                firewall_arn: f.firewall_arn.clone(),
170            })
171            .collect()
172    }
173
174    // ---------- Rule Groups ----------
175
176    #[allow(clippy::too_many_arguments)]
177    pub fn create_rule_group(
178        &mut self,
179        rule_group_name: &str,
180        rule_group_type: &str,
181        capacity: i32,
182        description: Option<&str>,
183        tags: Vec<(String, String)>,
184        rule_group_body: Option<serde_json::Value>,
185        rules: Option<&str>,
186        account_id: &str,
187        region: &str,
188    ) -> Result<&RuleGroup, NfwError> {
189        if self
190            .rule_groups
191            .values()
192            .any(|rg| rg.rule_group_name == rule_group_name)
193        {
194            return Err(NfwError::InvalidRequestDuplicateName {
195                name: rule_group_name.to_string(),
196            });
197        }
198
199        let rule_group_id = uuid::Uuid::new_v4().to_string();
200        let arn = format!(
201            "arn:aws:network-firewall:{region}:{account_id}:stateful-rulegroup/{rule_group_name}"
202        );
203
204        let rg = RuleGroup {
205            rule_group_name: rule_group_name.to_string(),
206            rule_group_arn: arn.clone(),
207            rule_group_id,
208            rule_group_type: rule_group_type.to_string(),
209            capacity,
210            description: description.map(|s| s.to_string()),
211            tags,
212            rule_group_body,
213            rules: rules.map(|s| s.to_string()),
214            encryption_configuration: None,
215        };
216
217        self.rule_groups.insert(arn.clone(), rg);
218        Ok(self.rule_groups.get(&arn).unwrap())
219    }
220
221    pub fn describe_rule_group(
222        &self,
223        rule_group_name: Option<&str>,
224        rule_group_arn: Option<&str>,
225    ) -> Result<&RuleGroup, NfwError> {
226        let rg = if let Some(arn) = rule_group_arn {
227            self.rule_groups.get(arn)
228        } else if let Some(name) = rule_group_name {
229            self.rule_groups
230                .values()
231                .find(|rg| rg.rule_group_name == name)
232        } else {
233            return Err(NfwError::InvalidRequestRuleGroupIdentifier);
234        };
235
236        match rg {
237            Some(rg) => Ok(rg),
238            None => Err(resource_not_found_error(
239                rule_group_arn.or(rule_group_name).unwrap_or("unknown"),
240            )),
241        }
242    }
243
244    pub fn delete_rule_group(
245        &mut self,
246        rule_group_name: Option<&str>,
247        rule_group_arn: Option<&str>,
248    ) -> Result<RuleGroup, NfwError> {
249        let arn = if let Some(arn) = rule_group_arn {
250            arn.to_string()
251        } else if let Some(name) = rule_group_name {
252            match self
253                .rule_groups
254                .values()
255                .find(|rg| rg.rule_group_name == name)
256            {
257                Some(rg) => rg.rule_group_arn.clone(),
258                None => return Err(resource_not_found_error(name)),
259            }
260        } else {
261            return Err(NfwError::InvalidRequestRuleGroupIdentifier);
262        };
263
264        match self.rule_groups.remove(&arn) {
265            Some(rg) => Ok(rg),
266            None => Err(resource_not_found_error(&arn)),
267        }
268    }
269
270    pub fn list_rule_groups(&self) -> Vec<RuleGroupMetadata> {
271        self.rule_groups
272            .values()
273            .map(|rg| RuleGroupMetadata {
274                name: rg.rule_group_name.clone(),
275                arn: rg.rule_group_arn.clone(),
276            })
277            .collect()
278    }
279
280    pub fn update_rule_group(
281        &mut self,
282        rule_group_name: Option<&str>,
283        rule_group_arn: Option<&str>,
284        description: Option<&str>,
285        rule_group_body: Option<serde_json::Value>,
286        rules: Option<&str>,
287    ) -> Result<&RuleGroup, NfwError> {
288        let arn = if let Some(arn) = rule_group_arn {
289            arn.to_string()
290        } else if let Some(name) = rule_group_name {
291            match self
292                .rule_groups
293                .values()
294                .find(|rg| rg.rule_group_name == name)
295            {
296                Some(rg) => rg.rule_group_arn.clone(),
297                None => return Err(resource_not_found_error(name)),
298            }
299        } else {
300            return Err(NfwError::InvalidRequestRuleGroupIdentifier);
301        };
302
303        let rg = match self.rule_groups.get_mut(&arn) {
304            Some(rg) => rg,
305            None => return Err(resource_not_found_error(&arn)),
306        };
307
308        if let Some(desc) = description {
309            rg.description = Some(desc.to_string());
310        }
311        if let Some(body) = rule_group_body {
312            rg.rule_group_body = Some(body);
313        }
314        if let Some(r) = rules {
315            rg.rules = Some(r.to_string());
316        }
317
318        Ok(self.rule_groups.get(&arn).unwrap())
319    }
320
321    // ---------- Firewall Policies ----------
322
323    pub fn create_firewall_policy(
324        &mut self,
325        firewall_policy_name: &str,
326        firewall_policy_body: serde_json::Value,
327        description: Option<&str>,
328        tags: Vec<(String, String)>,
329        account_id: &str,
330        region: &str,
331    ) -> Result<&FirewallPolicy, NfwError> {
332        if self
333            .firewall_policies
334            .values()
335            .any(|fp| fp.firewall_policy_name == firewall_policy_name)
336        {
337            return Err(NfwError::InvalidRequestDuplicateName {
338                name: firewall_policy_name.to_string(),
339            });
340        }
341
342        let firewall_policy_id = uuid::Uuid::new_v4().to_string();
343        let arn = format!(
344            "arn:aws:network-firewall:{region}:{account_id}:firewall-policy/{firewall_policy_name}"
345        );
346
347        let fp = FirewallPolicy {
348            firewall_policy_name: firewall_policy_name.to_string(),
349            firewall_policy_arn: arn.clone(),
350            firewall_policy_id,
351            description: description.map(|s| s.to_string()),
352            tags,
353            firewall_policy_body,
354            encryption_configuration: None,
355        };
356
357        self.firewall_policies.insert(arn.clone(), fp);
358        Ok(self.firewall_policies.get(&arn).unwrap())
359    }
360
361    pub fn describe_firewall_policy(
362        &self,
363        firewall_policy_name: Option<&str>,
364        firewall_policy_arn: Option<&str>,
365    ) -> Result<&FirewallPolicy, NfwError> {
366        let fp = if let Some(arn) = firewall_policy_arn {
367            self.firewall_policies.get(arn)
368        } else if let Some(name) = firewall_policy_name {
369            self.firewall_policies
370                .values()
371                .find(|fp| fp.firewall_policy_name == name)
372        } else {
373            return Err(NfwError::InvalidRequestFirewallPolicyIdentifier);
374        };
375
376        match fp {
377            Some(fp) => Ok(fp),
378            None => Err(resource_not_found_error(
379                firewall_policy_arn
380                    .or(firewall_policy_name)
381                    .unwrap_or("unknown"),
382            )),
383        }
384    }
385
386    pub fn delete_firewall_policy(
387        &mut self,
388        firewall_policy_name: Option<&str>,
389        firewall_policy_arn: Option<&str>,
390    ) -> Result<FirewallPolicy, NfwError> {
391        let arn = if let Some(arn) = firewall_policy_arn {
392            arn.to_string()
393        } else if let Some(name) = firewall_policy_name {
394            match self
395                .firewall_policies
396                .values()
397                .find(|fp| fp.firewall_policy_name == name)
398            {
399                Some(fp) => fp.firewall_policy_arn.clone(),
400                None => return Err(resource_not_found_error(name)),
401            }
402        } else {
403            return Err(NfwError::InvalidRequestFirewallPolicyIdentifier);
404        };
405
406        match self.firewall_policies.remove(&arn) {
407            Some(fp) => Ok(fp),
408            None => Err(resource_not_found_error(&arn)),
409        }
410    }
411
412    pub fn list_firewall_policies(&self) -> Vec<FirewallPolicyMetadata> {
413        self.firewall_policies
414            .values()
415            .map(|fp| FirewallPolicyMetadata {
416                name: fp.firewall_policy_name.clone(),
417                arn: fp.firewall_policy_arn.clone(),
418            })
419            .collect()
420    }
421
422    pub fn update_firewall_policy(
423        &mut self,
424        firewall_policy_name: Option<&str>,
425        firewall_policy_arn: Option<&str>,
426        firewall_policy_body: serde_json::Value,
427        description: Option<&str>,
428    ) -> Result<&FirewallPolicy, NfwError> {
429        let arn = if let Some(arn) = firewall_policy_arn {
430            arn.to_string()
431        } else if let Some(name) = firewall_policy_name {
432            match self
433                .firewall_policies
434                .values()
435                .find(|fp| fp.firewall_policy_name == name)
436            {
437                Some(fp) => fp.firewall_policy_arn.clone(),
438                None => return Err(resource_not_found_error(name)),
439            }
440        } else {
441            return Err(NfwError::InvalidRequestFirewallPolicyIdentifier);
442        };
443
444        let fp = match self.firewall_policies.get_mut(&arn) {
445            Some(fp) => fp,
446            None => return Err(resource_not_found_error(&arn)),
447        };
448
449        fp.firewall_policy_body = firewall_policy_body;
450        if let Some(desc) = description {
451            fp.description = Some(desc.to_string());
452        }
453
454        Ok(self.firewall_policies.get(&arn).unwrap())
455    }
456
457    // ---------- Tags ----------
458
459    pub fn list_tags_for_resource(
460        &self,
461        resource_arn: &str,
462    ) -> Result<Vec<(String, String)>, NfwError> {
463        if let Some(fw) = self.firewalls.get(resource_arn) {
464            return Ok(fw.tags.clone());
465        }
466        if let Some(rg) = self.rule_groups.get(resource_arn) {
467            return Ok(rg.tags.clone());
468        }
469        if let Some(fp) = self.firewall_policies.get(resource_arn) {
470            return Ok(fp.tags.clone());
471        }
472        Err(resource_not_found_error(resource_arn))
473    }
474
475    pub fn tag_resource(
476        &mut self,
477        resource_arn: &str,
478        tags: Vec<(String, String)>,
479    ) -> Result<(), NfwError> {
480        if let Some(fw) = self.firewalls.get_mut(resource_arn) {
481            for (k, v) in tags {
482                fw.tags.retain(|(ek, _)| *ek != k);
483                fw.tags.push((k, v));
484            }
485            return Ok(());
486        }
487        if let Some(rg) = self.rule_groups.get_mut(resource_arn) {
488            for (k, v) in tags {
489                rg.tags.retain(|(ek, _)| *ek != k);
490                rg.tags.push((k, v));
491            }
492            return Ok(());
493        }
494        if let Some(fp) = self.firewall_policies.get_mut(resource_arn) {
495            for (k, v) in tags {
496                fp.tags.retain(|(ek, _)| *ek != k);
497                fp.tags.push((k, v));
498            }
499            return Ok(());
500        }
501        Err(resource_not_found_error(resource_arn))
502    }
503
504    pub fn untag_resource(
505        &mut self,
506        resource_arn: &str,
507        tag_keys: Vec<&str>,
508    ) -> Result<(), NfwError> {
509        if let Some(fw) = self.firewalls.get_mut(resource_arn) {
510            fw.tags.retain(|(k, _)| !tag_keys.contains(&k.as_str()));
511            return Ok(());
512        }
513        if let Some(rg) = self.rule_groups.get_mut(resource_arn) {
514            rg.tags.retain(|(k, _)| !tag_keys.contains(&k.as_str()));
515            return Ok(());
516        }
517        if let Some(fp) = self.firewall_policies.get_mut(resource_arn) {
518            fp.tags.retain(|(k, _)| !tag_keys.contains(&k.as_str()));
519            return Ok(());
520        }
521        Err(resource_not_found_error(resource_arn))
522    }
523
524    // ---------- Resource Policies ----------
525
526    pub fn put_resource_policy(
527        &mut self,
528        resource_arn: &str,
529        policy: &str,
530    ) -> Result<(), NfwError> {
531        self.resource_policies.insert(
532            resource_arn.to_string(),
533            ResourcePolicy {
534                resource_arn: resource_arn.to_string(),
535                policy: policy.to_string(),
536            },
537        );
538        Ok(())
539    }
540
541    pub fn describe_resource_policy(&self, resource_arn: &str) -> Result<&str, NfwError> {
542        match self.resource_policies.get(resource_arn) {
543            Some(rp) => Ok(&rp.policy),
544            None => Err(resource_not_found_error(resource_arn)),
545        }
546    }
547
548    pub fn delete_resource_policy(&mut self, resource_arn: &str) -> Result<(), NfwError> {
549        match self.resource_policies.remove(resource_arn) {
550            Some(_) => Ok(()),
551            None => Err(resource_not_found_error(resource_arn)),
552        }
553    }
554
555    // ---------- Firewall update operations ----------
556
557    pub fn update_firewall_description(
558        &mut self,
559        firewall_name: Option<&str>,
560        firewall_arn: Option<&str>,
561        description: Option<&str>,
562    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
563        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
564        let fw = self.firewalls.get_mut(&arn).unwrap();
565        fw.description = description.map(|s| s.to_string());
566        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
567    }
568
569    pub fn update_firewall_delete_protection(
570        &mut self,
571        firewall_name: Option<&str>,
572        firewall_arn: Option<&str>,
573        delete_protection: bool,
574    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
575        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
576        let fw = self.firewalls.get_mut(&arn).unwrap();
577        fw.delete_protection = delete_protection;
578        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
579    }
580
581    pub fn associate_firewall_policy(
582        &mut self,
583        firewall_name: Option<&str>,
584        firewall_arn: Option<&str>,
585        firewall_policy_arn: &str,
586    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
587        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
588        let fw = self.firewalls.get_mut(&arn).unwrap();
589        fw.firewall_policy_arn = firewall_policy_arn.to_string();
590        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
591    }
592
593    pub fn associate_subnets(
594        &mut self,
595        firewall_name: Option<&str>,
596        firewall_arn: Option<&str>,
597        subnet_mappings: Vec<SubnetMapping>,
598    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
599        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
600        let fw = self.firewalls.get_mut(&arn).unwrap();
601        for sm in subnet_mappings {
602            if !fw
603                .subnet_mappings
604                .iter()
605                .any(|e| e.subnet_id == sm.subnet_id)
606            {
607                fw.subnet_mappings.push(sm);
608            }
609        }
610        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
611    }
612
613    pub fn disassociate_subnets(
614        &mut self,
615        firewall_name: Option<&str>,
616        firewall_arn: Option<&str>,
617        subnet_ids: Vec<&str>,
618    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
619        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
620        let fw = self.firewalls.get_mut(&arn).unwrap();
621        fw.subnet_mappings
622            .retain(|sm| !subnet_ids.contains(&sm.subnet_id.as_str()));
623        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
624    }
625
626    pub fn update_subnet_change_protection(
627        &mut self,
628        firewall_name: Option<&str>,
629        firewall_arn: Option<&str>,
630        subnet_change_protection: bool,
631    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
632        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
633        let fw = self.firewalls.get_mut(&arn).unwrap();
634        fw.subnet_change_protection = subnet_change_protection;
635        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
636    }
637
638    pub fn update_firewall_policy_change_protection(
639        &mut self,
640        firewall_name: Option<&str>,
641        firewall_arn: Option<&str>,
642        firewall_policy_change_protection: bool,
643    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
644        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
645        let fw = self.firewalls.get_mut(&arn).unwrap();
646        fw.firewall_policy_change_protection = firewall_policy_change_protection;
647        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
648    }
649
650    pub fn update_availability_zone_change_protection(
651        &mut self,
652        firewall_name: Option<&str>,
653        firewall_arn: Option<&str>,
654        availability_zone_change_protection: bool,
655    ) -> Result<(&Firewall, FirewallStatus), NfwError> {
656        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
657        let fw = self.firewalls.get_mut(&arn).unwrap();
658        fw.availability_zone_change_protection = availability_zone_change_protection;
659        Ok((self.firewalls.get(&arn).unwrap(), FirewallStatus::default()))
660    }
661
662    // ---------- TLS Inspection Configurations ----------
663
664    pub fn create_tls_inspection_configuration(
665        &mut self,
666        name: &str,
667        body: serde_json::Value,
668        description: Option<&str>,
669        tags: Vec<(String, String)>,
670        account_id: &str,
671        region: &str,
672    ) -> Result<&TlsInspectionConfiguration, NfwError> {
673        if self
674            .tls_inspection_configurations
675            .values()
676            .any(|t| t.name == name)
677        {
678            return Err(NfwError::InvalidRequestDuplicateName {
679                name: name.to_string(),
680            });
681        }
682
683        let id = uuid::Uuid::new_v4().to_string();
684        let arn =
685            format!("arn:aws:network-firewall:{region}:{account_id}:tls-configuration/{name}");
686
687        let tls = TlsInspectionConfiguration {
688            name: name.to_string(),
689            arn: arn.clone(),
690            id,
691            description: description.map(|s| s.to_string()),
692            tags,
693            body,
694        };
695
696        self.tls_inspection_configurations.insert(arn.clone(), tls);
697        Ok(self.tls_inspection_configurations.get(&arn).unwrap())
698    }
699
700    pub fn describe_tls_inspection_configuration(
701        &self,
702        name: Option<&str>,
703        arn: Option<&str>,
704    ) -> Result<&TlsInspectionConfiguration, NfwError> {
705        let tls = if let Some(a) = arn {
706            self.tls_inspection_configurations.get(a)
707        } else if let Some(n) = name {
708            self.tls_inspection_configurations
709                .values()
710                .find(|t| t.name == n)
711        } else {
712            return Err(NfwError::InvalidRequestTlsInspectionConfigIdentifier);
713        };
714
715        match tls {
716            Some(t) => Ok(t),
717            None => Err(resource_not_found_error(arn.or(name).unwrap_or("unknown"))),
718        }
719    }
720
721    pub fn delete_tls_inspection_configuration(
722        &mut self,
723        name: Option<&str>,
724        arn: Option<&str>,
725    ) -> Result<TlsInspectionConfiguration, NfwError> {
726        let resolved_arn = if let Some(a) = arn {
727            a.to_string()
728        } else if let Some(n) = name {
729            match self
730                .tls_inspection_configurations
731                .values()
732                .find(|t| t.name == n)
733            {
734                Some(t) => t.arn.clone(),
735                None => return Err(resource_not_found_error(n)),
736            }
737        } else {
738            return Err(NfwError::InvalidRequestTlsInspectionConfigIdentifier);
739        };
740
741        match self.tls_inspection_configurations.remove(&resolved_arn) {
742            Some(t) => Ok(t),
743            None => Err(resource_not_found_error(&resolved_arn)),
744        }
745    }
746
747    pub fn update_tls_inspection_configuration(
748        &mut self,
749        name: Option<&str>,
750        arn: Option<&str>,
751        body: serde_json::Value,
752        description: Option<&str>,
753    ) -> Result<&TlsInspectionConfiguration, NfwError> {
754        let resolved_arn = if let Some(a) = arn {
755            a.to_string()
756        } else if let Some(n) = name {
757            match self
758                .tls_inspection_configurations
759                .values()
760                .find(|t| t.name == n)
761            {
762                Some(t) => t.arn.clone(),
763                None => return Err(resource_not_found_error(n)),
764            }
765        } else {
766            return Err(NfwError::InvalidRequestTlsInspectionConfigIdentifier);
767        };
768
769        let tls = match self.tls_inspection_configurations.get_mut(&resolved_arn) {
770            Some(t) => t,
771            None => return Err(resource_not_found_error(&resolved_arn)),
772        };
773
774        tls.body = body;
775        if let Some(desc) = description {
776            tls.description = Some(desc.to_string());
777        }
778
779        Ok(self
780            .tls_inspection_configurations
781            .get(&resolved_arn)
782            .unwrap())
783    }
784
785    pub fn list_tls_inspection_configurations(&self) -> Vec<TlsInspectionConfigurationMetadata> {
786        self.tls_inspection_configurations
787            .values()
788            .map(|t| TlsInspectionConfigurationMetadata {
789                name: t.name.clone(),
790                arn: t.arn.clone(),
791            })
792            .collect()
793    }
794
795    // ---------- VPC Endpoint Associations ----------
796
797    pub fn create_vpc_endpoint_association(
798        &mut self,
799        firewall_arn: &str,
800        vpc_id: &str,
801        subnet_id: &str,
802        description: Option<&str>,
803        tags: Vec<(String, String)>,
804        account_id: &str,
805        region: &str,
806    ) -> Result<&VpcEndpointAssociation, NfwError> {
807        let id = uuid::Uuid::new_v4().to_string();
808        let arn =
809            format!("arn:aws:network-firewall:{region}:{account_id}:vpc-endpoint-association/{id}");
810
811        let assoc = VpcEndpointAssociation {
812            vpc_endpoint_association_arn: arn.clone(),
813            vpc_endpoint_association_id: id,
814            firewall_arn: firewall_arn.to_string(),
815            vpc_id: vpc_id.to_string(),
816            subnet_id: subnet_id.to_string(),
817            description: description.map(|s| s.to_string()),
818            tags,
819        };
820
821        self.vpc_endpoint_associations.insert(arn.clone(), assoc);
822        Ok(self.vpc_endpoint_associations.get(&arn).unwrap())
823    }
824
825    pub fn describe_vpc_endpoint_association(
826        &self,
827        arn: &str,
828    ) -> Result<&VpcEndpointAssociation, NfwError> {
829        match self.vpc_endpoint_associations.get(arn) {
830            Some(a) => Ok(a),
831            None => Err(resource_not_found_error(arn)),
832        }
833    }
834
835    pub fn delete_vpc_endpoint_association(
836        &mut self,
837        arn: &str,
838    ) -> Result<VpcEndpointAssociation, NfwError> {
839        match self.vpc_endpoint_associations.remove(arn) {
840            Some(a) => Ok(a),
841            None => Err(resource_not_found_error(arn)),
842        }
843    }
844
845    pub fn list_vpc_endpoint_associations(
846        &self,
847        firewall_arn: Option<&str>,
848    ) -> Vec<&VpcEndpointAssociation> {
849        self.vpc_endpoint_associations
850            .values()
851            .filter(|a| {
852                if let Some(fw_arn) = firewall_arn {
853                    a.firewall_arn == fw_arn
854                } else {
855                    true
856                }
857            })
858            .collect()
859    }
860
861    // ---------- Availability Zone Mappings ----------
862
863    pub fn associate_availability_zones(
864        &mut self,
865        firewall_name: Option<&str>,
866        firewall_arn: Option<&str>,
867        zones: Vec<AvailabilityZoneMapping>,
868    ) -> Result<(&Firewall, Vec<AvailabilityZoneMapping>), NfwError> {
869        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
870        let mappings = self
871            .availability_zone_mappings
872            .entry(arn.clone())
873            .or_default();
874        for zone in zones {
875            if !mappings
876                .iter()
877                .any(|m| m.availability_zone == zone.availability_zone)
878            {
879                mappings.push(zone);
880            }
881        }
882        let result_mappings = self.availability_zone_mappings.get(&arn).unwrap().clone();
883        Ok((self.firewalls.get(&arn).unwrap(), result_mappings))
884    }
885
886    pub fn disassociate_availability_zones(
887        &mut self,
888        firewall_name: Option<&str>,
889        firewall_arn: Option<&str>,
890        zones: Vec<&str>,
891    ) -> Result<(&Firewall, Vec<AvailabilityZoneMapping>), NfwError> {
892        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
893        let mappings = self
894            .availability_zone_mappings
895            .entry(arn.clone())
896            .or_default();
897        mappings.retain(|m| !zones.contains(&m.availability_zone.as_str()));
898        let result_mappings = self.availability_zone_mappings.get(&arn).unwrap().clone();
899        Ok((self.firewalls.get(&arn).unwrap(), result_mappings))
900    }
901
902    // ---------- Transit Gateway Attachments ----------
903
904    pub fn accept_transit_gateway_attachment(
905        &mut self,
906        transit_gateway_attachment_id: &str,
907    ) -> Result<&TransitGatewayAttachment, NfwError> {
908        let attachment = self
909            .transit_gateway_attachments
910            .get_mut(transit_gateway_attachment_id);
911        match attachment {
912            Some(a) => {
913                a.status = "ACCEPTED".to_string();
914                Ok(self
915                    .transit_gateway_attachments
916                    .get(transit_gateway_attachment_id)
917                    .unwrap())
918            }
919            None => {
920                // Auto-create it in accepted state (mock behaviour)
921                let att = TransitGatewayAttachment {
922                    transit_gateway_attachment_id: transit_gateway_attachment_id.to_string(),
923                    status: "ACCEPTED".to_string(),
924                };
925                self.transit_gateway_attachments
926                    .insert(transit_gateway_attachment_id.to_string(), att);
927                Ok(self
928                    .transit_gateway_attachments
929                    .get(transit_gateway_attachment_id)
930                    .unwrap())
931            }
932        }
933    }
934
935    pub fn reject_transit_gateway_attachment(
936        &mut self,
937        transit_gateway_attachment_id: &str,
938    ) -> Result<&TransitGatewayAttachment, NfwError> {
939        let attachment = self
940            .transit_gateway_attachments
941            .get_mut(transit_gateway_attachment_id);
942        match attachment {
943            Some(a) => {
944                a.status = "REJECTED".to_string();
945                Ok(self
946                    .transit_gateway_attachments
947                    .get(transit_gateway_attachment_id)
948                    .unwrap())
949            }
950            None => {
951                let att = TransitGatewayAttachment {
952                    transit_gateway_attachment_id: transit_gateway_attachment_id.to_string(),
953                    status: "REJECTED".to_string(),
954                };
955                self.transit_gateway_attachments
956                    .insert(transit_gateway_attachment_id.to_string(), att);
957                Ok(self
958                    .transit_gateway_attachments
959                    .get(transit_gateway_attachment_id)
960                    .unwrap())
961            }
962        }
963    }
964
965    pub fn delete_transit_gateway_attachment(
966        &mut self,
967        transit_gateway_attachment_id: &str,
968    ) -> Result<TransitGatewayAttachment, NfwError> {
969        match self
970            .transit_gateway_attachments
971            .remove(transit_gateway_attachment_id)
972        {
973            Some(mut a) => {
974                a.status = "DELETING".to_string();
975                Ok(a)
976            }
977            None => Err(resource_not_found_error(transit_gateway_attachment_id)),
978        }
979    }
980
981    // ---------- Proxies ----------
982
983    pub fn create_proxy(
984        &mut self,
985        proxy_name: &str,
986        nat_gateway_id: &str,
987        proxy_configuration_arn: Option<&str>,
988        proxy_configuration_name: Option<&str>,
989        tags: Vec<(String, String)>,
990        body: serde_json::Value,
991        account_id: &str,
992        region: &str,
993    ) -> Result<&NfwProxy, NfwError> {
994        if self.proxies.values().any(|p| p.proxy_name == proxy_name) {
995            return Err(NfwError::InvalidRequestDuplicateName {
996                name: proxy_name.to_string(),
997            });
998        }
999        let arn = format!("arn:aws:network-firewall:{region}:{account_id}:proxy/{proxy_name}");
1000        let proxy = NfwProxy {
1001            proxy_name: proxy_name.to_string(),
1002            proxy_arn: arn.clone(),
1003            nat_gateway_id: nat_gateway_id.to_string(),
1004            proxy_configuration_arn: proxy_configuration_arn.map(|s| s.to_string()),
1005            proxy_configuration_name: proxy_configuration_name.map(|s| s.to_string()),
1006            proxy_state: "READY".to_string(),
1007            tags,
1008            body,
1009        };
1010        self.proxies.insert(arn.clone(), proxy);
1011        Ok(self.proxies.get(&arn).unwrap())
1012    }
1013
1014    pub fn describe_proxy(
1015        &self,
1016        proxy_name: Option<&str>,
1017        proxy_arn: Option<&str>,
1018    ) -> Result<&NfwProxy, NfwError> {
1019        let proxy = if let Some(arn) = proxy_arn {
1020            self.proxies.get(arn)
1021        } else if let Some(name) = proxy_name {
1022            self.proxies.values().find(|p| p.proxy_name == name)
1023        } else {
1024            return Err(NfwError::InvalidRequestProxyIdentifier);
1025        };
1026        match proxy {
1027            Some(p) => Ok(p),
1028            None => Err(resource_not_found_error(
1029                proxy_arn.or(proxy_name).unwrap_or("unknown"),
1030            )),
1031        }
1032    }
1033
1034    pub fn delete_proxy(
1035        &mut self,
1036        proxy_name: Option<&str>,
1037        proxy_arn: Option<&str>,
1038    ) -> Result<NfwProxy, NfwError> {
1039        let arn = if let Some(arn) = proxy_arn {
1040            arn.to_string()
1041        } else if let Some(name) = proxy_name {
1042            match self.proxies.values().find(|p| p.proxy_name == name) {
1043                Some(p) => p.proxy_arn.clone(),
1044                None => return Err(resource_not_found_error(name)),
1045            }
1046        } else {
1047            return Err(NfwError::InvalidRequestProxyIdentifier);
1048        };
1049        match self.proxies.remove(&arn) {
1050            Some(p) => Ok(p),
1051            None => Err(resource_not_found_error(&arn)),
1052        }
1053    }
1054
1055    pub fn list_proxies(&self) -> Vec<&NfwProxy> {
1056        self.proxies.values().collect()
1057    }
1058
1059    pub fn update_proxy(
1060        &mut self,
1061        proxy_name: Option<&str>,
1062        proxy_arn: Option<&str>,
1063        body: serde_json::Value,
1064    ) -> Result<&NfwProxy, NfwError> {
1065        let arn = if let Some(arn) = proxy_arn {
1066            arn.to_string()
1067        } else if let Some(name) = proxy_name {
1068            match self.proxies.values().find(|p| p.proxy_name == name) {
1069                Some(p) => p.proxy_arn.clone(),
1070                None => return Err(resource_not_found_error(name)),
1071            }
1072        } else {
1073            return Err(NfwError::InvalidRequestProxyIdentifier);
1074        };
1075        let proxy = match self.proxies.get_mut(&arn) {
1076            Some(p) => p,
1077            None => return Err(resource_not_found_error(&arn)),
1078        };
1079        proxy.body = body;
1080        Ok(self.proxies.get(&arn).unwrap())
1081    }
1082
1083    // ---------- Proxy Configurations ----------
1084
1085    pub fn create_proxy_configuration(
1086        &mut self,
1087        name: &str,
1088        description: Option<&str>,
1089        tags: Vec<(String, String)>,
1090        body: serde_json::Value,
1091        account_id: &str,
1092        region: &str,
1093    ) -> Result<&NfwProxyConfiguration, NfwError> {
1094        if self
1095            .proxy_configurations
1096            .values()
1097            .any(|c| c.proxy_configuration_name == name)
1098        {
1099            return Err(NfwError::InvalidRequestDuplicateName {
1100                name: name.to_string(),
1101            });
1102        }
1103        let arn =
1104            format!("arn:aws:network-firewall:{region}:{account_id}:proxy-configuration/{name}");
1105        let config = NfwProxyConfiguration {
1106            proxy_configuration_name: name.to_string(),
1107            proxy_configuration_arn: arn.clone(),
1108            description: description.map(|s| s.to_string()),
1109            tags,
1110            body,
1111        };
1112        self.proxy_configurations.insert(arn.clone(), config);
1113        Ok(self.proxy_configurations.get(&arn).unwrap())
1114    }
1115
1116    pub fn describe_proxy_configuration(
1117        &self,
1118        name: Option<&str>,
1119        arn: Option<&str>,
1120    ) -> Result<&NfwProxyConfiguration, NfwError> {
1121        let config = if let Some(a) = arn {
1122            self.proxy_configurations.get(a)
1123        } else if let Some(n) = name {
1124            self.proxy_configurations
1125                .values()
1126                .find(|c| c.proxy_configuration_name == n)
1127        } else {
1128            return Err(NfwError::InvalidRequestProxyConfigIdentifier);
1129        };
1130        match config {
1131            Some(c) => Ok(c),
1132            None => Err(resource_not_found_error(arn.or(name).unwrap_or("unknown"))),
1133        }
1134    }
1135
1136    pub fn delete_proxy_configuration(
1137        &mut self,
1138        name: Option<&str>,
1139        arn: Option<&str>,
1140    ) -> Result<NfwProxyConfiguration, NfwError> {
1141        let resolved_arn = if let Some(a) = arn {
1142            a.to_string()
1143        } else if let Some(n) = name {
1144            match self
1145                .proxy_configurations
1146                .values()
1147                .find(|c| c.proxy_configuration_name == n)
1148            {
1149                Some(c) => c.proxy_configuration_arn.clone(),
1150                None => return Err(resource_not_found_error(n)),
1151            }
1152        } else {
1153            return Err(NfwError::InvalidRequestProxyConfigIdentifier);
1154        };
1155        match self.proxy_configurations.remove(&resolved_arn) {
1156            Some(c) => Ok(c),
1157            None => Err(resource_not_found_error(&resolved_arn)),
1158        }
1159    }
1160
1161    pub fn list_proxy_configurations(&self) -> Vec<&NfwProxyConfiguration> {
1162        self.proxy_configurations.values().collect()
1163    }
1164
1165    pub fn update_proxy_configuration(
1166        &mut self,
1167        name: Option<&str>,
1168        arn: Option<&str>,
1169        body: serde_json::Value,
1170    ) -> Result<&NfwProxyConfiguration, NfwError> {
1171        let resolved_arn = if let Some(a) = arn {
1172            a.to_string()
1173        } else if let Some(n) = name {
1174            match self
1175                .proxy_configurations
1176                .values()
1177                .find(|c| c.proxy_configuration_name == n)
1178            {
1179                Some(c) => c.proxy_configuration_arn.clone(),
1180                None => return Err(resource_not_found_error(n)),
1181            }
1182        } else {
1183            return Err(NfwError::InvalidRequestProxyConfigIdentifier);
1184        };
1185        let config = match self.proxy_configurations.get_mut(&resolved_arn) {
1186            Some(c) => c,
1187            None => return Err(resource_not_found_error(&resolved_arn)),
1188        };
1189        config.body = body;
1190        Ok(self.proxy_configurations.get(&resolved_arn).unwrap())
1191    }
1192
1193    // ---------- Proxy Rule Groups ----------
1194
1195    pub fn create_proxy_rule_group(
1196        &mut self,
1197        name: &str,
1198        description: Option<&str>,
1199        tags: Vec<(String, String)>,
1200        body: serde_json::Value,
1201        account_id: &str,
1202        region: &str,
1203    ) -> Result<&NfwProxyRuleGroup, NfwError> {
1204        if self
1205            .proxy_rule_groups
1206            .values()
1207            .any(|g| g.proxy_rule_group_name == name)
1208        {
1209            return Err(NfwError::InvalidRequestDuplicateName {
1210                name: name.to_string(),
1211            });
1212        }
1213        let arn = format!("arn:aws:network-firewall:{region}:{account_id}:proxy-rule-group/{name}");
1214        let group = NfwProxyRuleGroup {
1215            proxy_rule_group_name: name.to_string(),
1216            proxy_rule_group_arn: arn.clone(),
1217            description: description.map(|s| s.to_string()),
1218            tags,
1219            body,
1220        };
1221        self.proxy_rule_groups.insert(arn.clone(), group);
1222        Ok(self.proxy_rule_groups.get(&arn).unwrap())
1223    }
1224
1225    pub fn describe_proxy_rule_group(
1226        &self,
1227        name: Option<&str>,
1228        arn: Option<&str>,
1229    ) -> Result<&NfwProxyRuleGroup, NfwError> {
1230        let group = if let Some(a) = arn {
1231            self.proxy_rule_groups.get(a)
1232        } else if let Some(n) = name {
1233            self.proxy_rule_groups
1234                .values()
1235                .find(|g| g.proxy_rule_group_name == n)
1236        } else {
1237            return Err(NfwError::InvalidRequestProxyRuleGroupIdentifier);
1238        };
1239        match group {
1240            Some(g) => Ok(g),
1241            None => Err(resource_not_found_error(arn.or(name).unwrap_or("unknown"))),
1242        }
1243    }
1244
1245    pub fn delete_proxy_rule_group(
1246        &mut self,
1247        name: Option<&str>,
1248        arn: Option<&str>,
1249    ) -> Result<NfwProxyRuleGroup, NfwError> {
1250        let resolved_arn = if let Some(a) = arn {
1251            a.to_string()
1252        } else if let Some(n) = name {
1253            match self
1254                .proxy_rule_groups
1255                .values()
1256                .find(|g| g.proxy_rule_group_name == n)
1257            {
1258                Some(g) => g.proxy_rule_group_arn.clone(),
1259                None => return Err(resource_not_found_error(n)),
1260            }
1261        } else {
1262            return Err(NfwError::InvalidRequestProxyRuleGroupIdentifier);
1263        };
1264        match self.proxy_rule_groups.remove(&resolved_arn) {
1265            Some(g) => Ok(g),
1266            None => Err(resource_not_found_error(&resolved_arn)),
1267        }
1268    }
1269
1270    pub fn list_proxy_rule_groups(&self) -> Vec<&NfwProxyRuleGroup> {
1271        self.proxy_rule_groups.values().collect()
1272    }
1273
1274    // ---------- Flow Operations ----------
1275
1276    pub fn create_flow_operation(
1277        &mut self,
1278        firewall_arn: &str,
1279        flow_operation_type: &str,
1280        body: serde_json::Value,
1281    ) -> &FlowOperation {
1282        let id = uuid::Uuid::new_v4().to_string();
1283        let op = FlowOperation {
1284            flow_operation_id: id.clone(),
1285            firewall_arn: firewall_arn.to_string(),
1286            flow_operation_type: flow_operation_type.to_string(),
1287            flow_operation_status: "COMPLETED".to_string(),
1288            body,
1289        };
1290        self.flow_operations.insert(id.clone(), op);
1291        self.flow_operations.get(&id).unwrap()
1292    }
1293
1294    pub fn describe_flow_operation(
1295        &self,
1296        flow_operation_id: &str,
1297    ) -> Result<&FlowOperation, NfwError> {
1298        match self.flow_operations.get(flow_operation_id) {
1299            Some(op) => Ok(op),
1300            None => Err(resource_not_found_error(flow_operation_id)),
1301        }
1302    }
1303
1304    pub fn list_flow_operations(&self, firewall_arn: &str) -> Vec<&FlowOperation> {
1305        self.flow_operations
1306            .values()
1307            .filter(|op| op.firewall_arn == firewall_arn)
1308            .collect()
1309    }
1310
1311    // ---------- Analysis Reports ----------
1312
1313    pub fn start_analysis_report(
1314        &mut self,
1315        firewall_arn: &str,
1316        analysis_type: &str,
1317    ) -> &AnalysisReport {
1318        let id = uuid::Uuid::new_v4().to_string();
1319        let report = AnalysisReport {
1320            analysis_report_id: id.clone(),
1321            firewall_arn: firewall_arn.to_string(),
1322            analysis_type: analysis_type.to_string(),
1323            status: "COMPLETED".to_string(),
1324        };
1325        self.analysis_reports.insert(id.clone(), report);
1326        self.analysis_reports.get(&id).unwrap()
1327    }
1328
1329    pub fn list_analysis_reports(&self, firewall_arn: &str) -> Vec<&AnalysisReport> {
1330        self.analysis_reports
1331            .values()
1332            .filter(|r| r.firewall_arn == firewall_arn)
1333            .collect()
1334    }
1335
1336    pub fn get_analysis_report(
1337        &self,
1338        analysis_report_id: &str,
1339    ) -> Result<&AnalysisReport, NfwError> {
1340        match self.analysis_reports.get(analysis_report_id) {
1341            Some(r) => Ok(r),
1342            None => Err(resource_not_found_error(analysis_report_id)),
1343        }
1344    }
1345
1346    // ---------- Firewall Analysis Settings ----------
1347
1348    pub fn update_analysis_settings(
1349        &mut self,
1350        firewall_name: Option<&str>,
1351        firewall_arn: Option<&str>,
1352        enabled_analysis_types: Vec<String>,
1353    ) -> Result<(&Firewall, Vec<String>), NfwError> {
1354        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
1355        self.analysis_settings
1356            .insert(arn.clone(), enabled_analysis_types);
1357        let types = self.analysis_settings.get(&arn).unwrap().clone();
1358        Ok((self.firewalls.get(&arn).unwrap(), types))
1359    }
1360
1361    // ---------- Encryption Configuration ----------
1362
1363    pub fn update_encryption_configuration(
1364        &mut self,
1365        firewall_name: Option<&str>,
1366        firewall_arn: Option<&str>,
1367        config: EncryptionConfig,
1368    ) -> Result<(&Firewall, EncryptionConfig), NfwError> {
1369        let arn = self.resolve_firewall_arn(firewall_name, firewall_arn)?;
1370        self.encryption_configs.insert(arn.clone(), config);
1371        let enc = self.encryption_configs.get(&arn).unwrap().clone();
1372        Ok((self.firewalls.get(&arn).unwrap(), enc))
1373    }
1374
1375    fn resolve_firewall_arn(
1376        &self,
1377        firewall_name: Option<&str>,
1378        firewall_arn: Option<&str>,
1379    ) -> Result<String, NfwError> {
1380        if let Some(arn) = firewall_arn {
1381            if self.firewalls.contains_key(arn) {
1382                return Ok(arn.to_string());
1383            }
1384            return Err(resource_not_found_error(arn));
1385        }
1386        if let Some(name) = firewall_name {
1387            return match self.firewalls.values().find(|f| f.firewall_name == name) {
1388                Some(f) => Ok(f.firewall_arn.clone()),
1389                None => Err(resource_not_found_error(name)),
1390            };
1391        }
1392        Err(NfwError::InvalidRequestFirewallIdentifier)
1393    }
1394}
1395
1396fn resource_not_found_error(identifier: &str) -> NfwError {
1397    NfwError::ResourceNotFound {
1398        identifier: identifier.to_string(),
1399    }
1400}