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