1use crate::auth::Principal;
29use alloc::collections::BTreeMap;
30use alloc::string::String;
31use serde::{Deserialize, Serialize};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
40#[serde(rename_all = "lowercase")]
41#[non_exhaustive]
42pub enum TransportType {
43 #[default]
45 Stdio,
46 Http,
48 WebSocket,
50 Tcp,
52 Unix,
54 Wasm,
56 Channel,
58 Unknown,
60}
61
62impl TransportType {
63 #[inline]
65 pub fn is_network(&self) -> bool {
66 matches!(self, Self::Http | Self::WebSocket | Self::Tcp)
67 }
68
69 #[inline]
71 pub fn is_local(&self) -> bool {
72 matches!(self, Self::Stdio | Self::Unix | Self::Channel)
73 }
74
75 pub fn as_str(&self) -> &'static str {
77 match self {
78 Self::Stdio => "stdio",
79 Self::Http => "http",
80 Self::WebSocket => "websocket",
81 Self::Tcp => "tcp",
82 Self::Unix => "unix",
83 Self::Wasm => "wasm",
84 Self::Channel => "channel",
85 Self::Unknown => "unknown",
86 }
87 }
88}
89
90impl core::fmt::Display for TransportType {
91 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
92 write!(f, "{}", self.as_str())
93 }
94}
95
96#[derive(Debug, Clone, Default)]
112pub struct RequestContext {
113 pub request_id: String,
115 pub transport: TransportType,
117 pub metadata: BTreeMap<String, String>,
121 pub principal: Option<Principal>,
126}
127
128impl RequestContext {
129 pub fn new(request_id: impl Into<String>, transport: TransportType) -> Self {
140 Self {
141 request_id: request_id.into(),
142 transport,
143 metadata: BTreeMap::new(),
144 principal: None,
145 }
146 }
147
148 #[inline]
150 pub fn stdio() -> Self {
151 Self::new("", TransportType::Stdio)
152 }
153
154 #[inline]
156 pub fn http() -> Self {
157 Self::new("", TransportType::Http)
158 }
159
160 #[inline]
162 pub fn websocket() -> Self {
163 Self::new("", TransportType::WebSocket)
164 }
165
166 #[inline]
168 pub fn tcp() -> Self {
169 Self::new("", TransportType::Tcp)
170 }
171
172 #[inline]
174 pub fn wasm() -> Self {
175 Self::new("", TransportType::Wasm)
176 }
177
178 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
192 self.metadata.insert(key.into(), value.into());
193 self
194 }
195
196 pub fn insert_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
198 self.metadata.insert(key.into(), value.into());
199 }
200
201 pub fn get_metadata(&self, key: &str) -> Option<&str> {
215 self.metadata.get(key).map(|s| s.as_str())
216 }
217
218 pub fn has_metadata(&self, key: &str) -> bool {
220 self.metadata.contains_key(key)
221 }
222
223 pub fn with_request_id(mut self, id: impl Into<String>) -> Self {
225 self.request_id = id.into();
226 self
227 }
228
229 pub fn has_request_id(&self) -> bool {
231 !self.request_id.is_empty()
232 }
233
234 pub fn with_principal(mut self, principal: Principal) -> Self {
249 self.principal = Some(principal);
250 self
251 }
252
253 pub fn set_principal(&mut self, principal: Principal) {
255 self.principal = Some(principal);
256 }
257
258 pub fn principal(&self) -> Option<&Principal> {
263 self.principal.as_ref()
264 }
265
266 pub fn is_authenticated(&self) -> bool {
268 self.principal.is_some()
269 }
270
271 pub fn subject(&self) -> Option<&str> {
275 self.principal.as_ref().map(|p| p.subject.as_str())
276 }
277
278 pub fn clear_principal(&mut self) {
280 self.principal = None;
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_transport_type_display() {
290 assert_eq!(TransportType::Stdio.to_string(), "stdio");
291 assert_eq!(TransportType::Http.to_string(), "http");
292 assert_eq!(TransportType::WebSocket.to_string(), "websocket");
293 assert_eq!(TransportType::Tcp.to_string(), "tcp");
294 assert_eq!(TransportType::Unix.to_string(), "unix");
295 assert_eq!(TransportType::Wasm.to_string(), "wasm");
296 assert_eq!(TransportType::Channel.to_string(), "channel");
297 assert_eq!(TransportType::Unknown.to_string(), "unknown");
298 }
299
300 #[test]
301 fn test_transport_type_classification() {
302 assert!(TransportType::Http.is_network());
303 assert!(TransportType::WebSocket.is_network());
304 assert!(TransportType::Tcp.is_network());
305 assert!(!TransportType::Stdio.is_network());
306
307 assert!(TransportType::Stdio.is_local());
308 assert!(TransportType::Unix.is_local());
309 assert!(TransportType::Channel.is_local());
310 assert!(!TransportType::Http.is_local());
311 }
312
313 #[test]
314 fn test_request_context_new() {
315 let ctx = RequestContext::new("test-123", TransportType::Http);
316 assert_eq!(ctx.request_id, "test-123");
317 assert_eq!(ctx.transport, TransportType::Http);
318 assert!(ctx.metadata.is_empty());
319 }
320
321 #[test]
322 fn test_request_context_factory_methods() {
323 assert_eq!(RequestContext::stdio().transport, TransportType::Stdio);
324 assert_eq!(RequestContext::http().transport, TransportType::Http);
325 assert_eq!(
326 RequestContext::websocket().transport,
327 TransportType::WebSocket
328 );
329 assert_eq!(RequestContext::tcp().transport, TransportType::Tcp);
330 assert_eq!(RequestContext::wasm().transport, TransportType::Wasm);
331 }
332
333 #[test]
334 fn test_request_context_metadata() {
335 let ctx = RequestContext::new("1", TransportType::Http)
336 .with_metadata("key1", "value1")
337 .with_metadata("key2", "value2");
338
339 assert_eq!(ctx.get_metadata("key1"), Some("value1"));
340 assert_eq!(ctx.get_metadata("key2"), Some("value2"));
341 assert_eq!(ctx.get_metadata("key3"), None);
342
343 assert!(ctx.has_metadata("key1"));
344 assert!(!ctx.has_metadata("key3"));
345 }
346
347 #[test]
348 fn test_request_context_mutable_metadata() {
349 let mut ctx = RequestContext::new("1", TransportType::Http);
350 ctx.insert_metadata("key", "value");
351 assert_eq!(ctx.get_metadata("key"), Some("value"));
352 }
353
354 #[test]
355 fn test_request_context_request_id() {
356 let ctx = RequestContext::new("", TransportType::Http);
357 assert!(!ctx.has_request_id());
358
359 let ctx = ctx.with_request_id("request-456");
360 assert!(ctx.has_request_id());
361 assert_eq!(ctx.request_id, "request-456");
362 }
363
364 #[test]
365 fn test_request_context_default() {
366 let ctx = RequestContext::default();
367 assert_eq!(ctx.request_id, "");
368 assert_eq!(ctx.transport, TransportType::Stdio);
369 assert!(ctx.metadata.is_empty());
370 }
371
372 #[test]
373 fn test_request_context_clone() {
374 let ctx1 = RequestContext::new("1", TransportType::Http).with_metadata("key", "value");
375 let ctx2 = ctx1.clone();
376
377 assert_eq!(ctx1.request_id, ctx2.request_id);
378 assert_eq!(ctx1.transport, ctx2.transport);
379 assert_eq!(ctx1.get_metadata("key"), ctx2.get_metadata("key"));
380 }
381
382 #[test]
383 fn test_request_context_principal() {
384 let ctx = RequestContext::new("1", TransportType::Http);
385 assert!(!ctx.is_authenticated());
386 assert!(ctx.principal().is_none());
387 assert!(ctx.subject().is_none());
388
389 let principal = Principal::new("user-123")
390 .with_email("user@example.com")
391 .with_role("admin");
392
393 let ctx = ctx.with_principal(principal);
394 assert!(ctx.is_authenticated());
395 assert!(ctx.principal().is_some());
396 assert_eq!(ctx.subject(), Some("user-123"));
397 assert_eq!(
398 ctx.principal().unwrap().email,
399 Some("user@example.com".to_string())
400 );
401 assert!(ctx.principal().unwrap().has_role("admin"));
402 }
403
404 #[test]
405 fn test_request_context_principal_mutable() {
406 let mut ctx = RequestContext::new("1", TransportType::Http);
407 assert!(!ctx.is_authenticated());
408
409 ctx.set_principal(Principal::new("user-456"));
410 assert!(ctx.is_authenticated());
411 assert_eq!(ctx.subject(), Some("user-456"));
412
413 ctx.clear_principal();
414 assert!(!ctx.is_authenticated());
415 }
416}