Skip to main content

relay_knowledge/net/
mod.rs

1//! Network configuration and policy boundary.
2//!
3//! All network-facing code must enter through this module or its children.
4//! The current foundation layer defines event-driven HTTP configuration and
5//! QoS admission policy without opening sockets or starting unmanaged loops.
6
7use std::{
8    error::Error,
9    fmt,
10    sync::{Arc, RwLock},
11};
12
13use crate::env::{EnvError, EnvironmentConfig, NetworkEnvOverrides};
14
15pub mod http;
16pub mod qos;
17
18/// Resolved network policy shared by future HTTP clients and servers.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct NetworkConfig {
21    pub http: http::HttpConfig,
22    pub qos: qos::QosPolicy,
23}
24
25impl NetworkConfig {
26    /// Resolves environment overrides into validated network configuration.
27    pub fn from_overrides(overrides: &NetworkEnvOverrides) -> Result<Self, NetworkConfigError> {
28        Ok(Self {
29            http: http::HttpConfig::from_overrides(overrides).map_err(NetworkConfigError::Http)?,
30            qos: qos::QosPolicy::from_overrides(overrides).map_err(NetworkConfigError::Qos)?,
31        })
32    }
33}
34
35/// Refreshable network configuration shared by network adapters.
36#[derive(Debug, Clone)]
37pub struct NetworkRuntime {
38    inner: Arc<RwLock<NetworkConfig>>,
39}
40
41impl NetworkRuntime {
42    /// Creates a refreshable handle from validated network configuration.
43    pub fn from_config(config: NetworkConfig) -> Self {
44        Self {
45            inner: Arc::new(RwLock::new(config)),
46        }
47    }
48
49    /// Creates a refreshable handle from environment overrides.
50    pub fn from_overrides(overrides: &NetworkEnvOverrides) -> Result<Self, NetworkConfigError> {
51        NetworkConfig::from_overrides(overrides).map(Self::from_config)
52    }
53
54    /// Returns the latest validated network configuration.
55    pub fn current(&self) -> NetworkConfig {
56        self.inner
57            .read()
58            .unwrap_or_else(|poisoned| poisoned.into_inner())
59            .clone()
60    }
61
62    /// Replaces the active network configuration after validating overrides.
63    pub fn refresh_from_overrides(
64        &self,
65        overrides: &NetworkEnvOverrides,
66    ) -> Result<NetworkConfig, NetworkConfigError> {
67        let config = NetworkConfig::from_overrides(overrides)?;
68
69        *self
70            .inner
71            .write()
72            .unwrap_or_else(|poisoned| poisoned.into_inner()) = config.clone();
73
74        Ok(config)
75    }
76
77    /// Replaces the active network configuration from a typed environment snapshot.
78    pub fn refresh_from_environment(
79        &self,
80        environment: &EnvironmentConfig,
81    ) -> Result<NetworkConfig, NetworkConfigError> {
82        self.refresh_from_overrides(&environment.network)
83    }
84
85    /// Re-reads the current process environment and applies network changes.
86    pub fn refresh_from_process_environment(&self) -> Result<NetworkConfig, NetworkRuntimeError> {
87        let environment =
88            EnvironmentConfig::from_process().map_err(NetworkRuntimeError::Environment)?;
89
90        self.refresh_from_environment(&environment)
91            .map_err(NetworkRuntimeError::Config)
92    }
93}
94
95/// Network configuration error grouped by owning submodule.
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum NetworkConfigError {
98    Http(http::HttpConfigError),
99    Qos(qos::QosPolicyError),
100}
101
102impl fmt::Display for NetworkConfigError {
103    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
104        match self {
105            Self::Http(error) => write!(formatter, "invalid HTTP configuration: {error}"),
106            Self::Qos(error) => write!(formatter, "invalid QoS policy: {error}"),
107        }
108    }
109}
110
111impl Error for NetworkConfigError {}
112
113/// Error raised while refreshing network config from live environment state.
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum NetworkRuntimeError {
116    Environment(EnvError),
117    Config(NetworkConfigError),
118}
119
120impl fmt::Display for NetworkRuntimeError {
121    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::Environment(error) => write!(formatter, "{error}"),
124            Self::Config(error) => write!(formatter, "{error}"),
125        }
126    }
127}
128
129impl Error for NetworkRuntimeError {}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::env::PlatformKind;
135
136    #[test]
137    fn resolves_default_network_configuration() {
138        let config = NetworkConfig::from_overrides(&NetworkEnvOverrides::default())
139            .expect("defaults should resolve");
140
141        assert_eq!(config.http.bind_address.to_string(), "127.0.0.1:8791");
142        assert!(!config.http.proxy.is_proxy_configured());
143        assert!(config.http.proxy.ssl_verify);
144        assert_eq!(config.qos.max_connections, 1024);
145        assert_eq!(config.qos.max_in_flight_requests, 256);
146        assert_eq!(config.qos.max_queue_depth, 512);
147    }
148
149    #[test]
150    fn refreshes_runtime_network_config_from_environment_snapshot() {
151        let runtime = NetworkRuntime::from_overrides(&NetworkEnvOverrides::default())
152            .expect("runtime should build");
153        let environment = EnvironmentConfig::from_pairs(
154            PlatformKind::Unix,
155            [
156                ("HTTP_PROXY", "http://relay-proxy:8080"),
157                ("NO_PROXY", "localhost"),
158                ("SSL_VERIFY", "false"),
159                ("RELAY_KNOWLEDGE_QOS_MAX_CONNECTIONS", "8"),
160            ],
161        )
162        .expect("environment should parse");
163
164        runtime
165            .refresh_from_environment(&environment)
166            .expect("network refresh should succeed");
167        let config = runtime.current();
168
169        assert_eq!(
170            config.http.proxy.proxy,
171            Some("http://relay-proxy:8080".to_owned())
172        );
173        assert_eq!(config.http.proxy.no_proxy_rules, ["localhost"]);
174        assert!(!config.http.proxy.ssl_verify);
175        assert_eq!(config.qos.max_connections, 8);
176    }
177}