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/// ```
65#[must_use]
66pub const fn resolve_routing_path(
67 capabilities: &ProxyCapabilities,
68 preference: TransportPreference,
69) -> RoutingPath {
70 match preference {
71 TransportPreference::ForceTcp => RoutingPath::H1H2OverTcp,
72 TransportPreference::PersistentTcp => RoutingPath::PersistentTcp,
73 TransportPreference::PreferH3 => {
74 if capabilities.supports_http3_tunnel {
75 RoutingPath::H3OverUdp
76 } else {
77 RoutingPath::H1H2OverTcp
78 }
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::types::ProxyCapabilities;
87
88 #[test]
89 fn prefer_h3_with_udp_support_returns_h3() {
90 let caps = ProxyCapabilities {
91 supports_http3_tunnel: true,
92 ..Default::default()
93 };
94 assert_eq!(
95 resolve_routing_path(&caps, TransportPreference::PreferH3),
96 RoutingPath::H3OverUdp,
97 );
98 }
99
100 #[test]
101 fn prefer_h3_without_udp_support_falls_back_to_tcp() {
102 let caps = ProxyCapabilities::default();
103 assert_eq!(
104 resolve_routing_path(&caps, TransportPreference::PreferH3),
105 RoutingPath::H1H2OverTcp,
106 );
107 }
108
109 #[test]
110 fn force_tcp_always_returns_tcp() {
111 let caps = ProxyCapabilities {
112 supports_http3_tunnel: true,
113 ..Default::default()
114 };
115 assert_eq!(
116 resolve_routing_path(&caps, TransportPreference::ForceTcp),
117 RoutingPath::H1H2OverTcp,
118 );
119 }
120
121 #[test]
122 fn persistent_tcp_returns_persistent_tcp() {
123 let caps = ProxyCapabilities {
124 supports_http3_tunnel: true,
125 ..Default::default()
126 };
127 assert_eq!(
128 resolve_routing_path(&caps, TransportPreference::PersistentTcp),
129 RoutingPath::PersistentTcp,
130 );
131 }
132}