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}
24
25/// Resolve the [`RoutingPath`] for a request given proxy capabilities and the
26/// caller's transport preference.
27///
28/// # Decision logic
29///
30/// | Preference | `supports_http3_tunnel` | Result |
31/// |--------------|------------------------|-----------------|
32/// | `PreferH3` | `true` | `H3OverUdp` |
33/// | `PreferH3` | `false` | `H1H2OverTcp` |
34/// | `ForceTcp` | any | `H1H2OverTcp` |
35///
36/// # Example
37/// ```
38/// use stygian_proxy::routing::{resolve_routing_path, TransportPreference};
39/// use stygian_proxy::types::{ProxyCapabilities, RoutingPath};
40///
41/// let caps = ProxyCapabilities { supports_http3_tunnel: true, ..Default::default() };
42/// assert_eq!(
43/// resolve_routing_path(&caps, TransportPreference::PreferH3),
44/// RoutingPath::H3OverUdp,
45/// );
46/// assert_eq!(
47/// resolve_routing_path(&caps, TransportPreference::ForceTcp),
48/// RoutingPath::H1H2OverTcp,
49/// );
50/// let fallback = ProxyCapabilities::default();
51/// assert_eq!(
52/// resolve_routing_path(&fallback, TransportPreference::PreferH3),
53/// RoutingPath::H1H2OverTcp,
54/// );
55/// ```
56pub const fn resolve_routing_path(
57 capabilities: &ProxyCapabilities,
58 preference: TransportPreference,
59) -> RoutingPath {
60 match preference {
61 TransportPreference::ForceTcp => RoutingPath::H1H2OverTcp,
62 TransportPreference::PreferH3 => {
63 if capabilities.supports_http3_tunnel {
64 RoutingPath::H3OverUdp
65 } else {
66 RoutingPath::H1H2OverTcp
67 }
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::types::ProxyCapabilities;
76
77 #[test]
78 fn prefer_h3_with_udp_support_returns_h3() {
79 let caps = ProxyCapabilities {
80 supports_http3_tunnel: true,
81 ..Default::default()
82 };
83 assert_eq!(
84 resolve_routing_path(&caps, TransportPreference::PreferH3),
85 RoutingPath::H3OverUdp,
86 );
87 }
88
89 #[test]
90 fn prefer_h3_without_udp_support_falls_back_to_tcp() {
91 let caps = ProxyCapabilities::default();
92 assert_eq!(
93 resolve_routing_path(&caps, TransportPreference::PreferH3),
94 RoutingPath::H1H2OverTcp,
95 );
96 }
97
98 #[test]
99 fn force_tcp_always_returns_tcp() {
100 let caps = ProxyCapabilities {
101 supports_http3_tunnel: true,
102 ..Default::default()
103 };
104 assert_eq!(
105 resolve_routing_path(&caps, TransportPreference::ForceTcp),
106 RoutingPath::H1H2OverTcp,
107 );
108 }
109}