Skip to main content

ignition_core/client/
gan.rs

1//! Gateway Area Network overview model (09-04, EXT-02) — `GET
2//! /data/api/v1/overview/gan`, the ZERO-CONNECTION body live-captured
3//! identically on both rigs (09-LIVE-CAPTURES §4: 8.3.6 rig A + 8.3.3
4//! rig B). A fresh non-GAN gateway's zero-connection shape IS the
5//! canonical capture — 5 flat scalars, no arrays, no nesting.
6//!
7//! **Number shapes (capture-locked):** the connection counts and
8//! `remoteGateways` serialize as JSON **ints** (`0`); the byte rates
9//! serialize as JSON **floats** (`0.0`) — so counts are integer-typed
10//! and rates are `f64` (which parses BOTH forms; a point release that
11//! switches a rate to whole-number JSON can never fail the parse).
12//!
13//! Decision 4 of the phase: `overview/gan` ONLY — no
14//! `/gateway-network/gateways` companion this phase; `ign api call`
15//! covers that list.
16
17use std::collections::BTreeMap;
18
19use serde::{Deserialize, Serialize};
20
21/// GET path of the GAN overview capability (83-api collection + live
22/// capture).
23pub const GAN_OVERVIEW_PATH: &str = "/data/api/v1/overview/gan";
24
25/// GET `/data/api/v1/overview/gan` — the 5-field GAN summary.
26/// Live-captured 200 on BOTH rigs; zero drift.
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct GanStatusWire {
29    /// Total GAN connections — `0` on both captures (JSON int); a
30    /// non-GAN gateway's steady state IS zero (the capture IS the
31    /// meaning: zeros are healthy data, not absence of feature).
32    #[serde(rename = "totalConnections", alias = "total_connections", default)]
33    pub total_connections: i64,
34    /// Currently-running connections — `0` on both captures (JSON int).
35    #[serde(rename = "runningConnections", alias = "running_connections", default)]
36    pub running_connections: i64,
37    /// Outbound byte rate — `0.0` on both captures (JSON FLOAT; f64
38    /// parses both the float and a hypothetical whole-number form).
39    #[serde(rename = "outgoingByteRate", alias = "outgoing_byte_rate", default)]
40    pub outgoing_byte_rate: f64,
41    /// Inbound byte rate — `0.0` on both captures (JSON float).
42    #[serde(rename = "incomingByteRate", alias = "incoming_byte_rate", default)]
43    pub incoming_byte_rate: f64,
44    /// Remote gateways known to this one — `0` on both captures (JSON
45    /// int).
46    #[serde(rename = "remoteGateways", alias = "remote_gateways", default)]
47    pub remote_gateways: i64,
48    /// Unknown keys round-trip (version tolerance — the passthrough
49    /// proof fixture appends a fake key and rides it here).
50    #[serde(flatten)]
51    pub extra: BTreeMap<String, serde_json::Value>,
52}
53
54#[cfg(test)]
55mod tests {
56    use super::GanStatusWire;
57
58    /// THE live capture (IDENTICAL on both rigs, 09-LIVE-CAPTURES §4)
59    /// — the zero-connection body, floats spelled `0.0`.
60    #[test]
61    fn gan_parses_the_live_zero_connection_capture() {
62        let wire: GanStatusWire = serde_json::from_value(serde_json::json!({
63            "totalConnections": 0,
64            "runningConnections": 0,
65            "outgoingByteRate": 0.0,
66            "incomingByteRate": 0.0,
67            "remoteGateways": 0
68        }))
69        .expect("the live zero-connection shape must parse");
70        assert_eq!(wire.total_connections, 0);
71        assert_eq!(wire.running_connections, 0);
72        assert_eq!(wire.outgoing_byte_rate, 0.0);
73        assert_eq!(wire.incoming_byte_rate, 0.0);
74        assert_eq!(wire.remote_gateways, 0);
75    }
76
77    /// Whole-number rate forms AND integer/float mixes still parse
78    /// (f64 tolerance), a populated shape carries real values, and
79    /// unknown keys ride `extra` (the round-trip proof).
80    #[test]
81    fn gan_tolerant_over_populated_and_unknown_shapes() {
82        let wire: GanStatusWire = serde_json::from_value(serde_json::json!({
83            "totalConnections": 4,
84            "runningConnections": 2,
85            "outgoingByteRate": 15360,
86            "incomingByteRate": 2048.5,
87            "remoteGateways": 1,
88            "futurePointReleaseField": {"rate": 1.25}
89        }))
90        .expect("whole-number + unknown-key forms must parse");
91        assert_eq!(wire.total_connections, 4);
92        assert_eq!(wire.running_connections, 2);
93        assert_eq!(wire.outgoing_byte_rate, 15360.0, "f64 parses ints too");
94        assert_eq!(wire.incoming_byte_rate, 2048.5);
95        assert_eq!(wire.remote_gateways, 1);
96        assert_eq!(
97            wire.extra.get("futurePointReleaseField"),
98            Some(&serde_json::json!({"rate": 1.25})),
99            "unknown keys ride flatten passthrough"
100        );
101    }
102}