1use serde::{Deserialize, Serialize};
2
3use crate::ir::NetworkConfig;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum DeviceProfile {
17 L2Switch,
19 L3Router,
21}
22
23impl DeviceProfile {
24 pub fn label(&self) -> &'static str {
25 match self {
26 DeviceProfile::L2Switch => "L2 (switching)",
27 DeviceProfile::L3Router => "L3 (routing)",
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct DomainMismatch {
44 pub domain: String,
46 pub detail: String,
47}
48
49pub fn detect_domain_mismatches(
52 config: &NetworkConfig,
53 profile: DeviceProfile,
54) -> Vec<DomainMismatch> {
55 let mut out = Vec::new();
56
57 match profile {
58 DeviceProfile::L2Switch => {
59 if !config.routing.static_routes.is_empty() {
60 out.push(DomainMismatch {
61 domain: "L3/static".to_string(),
62 detail: format!(
63 "{} статических маршрутов — не относится к L2-профилю",
64 config.routing.static_routes.len()
65 ),
66 });
67 }
68 if !config.routing.ospf.is_empty() {
69 out.push(DomainMismatch {
70 domain: "L3/ospf".to_string(),
71 detail: format!("{} процессов OSPF", config.routing.ospf.len()),
72 });
73 }
74 if config.routing.bgp.is_some() {
75 out.push(DomainMismatch {
76 domain: "L3/bgp".to_string(),
77 detail: "конфигурация BGP".to_string(),
78 });
79 }
80 if !config.routing.eigrp.is_empty() {
81 out.push(DomainMismatch {
82 domain: "L3/eigrp".to_string(),
83 detail: format!("{} процессов EIGRP", config.routing.eigrp.len()),
84 });
85 }
86 if !config.acls.is_empty() {
87 out.push(DomainMismatch {
88 domain: "L3/acl".to_string(),
89 detail: format!("{} ACL", config.acls.len()),
90 });
91 }
92 if !config.nat.is_empty() {
93 out.push(DomainMismatch {
94 domain: "L3/nat".to_string(),
95 detail: format!("{} правил NAT", config.nat.len()),
96 });
97 }
98 let hsrp_count: usize = config.interfaces.iter().map(|i| i.hsrp.len()).sum();
99 if hsrp_count > 0 {
100 out.push(DomainMismatch {
101 domain: "L3/hsrp".to_string(),
102 detail: format!("{} групп HSRP", hsrp_count),
103 });
104 }
105 }
106 DeviceProfile::L3Router => {
107 if !config.vlans.is_empty() {
108 out.push(DomainMismatch {
109 domain: "L2/vlan".to_string(),
110 detail: format!("{} VLAN в базе", config.vlans.len()),
111 });
112 }
113 if config.stp.is_some() {
114 out.push(DomainMismatch {
115 domain: "L2/stp".to_string(),
116 detail: "глобальная конфигурация STP".to_string(),
117 });
118 }
119 let l2_iface_count = config.interfaces.iter().filter(|i| i.l2.is_some()).count();
120 if l2_iface_count > 0 {
121 out.push(DomainMismatch {
122 domain: "L2/switchport".to_string(),
123 detail: format!(
124 "{} интерфейсов в режиме switchport (access/trunk)",
125 l2_iface_count
126 ),
127 });
128 }
129 let voice_vlan_count = config
130 .interfaces
131 .iter()
132 .filter(|i| i.voice_vlan.is_some())
133 .count();
134 if voice_vlan_count > 0 {
135 out.push(DomainMismatch {
136 domain: "L2/voice-vlan".to_string(),
137 detail: format!("{} интерфейсов с voice VLAN", voice_vlan_count),
138 });
139 }
140 }
141 }
142
143 out
144}