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