Skip to main content

eggserve_core/primitives/
connection_info.rs

1//! Connection metadata for a request.
2//!
3//! [`ConnectionInfo`] carries transport-level metadata about the connection
4//! on which a request was received. It is separate from request headers
5//! and is not mixed into the header block.
6
7use std::fmt;
8use std::net::SocketAddr;
9
10/// TLS metadata for a connection.
11///
12/// Contains information about the TLS session, if any. Bounded to
13/// avoid exposing implementation-specific internals.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct TlsInfo {
16    /// The negotiated TLS protocol version (e.g., "TLSv1.3"), if available.
17    pub protocol_version: Option<String>,
18    /// The Server Name Indication (SNI) value, if available.
19    pub server_name: Option<String>,
20}
21
22/// Immutable connection metadata for an HTTP request.
23///
24/// Values come from the actual transport. `Forwarded` and
25/// `X-Forwarded-*` headers are ordinary untrusted headers and are not
26/// part of this type.
27///
28/// # Separation from headers
29///
30/// Connection metadata is never mixed into request headers. Callers who
31/// need proxy-trusted values should read `Forwarded` or
32/// `X-Forwarded-*` headers separately and validate them according to
33/// their trust model.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct ConnectionInfo {
36    /// The local socket address the connection was accepted on.
37    pub local_addr: SocketAddr,
38    /// The remote socket address of the peer.
39    pub remote_addr: SocketAddr,
40    /// The request URI scheme (e.g., `http` or `https`).
41    pub scheme: Scheme,
42    /// TLS session metadata, if the connection is TLS-secured.
43    pub tls: Option<TlsInfo>,
44}
45
46/// The request URI scheme.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum Scheme {
49    /// Plain HTTP.
50    Http,
51    /// HTTPS (HTTP over TLS).
52    Https,
53}
54
55impl Scheme {
56    /// Returns the scheme as a string slice.
57    pub fn as_str(&self) -> &'static str {
58        match self {
59            Self::Http => "http",
60            Self::Https => "https",
61        }
62    }
63}
64
65impl fmt::Display for Scheme {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        f.write_str(self.as_str())
68    }
69}
70
71impl fmt::Display for TlsInfo {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "TLS")?;
74        if let Some(ref v) = self.protocol_version {
75            write!(f, " {v}")?;
76        }
77        if let Some(ref n) = self.server_name {
78            write!(f, " SNI={n}")?;
79        }
80        Ok(())
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn scheme_as_str() {
90        assert_eq!(Scheme::Http.as_str(), "http");
91        assert_eq!(Scheme::Https.as_str(), "https");
92    }
93
94    #[test]
95    fn scheme_display() {
96        assert_eq!(format!("{}", Scheme::Http), "http");
97        assert_eq!(format!("{}", Scheme::Https), "https");
98    }
99
100    #[test]
101    fn tls_info_display() {
102        let info = TlsInfo {
103            protocol_version: Some("TLSv1.3".to_string()),
104            server_name: Some("example.com".to_string()),
105        };
106        let display = format!("{info}");
107        assert!(display.contains("TLSv1.3"));
108        assert!(display.contains("example.com"));
109    }
110
111    #[test]
112    fn tls_info_minimal() {
113        let info = TlsInfo {
114            protocol_version: None,
115            server_name: None,
116        };
117        assert_eq!(format!("{info}"), "TLS");
118    }
119
120    #[test]
121    fn connection_info_equality() {
122        let a = ConnectionInfo {
123            local_addr: "127.0.0.1:8000".parse().unwrap(),
124            remote_addr: "127.0.0.1:12345".parse().unwrap(),
125            scheme: Scheme::Http,
126            tls: None,
127        };
128        let b = ConnectionInfo {
129            local_addr: "127.0.0.1:8000".parse().unwrap(),
130            remote_addr: "127.0.0.1:12345".parse().unwrap(),
131            scheme: Scheme::Http,
132            tls: None,
133        };
134        assert_eq!(a, b);
135    }
136
137    #[test]
138    fn connection_info_with_tls() {
139        let info = ConnectionInfo {
140            local_addr: "0.0.0.0:443".parse().unwrap(),
141            remote_addr: "10.0.0.1:54321".parse().unwrap(),
142            scheme: Scheme::Https,
143            tls: Some(TlsInfo {
144                protocol_version: Some("TLSv1.3".to_string()),
145                server_name: Some("example.com".to_string()),
146            }),
147        };
148        assert_eq!(info.scheme, Scheme::Https);
149        assert!(info.tls.is_some());
150    }
151}