use std::collections::HashMap;
pub struct Request {
pub method: String,
pub path: String,
pub headers: HashMap<String, String>,
pub version: String,
pub query: HashMap<String, String>,
pub params: HashMap<String, String>,
pub body: String,
}
impl Request {
pub fn new(request_line: &str, headers: HashMap<String, String>, body: String) -> Request {
let parts: Vec<&str> = request_line.split_whitespace().collect();
let (method, full_path, version) = match parts.as_slice() {
[m, p, v] => (m.to_string(), p.to_string(), v.to_string()),
_ => ("GET".to_string(), "/".to_string(), "HTTP/1.1".to_string()),
};
let (path, query) = if let Some((p, q)) = full_path.split_once('?') {
(p.to_string(), Self::parse_query(q))
} else {
(full_path, HashMap::new())
};
Request {
method,
path,
headers,
version,
query,
params: HashMap::new(),
body,
}
}
pub fn header(&self, key: &str) -> Option<&String> {
self.headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(key))
.map(|(_, v)| v)
}
pub fn header_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
self.headers
.get(key)
.map(|val| val.as_str())
.unwrap_or(default)
}
pub fn header_expect(&self, key: &str) -> Result<&str, String> {
self.headers.get(key).map(|val| val.as_str()).ok_or(format!(
"[rxpress error]: Required header `{}` is missing. \
Please include it in your request, e.g., `{}: value`.",
key, key
))
}
pub fn param(&self, key: &str) -> Option<&String> {
self.params.get(key)
}
pub fn param_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
self.params
.get(key)
.map(|val| val.as_str())
.unwrap_or(default)
}
pub fn param_expect(&self, key: &str) -> Result<&str, String> {
self.params.get(key).map(|val| val.as_str()).ok_or(format!(
"[rxpress error]: Required route parameter `{}` is missing. \
Ensure your route includes it, e.g., `/route/:{}`.",
key, key
))
}
pub fn query(&self, key: &str) -> Option<&String> {
self.query.get(key)
}
pub fn query_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
self.query
.get(key)
.map(|val| val.as_str())
.unwrap_or(default)
}
pub fn query_expect(&self, key: &str) -> Result<&str, String> {
self.query.get(key).map(|val| val.as_str()).ok_or(format!(
"[rxpress error]: Required query parameter `{}` is missing. \
Please include it in your request, e.g., `/route?{}=value`.",
key, key
))
}
fn parse_query(q: &str) -> HashMap<String, String> {
let mut map: HashMap<String, String> = HashMap::new();
for pair in q.split('&') {
if let Some((k, v)) = pair.split_once('=') {
map.insert(k.to_string(), v.to_string());
} else {
map.insert(pair.to_string(), "".to_string());
}
}
map
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn make_req_line(line: &str) -> Request {
Request::new(line, HashMap::new(), "".into())
}
#[test]
fn test_header_case_insensitive() {
let mut headers = HashMap::new();
headers.insert("Content-Type".into(), "application/json".into());
let req = Request::new("GET / HTTP/1.1", headers, "".into());
assert_eq!(
req.header("content-type"),
Some(&"application/json".to_string())
);
}
#[test]
fn test_header_or() {
let mut headers = HashMap::new();
headers.insert("Content-Type".into(), "application/json".into());
let req = Request::new("GET / HTTP/1.1", headers, "".into());
assert_eq!(
req.header_or("Content-Type", "text/plain"),
"application/json"
);
assert_eq!(req.header_or("Non-Existent", "default"), "default");
}
#[test]
fn test_header_expect() {
let mut headers = HashMap::new();
headers.insert("Authorization".into(), "Bearer abc123".into());
let req = Request::new("GET / HTTP/1.1", headers, "".into());
assert_eq!(req.header_expect("Authorization").unwrap(), "Bearer abc123");
let err = req.header_expect("X-Token").unwrap_err();
assert!(err.contains("Required header `X-Token` is missing"));
}
#[test]
fn test_param_insertion_and_lookup() {
let mut req = make_req_line("GET /users/1 HTTP/1.1");
req.params.insert("id".into(), "1".into());
assert_eq!(req.param("id"), Some(&"1".to_string()));
}
#[test]
fn test_param_or() {
let mut req = Request::new("GET /users/ HTTP/1.1", HashMap::new(), "".into());
req.params.insert("id".into(), "42".into());
assert_eq!(req.param_or("id", "0"), "42");
assert_eq!(req.param_or("username", "guest"), "guest");
}
#[test]
fn test_param_expect() {
let mut req = Request::new("GET /users/42 HTTP/1.1", HashMap::new(), "".into());
req.params.insert("id".into(), "42".into());
assert_eq!(req.param_expect("id").unwrap(), "42");
let err = req.param_expect("username").unwrap_err();
assert!(err.contains("Required route parameter `username` is missing"));
}
#[test]
fn test_query_lookup() {
let req = make_req_line("GET /search?q=rust HTTP/1.1");
assert_eq!(req.query("q"), Some(&"rust".to_string()));
}
#[test]
fn test_query_or() {
let req = Request::new("GET /search?q=rust HTTP/1.1", HashMap::new(), "".into());
assert_eq!(req.query_or("q", "none"), "rust");
assert_eq!(req.query_or("page", "1"), "1");
}
#[test]
fn test_query_expect() {
let req = Request::new("GET /search?q=rust HTTP/1.1", HashMap::new(), "".into());
assert_eq!(req.query_expect("q").unwrap(), "rust");
let err = req.query_expect("page").unwrap_err();
assert!(err.contains("Required query parameter `page` is missing"));
}
#[test]
fn test_parse_query_function() {
let parsed = Request::parse_query("a=1&b=2&empty");
assert_eq!(parsed.get("a"), Some(&"1".to_string()));
assert_eq!(parsed.get("b"), Some(&"2".to_string()));
assert_eq!(parsed.get("empty"), Some(&"".to_string()));
}
}