Skip to main content

unifly_api/convert/
network.rs

1use std::collections::HashMap;
2use std::net::{IpAddr, Ipv4Addr};
3
4use serde_json::Value;
5
6use crate::integration_types;
7use crate::model::common::DataSource;
8use crate::model::entity_id::EntityId;
9use crate::model::network::{DhcpConfig, Ipv6Mode, Network, NetworkManagement};
10
11use super::helpers::map_origin;
12
13fn net_field<'a>(
14    extra: &'a HashMap<String, Value>,
15    metadata: &'a Value,
16    key: &str,
17) -> Option<&'a Value> {
18    extra.get(key).or_else(|| metadata.get(key))
19}
20
21#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
22fn parse_network_fields(
23    id: uuid::Uuid,
24    name: String,
25    enabled: bool,
26    management_str: &str,
27    vlan_id: i32,
28    is_default: bool,
29    metadata: &Value,
30    extra: &HashMap<String, Value>,
31) -> Network {
32    // ── Feature flags ───────────────────────────────────────────
33    let isolation_enabled = net_field(extra, metadata, "isolationEnabled")
34        .and_then(Value::as_bool)
35        .unwrap_or(false);
36    let internet_access_enabled = net_field(extra, metadata, "internetAccessEnabled")
37        .and_then(Value::as_bool)
38        .unwrap_or(true);
39    let mdns_forwarding_enabled = net_field(extra, metadata, "mdnsForwardingEnabled")
40        .and_then(Value::as_bool)
41        .unwrap_or(false);
42    let cellular_backup_enabled = net_field(extra, metadata, "cellularBackupEnabled")
43        .and_then(Value::as_bool)
44        .unwrap_or(false);
45
46    // ── Firewall zone ───────────────────────────────────────────
47    let firewall_zone_id = net_field(extra, metadata, "zoneId")
48        .and_then(Value::as_str)
49        .and_then(|s| uuid::Uuid::parse_str(s).ok())
50        .map(EntityId::Uuid);
51
52    // ── IPv4 configuration ──────────────────────────────────────
53    let ipv4 = net_field(extra, metadata, "ipv4Configuration");
54
55    let gateway_ip: Option<Ipv4Addr> = ipv4
56        .and_then(|v| v.get("hostIpAddress").or_else(|| v.get("host")))
57        .and_then(Value::as_str)
58        .and_then(|s| s.parse().ok());
59
60    let subnet = ipv4.and_then(|v| {
61        let host = v.get("hostIpAddress").or_else(|| v.get("host"))?.as_str()?;
62        let prefix = v
63            .get("prefixLength")
64            .or_else(|| v.get("prefix"))?
65            .as_u64()?;
66        Some(format!("{host}/{prefix}"))
67    });
68
69    // ── DHCP ────────────────────────────────────────────────────
70    let dhcp = ipv4.and_then(|v| {
71        if let Some(dhcp_cfg) = v.get("dhcpConfiguration") {
72            let mode = dhcp_cfg.get("mode").and_then(Value::as_str).unwrap_or("");
73            let dhcp_enabled = mode == "SERVER";
74            let range = dhcp_cfg.get("ipAddressRange");
75            let range_start = range
76                .and_then(|r| r.get("start").or_else(|| r.get("rangeStart")))
77                .and_then(Value::as_str)
78                .and_then(|s| s.parse().ok());
79            let range_stop = range
80                .and_then(|r| r.get("end").or_else(|| r.get("rangeStop")))
81                .and_then(Value::as_str)
82                .and_then(|s| s.parse().ok());
83            let lease_time_secs = dhcp_cfg.get("leaseTimeSeconds").and_then(Value::as_u64);
84            let dns_servers = dhcp_cfg
85                .get("dnsServerIpAddressesOverride")
86                .and_then(Value::as_array)
87                .map(|arr| {
88                    arr.iter()
89                        .filter_map(|v| v.as_str()?.parse::<IpAddr>().ok())
90                        .collect()
91                })
92                .unwrap_or_default();
93            return Some(DhcpConfig {
94                enabled: dhcp_enabled,
95                range_start,
96                range_stop,
97                lease_time_secs,
98                dns_servers,
99                gateway: gateway_ip,
100            });
101        }
102
103        let server = v.get("dhcp")?.get("server")?;
104        let dhcp_enabled = server
105            .get("enabled")
106            .and_then(Value::as_bool)
107            .unwrap_or(false);
108        let range_start = server
109            .get("rangeStart")
110            .and_then(Value::as_str)
111            .and_then(|s| s.parse().ok());
112        let range_stop = server
113            .get("rangeStop")
114            .and_then(Value::as_str)
115            .and_then(|s| s.parse().ok());
116        let lease_time_secs = server.get("leaseTimeSec").and_then(Value::as_u64);
117        let dns_servers = server
118            .get("dnsOverride")
119            .and_then(|d| d.get("servers"))
120            .and_then(Value::as_array)
121            .map(|arr| {
122                arr.iter()
123                    .filter_map(|v| v.as_str()?.parse::<IpAddr>().ok())
124                    .collect()
125            })
126            .unwrap_or_default();
127        let gateway = server
128            .get("gateway")
129            .and_then(Value::as_str)
130            .and_then(|s| s.parse().ok())
131            .or(gateway_ip);
132        Some(DhcpConfig {
133            enabled: dhcp_enabled,
134            range_start,
135            range_stop,
136            lease_time_secs,
137            dns_servers,
138            gateway,
139        })
140    });
141
142    // ── PXE / NTP / TFTP ────────────────────────────────────────
143    let pxe_enabled = ipv4
144        .and_then(|v| v.get("pxe"))
145        .and_then(|v| v.get("enabled"))
146        .and_then(Value::as_bool)
147        .unwrap_or(false);
148    let ntp_server = ipv4
149        .and_then(|v| v.get("ntp"))
150        .and_then(|v| v.get("server"))
151        .and_then(Value::as_str)
152        .and_then(|s| s.parse::<IpAddr>().ok());
153    let tftp_server = ipv4
154        .and_then(|v| v.get("tftp"))
155        .and_then(|v| v.get("server"))
156        .and_then(Value::as_str)
157        .map(String::from);
158
159    // ── IPv6 ────────────────────────────────────────────────────
160    let ipv6 = net_field(extra, metadata, "ipv6Configuration");
161    let ipv6_enabled = ipv6.is_some();
162    let ipv6_mode = ipv6
163        .and_then(|v| v.get("interfaceType").or_else(|| v.get("type")))
164        .and_then(Value::as_str)
165        .and_then(|s| match s {
166            "PREFIX_DELEGATION" => Some(Ipv6Mode::PrefixDelegation),
167            "STATIC" => Some(Ipv6Mode::Static),
168            _ => None,
169        });
170    let slaac_enabled = ipv6
171        .and_then(|v| {
172            v.get("clientAddressAssignment")
173                .and_then(|ca| ca.get("slaacEnabled"))
174                .and_then(Value::as_bool)
175                .or_else(|| {
176                    v.get("slaac")
177                        .and_then(|s| s.get("enabled"))
178                        .and_then(Value::as_bool)
179                })
180        })
181        .unwrap_or(false);
182    let dhcpv6_enabled = ipv6
183        .and_then(|v| {
184            v.get("clientAddressAssignment")
185                .and_then(|ca| ca.get("dhcpv6Enabled"))
186                .and_then(Value::as_bool)
187                .or_else(|| {
188                    v.get("dhcpv6")
189                        .and_then(|d| d.get("enabled"))
190                        .and_then(Value::as_bool)
191                })
192        })
193        .unwrap_or(false);
194    let ipv6_prefix = ipv6.and_then(|v| {
195        v.get("additionalHostIpSubnets")
196            .and_then(Value::as_array)
197            .and_then(|a| a.first())
198            .and_then(Value::as_str)
199            .map(String::from)
200            .or_else(|| v.get("prefix").and_then(Value::as_str).map(String::from))
201    });
202
203    // ── Management type inference ───────────────────────────────
204    let has_ipv4_config = ipv4.is_some();
205    let has_device_id = extra.contains_key("deviceId");
206    let management = if has_ipv4_config && !has_device_id {
207        Some(NetworkManagement::Gateway)
208    } else if has_device_id {
209        Some(NetworkManagement::Switch)
210    } else if has_ipv4_config {
211        Some(NetworkManagement::Gateway)
212    } else {
213        None
214    };
215
216    Network {
217        id: EntityId::Uuid(id),
218        name,
219        enabled,
220        management,
221        purpose: None,
222        is_default,
223        #[allow(
224            clippy::as_conversions,
225            clippy::cast_possible_truncation,
226            clippy::cast_sign_loss
227        )]
228        vlan_id: Some(vlan_id as u16),
229        subnet,
230        gateway_ip,
231        dhcp,
232        ipv6_enabled,
233        ipv6_mode,
234        ipv6_prefix,
235        dhcpv6_enabled,
236        slaac_enabled,
237        ntp_server,
238        pxe_enabled,
239        tftp_server,
240        firewall_zone_id,
241        isolation_enabled,
242        internet_access_enabled,
243        mdns_forwarding_enabled,
244        cellular_backup_enabled,
245        origin: map_origin(management_str),
246        source: DataSource::IntegrationApi,
247    }
248}
249
250impl From<integration_types::NetworkResponse> for Network {
251    fn from(n: integration_types::NetworkResponse) -> Self {
252        parse_network_fields(
253            n.id,
254            n.name,
255            n.enabled,
256            &n.management,
257            n.vlan_id,
258            n.default,
259            &n.metadata,
260            &n.extra,
261        )
262    }
263}
264
265impl From<integration_types::NetworkDetailsResponse> for Network {
266    fn from(n: integration_types::NetworkDetailsResponse) -> Self {
267        parse_network_fields(
268            n.id,
269            n.name,
270            n.enabled,
271            &n.management,
272            n.vlan_id,
273            n.default,
274            &n.metadata,
275            &n.extra,
276        )
277    }
278}