stygian_proxy/routing.rs
1//! Protocol-aware routing path resolution.
2//!
3//! [`resolve_routing_path`] translates a proxy's advertised capabilities and
4//! the caller's transport preference into a concrete [`RoutingPath`] that the
5//! HTTP client layer uses when opening a connection.
6
7use crate::types::{ProxyCapabilities, RoutingPath};
8
9/// Preference expressed by the caller when acquiring a proxy.
10///
11/// # Example
12/// ```
13/// use stygian_proxy::routing::TransportPreference;
14/// assert_eq!(TransportPreference::default(), TransportPreference::PreferH3);
15/// ```
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum TransportPreference {
18 /// Use HTTP/3 over QUIC if the proxy supports it; fall back to TCP.
19 #[default]
20 PreferH3,
21 /// Always use HTTP/1.1 or HTTP/2 over a TCP CONNECT tunnel.
22 ForceTcp,
23 /// Prefer a persistent TCP connection that spans multiple logical requests.
24 ///
25 /// Forces a TCP CONNECT tunnel and signals the HTTP client layer to keep
26 /// the underlying connection open between requests. Connection lifetime
27 /// and per-connection request limits are governed by
28 /// [`crate::types::ProxyConfig::max_requests_per_connection`] and
29 /// [`crate::types::ProxyConfig::connection_max_age_secs`].
30 PersistentTcp,
31}
32
33/// Resolve the [`RoutingPath`] for a request given proxy capabilities and the
34/// caller's transport preference.
35///
36/// # Decision logic
37///
38/// | Preference | `supports_http3_tunnel` | Result |
39/// |------------------|------------------------|------------------|
40/// | `PreferH3` | `true` | `H3OverUdp` |
41/// | `PreferH3` | `false` | `H1H2OverTcp` |
42/// | `ForceTcp` | any | `H1H2OverTcp` |
43/// | `PersistentTcp` | any | `PersistentTcp` |
44///
45/// # Example
46/// ```
47/// use stygian_proxy::routing::{resolve_routing_path, TransportPreference};
48/// use stygian_proxy::types::{ProxyCapabilities, RoutingPath};
49///
50/// let caps = ProxyCapabilities { supports_http3_tunnel: true, ..Default::default() };
51/// assert_eq!(
52/// resolve_routing_path(&caps, TransportPreference::PreferH3),
53/// RoutingPath::H3OverUdp,
54/// );
55/// assert_eq!(
56/// resolve_routing_path(&caps, TransportPreference::ForceTcp),
57/// RoutingPath::H1H2OverTcp,
58/// );
59/// let fallback = ProxyCapabilities::default();
60/// assert_eq!(
61/// resolve_routing_path(&fallback, TransportPreference::PreferH3),
62/// RoutingPath::H1H2OverTcp,
63/// );
64/// ```
65pub const fn resolve_routing_path(
66 capabilities: &ProxyCapabilities,
67 preference: TransportPreference,
68) -> RoutingPath {
69 match preference {
70 TransportPreference::ForceTcp => RoutingPath::H1H2OverTcp,
71 TransportPreference::PersistentTcp => RoutingPath::PersistentTcp,
72 TransportPreference::PreferH3 => {
73 if capabilities.supports_http3_tunnel {
74 RoutingPath::H3OverUdp
75 } else {
76 RoutingPath::H1H2OverTcp
77 }
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::types::ProxyCapabilities;
86
87 #[test]
88 fn prefer_h3_with_udp_support_returns_h3() {
89 let caps = ProxyCapabilities {
90 supports_http3_tunnel: true,
91 ..Default::default()
92 };
93 assert_eq!(
94 resolve_routing_path(&caps, TransportPreference::PreferH3),
95 RoutingPath::H3OverUdp,
96 );
97 }
98
99 #[test]
100 fn prefer_h3_without_udp_support_falls_back_to_tcp() {
101 let caps = ProxyCapabilities::default();
102 assert_eq!(
103 resolve_routing_path(&caps, TransportPreference::PreferH3),
104 RoutingPath::H1H2OverTcp,
105 );
106 }
107
108 #[test]
109 fn force_tcp_always_returns_tcp() {
110 let caps = ProxyCapabilities {
111 supports_http3_tunnel: true,
112 ..Default::default()
113 };
114 assert_eq!(
115 resolve_routing_path(&caps, TransportPreference::ForceTcp),
116 RoutingPath::H1H2OverTcp,
117 );
118 }
119
120 #[test]
121 fn persistent_tcp_returns_persistent_tcp() {
122 let caps = ProxyCapabilities {
123 supports_http3_tunnel: true,
124 ..Default::default()
125 };
126 assert_eq!(
127 resolve_routing_path(&caps, TransportPreference::PersistentTcp),
128 RoutingPath::PersistentTcp,
129 );
130 }
131}