sentinel_proxy/proxy/
context.rs

1//! Request context for the proxy request lifecycle.
2//!
3//! The `RequestContext` struct maintains state throughout a single request,
4//! including timing, routing decisions, and metadata for logging.
5
6use std::time::Instant;
7
8/// Request context maintained throughout the request lifecycle.
9///
10/// This struct uses a hybrid approach:
11/// - Immutable fields (start_time) are private with getters
12/// - Mutable fields are public(crate) for efficient access within the proxy module
13pub struct RequestContext {
14    /// Request start time (immutable after creation)
15    start_time: Instant,
16
17    // === Tracing ===
18    /// Unique trace ID for request tracing (also used as correlation_id)
19    pub(crate) trace_id: String,
20
21    // === Routing ===
22    /// Selected route ID
23    pub(crate) route_id: Option<String>,
24    /// Selected upstream
25    pub(crate) upstream: Option<String>,
26    /// Number of upstream attempts
27    pub(crate) upstream_attempts: u32,
28
29    // === Request metadata (cached for logging) ===
30    /// HTTP method
31    pub(crate) method: String,
32    /// Request path
33    pub(crate) path: String,
34    /// Query string
35    pub(crate) query: Option<String>,
36
37    // === Client info ===
38    /// Client IP address
39    pub(crate) client_ip: String,
40    /// User-Agent header
41    pub(crate) user_agent: Option<String>,
42    /// Referer header
43    pub(crate) referer: Option<String>,
44    /// Host header
45    pub(crate) host: Option<String>,
46
47    // === Response tracking ===
48    /// Response body bytes (set during response)
49    pub(crate) response_bytes: u64,
50}
51
52impl RequestContext {
53    /// Create a new empty request context with the current timestamp.
54    pub fn new() -> Self {
55        Self {
56            start_time: Instant::now(),
57            trace_id: String::new(),
58            route_id: None,
59            upstream: None,
60            upstream_attempts: 0,
61            method: String::new(),
62            path: String::new(),
63            query: None,
64            client_ip: String::new(),
65            user_agent: None,
66            referer: None,
67            host: None,
68            response_bytes: 0,
69        }
70    }
71
72    // === Immutable field accessors ===
73
74    /// Get the request start time.
75    #[inline]
76    pub fn start_time(&self) -> Instant {
77        self.start_time
78    }
79
80    /// Get elapsed duration since request start.
81    #[inline]
82    pub fn elapsed(&self) -> std::time::Duration {
83        self.start_time.elapsed()
84    }
85
86    // === Read-only accessors ===
87
88    /// Get trace_id (alias for backwards compatibility with correlation_id usage).
89    #[inline]
90    pub fn correlation_id(&self) -> &str {
91        &self.trace_id
92    }
93
94    /// Get the trace ID.
95    #[inline]
96    pub fn trace_id(&self) -> &str {
97        &self.trace_id
98    }
99
100    /// Get the route ID, if set.
101    #[inline]
102    pub fn route_id(&self) -> Option<&str> {
103        self.route_id.as_deref()
104    }
105
106    /// Get the upstream ID, if set.
107    #[inline]
108    pub fn upstream(&self) -> Option<&str> {
109        self.upstream.as_deref()
110    }
111
112    /// Get the number of upstream attempts.
113    #[inline]
114    pub fn upstream_attempts(&self) -> u32 {
115        self.upstream_attempts
116    }
117
118    /// Get the HTTP method.
119    #[inline]
120    pub fn method(&self) -> &str {
121        &self.method
122    }
123
124    /// Get the request path.
125    #[inline]
126    pub fn path(&self) -> &str {
127        &self.path
128    }
129
130    /// Get the query string, if present.
131    #[inline]
132    pub fn query(&self) -> Option<&str> {
133        self.query.as_deref()
134    }
135
136    /// Get the client IP address.
137    #[inline]
138    pub fn client_ip(&self) -> &str {
139        &self.client_ip
140    }
141
142    /// Get the User-Agent header, if present.
143    #[inline]
144    pub fn user_agent(&self) -> Option<&str> {
145        self.user_agent.as_deref()
146    }
147
148    /// Get the Referer header, if present.
149    #[inline]
150    pub fn referer(&self) -> Option<&str> {
151        self.referer.as_deref()
152    }
153
154    /// Get the Host header, if present.
155    #[inline]
156    pub fn host(&self) -> Option<&str> {
157        self.host.as_deref()
158    }
159
160    /// Get the response body size in bytes.
161    #[inline]
162    pub fn response_bytes(&self) -> u64 {
163        self.response_bytes
164    }
165
166    // === Mutation helpers ===
167
168    /// Set the trace ID.
169    #[inline]
170    pub fn set_trace_id(&mut self, trace_id: impl Into<String>) {
171        self.trace_id = trace_id.into();
172    }
173
174    /// Set the route ID.
175    #[inline]
176    pub fn set_route_id(&mut self, route_id: impl Into<String>) {
177        self.route_id = Some(route_id.into());
178    }
179
180    /// Set the upstream ID.
181    #[inline]
182    pub fn set_upstream(&mut self, upstream: impl Into<String>) {
183        self.upstream = Some(upstream.into());
184    }
185
186    /// Increment upstream attempt counter.
187    #[inline]
188    pub fn inc_upstream_attempts(&mut self) {
189        self.upstream_attempts += 1;
190    }
191
192    /// Set response bytes.
193    #[inline]
194    pub fn set_response_bytes(&mut self, bytes: u64) {
195        self.response_bytes = bytes;
196    }
197}
198
199impl Default for RequestContext {
200    fn default() -> Self {
201        Self::new()
202    }
203}