next_rs_middleware/
request.rs1use std::collections::HashMap;
2
3#[derive(Debug, Clone)]
4pub struct NextRequest {
5 pub method: String,
6 pub url: String,
7 pub path: String,
8 pub query: HashMap<String, String>,
9 pub headers: HashMap<String, String>,
10 pub cookies: HashMap<String, String>,
11 pub geo: Option<GeoData>,
12 pub ip: Option<String>,
13}
14
15#[derive(Debug, Clone)]
16pub struct GeoData {
17 pub country: Option<String>,
18 pub region: Option<String>,
19 pub city: Option<String>,
20}
21
22impl NextRequest {
23 pub fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
24 let url_str: String = url.into();
25 let (path, query) = Self::parse_url(&url_str);
26
27 Self {
28 method: method.into(),
29 url: url_str,
30 path,
31 query,
32 headers: HashMap::new(),
33 cookies: HashMap::new(),
34 geo: None,
35 ip: None,
36 }
37 }
38
39 fn parse_url(url: &str) -> (String, HashMap<String, String>) {
40 let parts: Vec<&str> = url.splitn(2, '?').collect();
41 let path = parts[0].to_string();
42 let mut query = HashMap::new();
43
44 if parts.len() > 1 {
45 for pair in parts[1].split('&') {
46 let kv: Vec<&str> = pair.splitn(2, '=').collect();
47 if kv.len() == 2 {
48 query.insert(kv[0].to_string(), kv[1].to_string());
49 }
50 }
51 }
52
53 (path, query)
54 }
55
56 pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
57 self.headers.insert(key.into(), value.into());
58 self
59 }
60
61 pub fn with_cookie(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
62 self.cookies.insert(key.into(), value.into());
63 self
64 }
65
66 pub fn with_ip(mut self, ip: impl Into<String>) -> Self {
67 self.ip = Some(ip.into());
68 self
69 }
70
71 pub fn header(&self, key: &str) -> Option<&String> {
72 self.headers.get(key)
73 }
74
75 pub fn cookie(&self, key: &str) -> Option<&String> {
76 self.cookies.get(key)
77 }
78
79 pub fn query_param(&self, key: &str) -> Option<&String> {
80 self.query.get(key)
81 }
82
83 pub fn next_url(&self) -> NextUrl {
84 NextUrl {
85 pathname: self.path.clone(),
86 search: if self.query.is_empty() {
87 String::new()
88 } else {
89 format!(
90 "?{}",
91 self.query
92 .iter()
93 .map(|(k, v)| format!("{}={}", k, v))
94 .collect::<Vec<_>>()
95 .join("&")
96 )
97 },
98 origin: self
99 .headers
100 .get("host")
101 .map(|h| format!("https://{}", h))
102 .unwrap_or_default(),
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
108pub struct NextUrl {
109 pub pathname: String,
110 pub search: String,
111 pub origin: String,
112}
113
114impl NextUrl {
115 pub fn clone_with_pathname(&self, pathname: impl Into<String>) -> Self {
116 Self {
117 pathname: pathname.into(),
118 search: self.search.clone(),
119 origin: self.origin.clone(),
120 }
121 }
122
123 pub fn href(&self) -> String {
124 format!("{}{}{}", self.origin, self.pathname, self.search)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_request_creation() {
134 let req = NextRequest::new("GET", "/api/users?page=1&limit=10");
135
136 assert_eq!(req.method, "GET");
137 assert_eq!(req.path, "/api/users");
138 assert_eq!(req.query_param("page"), Some(&"1".to_string()));
139 assert_eq!(req.query_param("limit"), Some(&"10".to_string()));
140 }
141
142 #[test]
143 fn test_request_headers_cookies() {
144 let req = NextRequest::new("POST", "/login")
145 .with_header("Content-Type", "application/json")
146 .with_cookie("session", "abc123");
147
148 assert_eq!(
149 req.header("Content-Type"),
150 Some(&"application/json".to_string())
151 );
152 assert_eq!(req.cookie("session"), Some(&"abc123".to_string()));
153 }
154
155 #[test]
156 fn test_next_url() {
157 let req = NextRequest::new("GET", "/blog/post?id=123").with_header("host", "example.com");
158
159 let url = req.next_url();
160 assert_eq!(url.pathname, "/blog/post");
161 assert!(url.search.contains("id=123"));
162 assert_eq!(url.origin, "https://example.com");
163 }
164
165 #[test]
166 fn test_next_url_clone_with_pathname() {
167 let url = NextUrl {
168 pathname: "/old".to_string(),
169 search: "?foo=bar".to_string(),
170 origin: "https://test.com".to_string(),
171 };
172
173 let new_url = url.clone_with_pathname("/new");
174 assert_eq!(new_url.pathname, "/new");
175 assert_eq!(new_url.search, "?foo=bar");
176 }
177}