Skip to main content

bulwark_security/request/
context.rs

1use std::collections::HashMap;
2use std::str::FromStr;
3
4/// Represents a normalized HTTP request inside Bulwark.
5///
6/// This is NOT a raw HTTP request.
7/// Everything here is meant to be:
8/// - explicit
9/// - predictable
10/// - safe to inspect
11#[derive(Debug, Clone)]
12pub struct RequestContext {
13    /// HTTP method (GET, POST, etc)
14    pub method: Method,
15
16    /// Normalized path (no query string)
17    pub path: String,
18
19    /// Query parameters (?a=b&c=d)
20    pub query: HashMap<String, String>,
21
22    /// Request headers (lowercased keys)
23    pub headers: HashMap<String, String>,
24
25    /// Raw request body (optional)
26    pub body: Option<Vec<u8>>,
27
28    /// Client IP (if known)
29    pub client_ip: Option<String>,
30}
31
32impl RequestContext {
33    /// Create a new empty request context.
34    pub fn new(method: Method, path: impl Into<String>) -> Self {
35        Self {
36            method,
37            path: path.into(),
38            query: HashMap::new(),
39            headers: HashMap::new(),
40            body: None,
41            client_ip: None,
42        }
43    }
44
45    /// Insert a header (key will be lowercased).
46    pub fn insert_header(&mut self, key: impl Into<String>, value: impl Into<String>) {
47        self.headers.insert(key.into().to_lowercase(), value.into());
48    }
49
50    /// Insert a query parameter.
51    pub fn insert_query(&mut self, key: impl Into<String>, value: impl Into<String>) {
52        self.query.insert(key.into(), value.into());
53    }
54
55    /// Set request body.
56    pub fn set_body(&mut self, body: Vec<u8>) {
57        self.body = Some(body);
58    }
59
60    /// Set client IP.
61    pub fn set_client_ip(&mut self, ip: impl Into<String>) {
62        self.client_ip = Some(ip.into());
63    }
64
65    /// Get a header value (case-insensitive).
66    pub fn header(&self, key: &str) -> Option<&String> {
67        self.headers.get(&key.to_lowercase())
68    }
69
70    /// Check if request has a body.
71    pub fn has_body(&self) -> bool {
72        self.body.is_some()
73    }
74}
75
76/// Supported HTTP methods.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum Method {
79    GET,
80    POST,
81    PUT,
82    DELETE,
83    PATCH,
84    HEAD,
85    OPTIONS,
86}
87
88impl FromStr for Method {
89    type Err = ();
90
91    fn from_str(value: &str) -> Result<Self, Self::Err> {
92        match value {
93            "GET" => Ok(Method::GET),
94            "POST" => Ok(Method::POST),
95            "PUT" => Ok(Method::PUT),
96            "DELETE" => Ok(Method::DELETE),
97            "PATCH" => Ok(Method::PATCH),
98            "HEAD" => Ok(Method::HEAD),
99            "OPTIONS" => Ok(Method::OPTIONS),
100            _ => Err(()),
101        }
102    }
103}