1use std::collections::HashMap;
13use std::sync::OnceLock;
14
15use bytes::Bytes;
16use http::{HeaderMap, Method, Uri, Version};
17use rainier_support::{Error, Extensions, Result};
18use serde::de::DeserializeOwned;
19use serde_json::Value;
20
21use crate::cookie::parse_cookie_header;
22use crate::input;
23use crate::upload::{self, Multipart, UploadedFile};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct ClientIp(pub std::net::IpAddr);
31
32#[derive(Debug)]
34enum ParsedBody {
35 Json(Value),
37 Form(Value),
39 Multipart(Box<Multipart>),
41 Opaque,
46}
47
48#[derive(Debug)]
50pub struct Request {
51 method: Method,
52 uri: Uri,
53 version: Version,
54 headers: HeaderMap,
55 body: Bytes,
56 extensions: Extensions,
57 route_params: HashMap<String, String>,
58 input_override: Option<Value>,
64
65 query: OnceLock<Value>,
66 parsed_body: OnceLock<ParsedBody>,
67 cookies: OnceLock<HashMap<String, String>>,
68}
69
70impl Request {
71 pub fn new(method: Method, uri: Uri, headers: HeaderMap, body: Bytes) -> Self {
74 Self {
75 method,
76 uri,
77 version: Version::HTTP_11,
78 headers,
79 body,
80 extensions: Extensions::new(),
81 route_params: HashMap::new(),
82 input_override: None,
83 query: OnceLock::new(),
84 parsed_body: OnceLock::new(),
85 cookies: OnceLock::new(),
86 }
87 }
88
89 pub fn builder() -> RequestBuilder {
91 RequestBuilder::new()
92 }
93
94 pub fn from_http(request: http::Request<Bytes>) -> Self {
96 let (parts, body) = request.into_parts();
97 let mut request = Self::new(parts.method, parts.uri, parts.headers, body);
98 request.version = parts.version;
99 request
100 }
101
102 pub fn method(&self) -> &Method {
106 &self.method
107 }
108
109 pub fn uri(&self) -> &Uri {
111 &self.uri
112 }
113
114 pub fn path(&self) -> &str {
116 self.uri.path()
117 }
118
119 pub fn query_string(&self) -> &str {
121 self.uri.query().unwrap_or("")
122 }
123
124 pub fn version(&self) -> Version {
126 self.version
127 }
128
129 pub fn headers(&self) -> &HeaderMap {
131 &self.headers
132 }
133
134 pub fn headers_mut(&mut self) -> &mut HeaderMap {
136 &mut self.headers
137 }
138
139 pub fn header(&self, name: &str) -> Option<&str> {
141 self.headers.get(name)?.to_str().ok()
142 }
143
144 pub fn set_method(&mut self, method: Method) {
153 self.method = method;
154 }
155
156 pub fn is_method(&self, method: &Method) -> bool {
158 self.method == method
159 }
160
161 pub fn body(&self) -> &Bytes {
163 &self.body
164 }
165
166 pub fn body_string(&self) -> String {
168 String::from_utf8_lossy(&self.body).into_owned()
169 }
170
171 pub fn content_type(&self) -> Option<String> {
173 let raw = self.header("content-type")?;
174 let base = raw.split(';').next().unwrap_or(raw);
175 Some(base.trim().to_ascii_lowercase())
176 }
177
178 pub fn is_json(&self) -> bool {
181 self.content_type().is_some_and(|ct| ct == "application/json" || ct.ends_with("+json"))
182 }
183
184 pub fn expects_json(&self) -> bool {
190 if self.is_json() {
191 return true;
192 }
193 if self.header("x-requested-with").is_some_and(|v| v.eq_ignore_ascii_case("XMLHttpRequest"))
194 {
195 return true;
196 }
197 match self.header("accept") {
198 Some(accept) => accept.split(',').any(|part| {
201 let part = part.split(';').next().unwrap_or(part).trim();
202 part == "application/json" || part.ends_with("+json")
203 }),
204 None => false,
205 }
206 }
207
208 pub fn query(&self) -> &Value {
212 self.query.get_or_init(|| input::parse_urlencoded(self.query_string().as_bytes()))
213 }
214
215 fn parsed_body(&self) -> &ParsedBody {
216 self.parsed_body.get_or_init(|| {
217 if self.body.is_empty() {
218 return ParsedBody::Opaque;
219 }
220 match self.content_type().as_deref() {
221 Some(ct) if ct == "application/json" || ct.ends_with("+json") => {
222 match serde_json::from_slice(&self.body) {
223 Ok(value) => ParsedBody::Json(value),
224 Err(_) => ParsedBody::Opaque,
225 }
226 }
227 Some("application/x-www-form-urlencoded") => {
228 ParsedBody::Form(input::parse_urlencoded(&self.body))
229 }
230 Some("multipart/form-data") => {
231 let boundary = self.header("content-type").and_then(upload::boundary_of);
232 match boundary.and_then(|b| upload::parse(&self.body, &b).ok()) {
233 Some(parsed) => ParsedBody::Multipart(Box::new(parsed)),
234 None => ParsedBody::Opaque,
235 }
236 }
237 _ => ParsedBody::Opaque,
238 }
239 })
240 }
241
242 pub fn body_input(&self) -> &Value {
244 static EMPTY: OnceLock<Value> = OnceLock::new();
245 if let Some(overridden) = &self.input_override {
246 return overridden;
247 }
248 match self.parsed_body() {
249 ParsedBody::Json(value) => value,
250 ParsedBody::Form(value) => value,
251 ParsedBody::Multipart(parsed) => &parsed.fields,
252 ParsedBody::Opaque => EMPTY.get_or_init(|| Value::Object(serde_json::Map::new())),
253 }
254 }
255
256 pub fn all(&self) -> Value {
263 match &self.input_override {
264 Some(overridden) => overridden.clone(),
265 None => input::merge(self.query().clone(), self.body_input().clone()),
266 }
267 }
268
269 pub fn input(&self, key: &str) -> Option<String> {
271 if let Some(overridden) = &self.input_override {
272 return input::lookup(overridden, key).and_then(input::scalar_to_string);
273 }
274 input::lookup(self.body_input(), key)
275 .or_else(|| input::lookup(self.query(), key))
276 .and_then(input::scalar_to_string)
277 }
278
279 pub fn transform_input(&mut self, transform: impl FnOnce(Value) -> Value) {
285 self.input_override = Some(transform(self.all()));
286 }
287
288 pub fn set_input(&mut self, value: Value) {
290 self.input_override = Some(value);
291 }
292
293 pub fn merge_input(&mut self, extra: Value) {
297 self.transform_input(|current| input::merge(current, extra));
298 }
299
300 pub fn input_or(&self, key: &str, default: impl Into<String>) -> String {
302 self.input(key).unwrap_or_else(|| default.into())
303 }
304
305 pub fn input_value(&self, key: &str) -> Option<&Value> {
307 if let Some(overridden) = &self.input_override {
308 return input::lookup(overridden, key);
309 }
310 input::lookup(self.body_input(), key).or_else(|| input::lookup(self.query(), key))
311 }
312
313 pub fn has(&self, key: &str) -> bool {
315 self.input_value(key).is_some()
316 }
317
318 pub fn input_was_rewritten(&self) -> bool {
320 self.input_override.is_some()
321 }
322
323 pub fn filled(&self, key: &str) -> bool {
325 match self.input_value(key) {
326 Some(Value::String(s)) => !s.trim().is_empty(),
327 Some(Value::Null) | None => false,
328 Some(Value::Array(items)) => !items.is_empty(),
329 Some(Value::Object(map)) => !map.is_empty(),
330 Some(_) => true,
331 }
332 }
333
334 pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
339 if self.body.is_empty() {
340 return Err(Error::bad_request("a JSON body is required but the request had none"));
341 }
342 if !self.is_json() {
343 return Err(Error::bad_request(format!(
344 "expected a JSON body but the Content-Type was `{}`",
345 self.content_type().unwrap_or_else(|| "(none)".into())
346 )));
347 }
348 serde_json::from_slice(&self.body)
349 .map_err(|e| Error::bad_request(format!("malformed JSON body: {e}")))
350 }
351
352 pub fn form<T: DeserializeOwned>(&self) -> Result<T> {
358 serde_json::from_value(self.all())
359 .map_err(|e| Error::bad_request(format!("could not read the request input: {e}")))
360 }
361
362 pub fn query_as<T: DeserializeOwned>(&self) -> Result<T> {
364 serde_json::from_value(self.query().clone())
365 .map_err(|e| Error::bad_request(format!("could not read the query string: {e}")))
366 }
367
368 pub fn files(&self, field: &str) -> &[UploadedFile] {
372 match self.parsed_body() {
373 ParsedBody::Multipart(parsed) => {
374 parsed.files.get(field).map(Vec::as_slice).unwrap_or(&[])
375 }
376 _ => &[],
377 }
378 }
379
380 pub fn file(&self, field: &str) -> Option<&UploadedFile> {
382 self.files(field).first()
383 }
384
385 pub fn has_file(&self, field: &str) -> bool {
387 self.file(field).is_some_and(|f| !f.is_empty())
388 }
389
390 pub fn cookies(&self) -> &HashMap<String, String> {
394 self.cookies.get_or_init(|| match self.header("cookie") {
395 Some(header) => parse_cookie_header(header),
396 None => HashMap::new(),
397 })
398 }
399
400 pub fn cookie(&self, name: &str) -> Option<&str> {
402 self.cookies().get(name).map(String::as_str)
403 }
404
405 pub fn bearer_token(&self) -> Option<&str> {
407 let header = self.header("authorization")?;
408 let (scheme, token) = header.split_once(' ')?;
409 scheme.eq_ignore_ascii_case("bearer").then(|| token.trim())
410 }
411
412 pub fn route_param(&self, name: &str) -> Option<&str> {
416 self.route_params.get(name).map(String::as_str)
417 }
418
419 pub fn route_params(&self) -> &HashMap<String, String> {
421 &self.route_params
422 }
423
424 pub fn set_route_params(&mut self, params: HashMap<String, String>) {
426 self.route_params = params;
427 }
428
429 pub fn extensions(&self) -> &Extensions {
434 &self.extensions
435 }
436
437 pub fn extensions_mut(&mut self) -> &mut Extensions {
439 &mut self.extensions
440 }
441
442 pub fn with_extension<T: Send + Sync + 'static>(mut self, value: T) -> Self {
444 self.extensions.insert(value);
445 self
446 }
447
448 pub fn extension<T: Send + Sync + 'static>(&self) -> Option<&T> {
450 self.extensions.get::<T>()
451 }
452
453 pub fn ip(&self) -> Option<std::net::IpAddr> {
455 self.extensions.get::<ClientIp>().map(|ip| ip.0)
456 }
457}
458
459#[derive(Debug)]
474pub struct RequestBuilder {
475 method: Method,
476 uri: String,
477 headers: HeaderMap,
478 body: Bytes,
479 route_params: HashMap<String, String>,
480}
481
482impl Default for RequestBuilder {
483 fn default() -> Self {
484 Self::new()
485 }
486}
487
488impl RequestBuilder {
489 pub fn new() -> Self {
491 Self {
492 method: Method::GET,
493 uri: "/".to_string(),
494 headers: HeaderMap::new(),
495 body: Bytes::new(),
496 route_params: HashMap::new(),
497 }
498 }
499
500 pub fn method(mut self, method: Method) -> Self {
502 self.method = method;
503 self
504 }
505
506 pub fn uri(mut self, uri: impl Into<String>) -> Self {
508 self.uri = uri.into();
509 self
510 }
511
512 pub fn header(mut self, name: &str, value: &str) -> Self {
514 if let (Ok(name), Ok(value)) =
515 (http::header::HeaderName::try_from(name), http::header::HeaderValue::from_str(value))
516 {
517 self.headers.insert(name, value);
518 }
519 self
520 }
521
522 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
524 self.body = body.into();
525 self
526 }
527
528 pub fn json(self, value: &impl serde::Serialize) -> Self {
530 let encoded = serde_json::to_vec(value).unwrap_or_default();
531 self.header("content-type", "application/json").body(encoded)
532 }
533
534 pub fn form(self, pairs: &[(&str, &str)]) -> Self {
536 let encoded: String =
537 form_urlencoded::Serializer::new(String::new()).extend_pairs(pairs).finish();
538 self.header("content-type", "application/x-www-form-urlencoded").body(encoded)
539 }
540
541 pub fn route_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
543 self.route_params.insert(name.into(), value.into());
544 self
545 }
546
547 pub fn build(self) -> Request {
549 let uri: Uri = self.uri.parse().unwrap_or_else(|_| Uri::from_static("/"));
550 let mut request = Request::new(self.method, uri, self.headers, self.body);
551 request.route_params = self.route_params;
552 request
553 }
554}
555
556#[cfg(test)]
557mod input_rewriting_tests {
558 use super::*;
559 use serde_json::json;
560
561 #[test]
562 fn transform_input_replaces_what_later_reads_see() {
563 let mut request = Request::builder().uri("/x?name=+ada+&age=36").build();
564 assert_eq!(request.input("name").as_deref(), Some(" ada "));
565
566 request.transform_input(|value| {
567 let mut object = value.as_object().cloned().unwrap_or_default();
568 if let Some(Value::String(name)) = object.get_mut("name") {
569 *name = name.trim().to_string();
570 }
571 Value::Object(object)
572 });
573
574 assert!(request.input_was_rewritten());
575 assert_eq!(request.input("name").as_deref(), Some("ada"));
576 assert_eq!(request.all(), json!({ "name": "ada", "age": "36" }));
577 }
578
579 #[test]
580 fn a_rewrite_is_not_undone_by_the_original_query_string() {
581 let mut request = Request::builder().uri("/x?secret=leak").build();
584 request.set_input(json!({}));
585 assert_eq!(request.input("secret"), None);
586 assert!(!request.has("secret"));
587 }
588
589 #[test]
590 fn merge_input_layers_over_the_current_values() {
591 let mut request = Request::builder().uri("/x?a=1").build();
592 request.merge_input(json!({ "b": "2" }));
593 assert_eq!(request.all(), json!({ "a": "1", "b": "2" }));
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use serde_json::json;
601
602 #[test]
603 fn exposes_the_message_basics() {
604 let request = Request::builder().method(Method::POST).uri("/posts?page=2").build();
605 assert_eq!(request.method(), Method::POST);
606 assert_eq!(request.path(), "/posts");
607 assert_eq!(request.query_string(), "page=2");
608 assert!(request.is_method(&Method::POST));
609 }
610
611 #[test]
612 fn reads_query_string_input() {
613 let request = Request::builder().uri("/search?q=rust&tags[]=a&tags[]=b").build();
614 assert_eq!(request.input("q").as_deref(), Some("rust"));
615 assert_eq!(request.input_value("tags"), Some(&json!(["a", "b"])));
616 }
617
618 #[test]
619 fn reads_json_body_input() {
620 let request = Request::builder()
621 .method(Method::POST)
622 .json(&json!({ "title": "Hi", "meta": { "tag": "rust" } }))
623 .build();
624
625 assert_eq!(request.input("title").as_deref(), Some("Hi"));
626 assert_eq!(request.input("meta.tag").as_deref(), Some("rust"));
627 assert!(request.is_json());
628 }
629
630 #[test]
631 fn reads_form_body_input() {
632 let request = Request::builder()
633 .method(Method::POST)
634 .form(&[("title", "Hi"), ("body", "there")])
635 .build();
636 assert_eq!(request.input("title").as_deref(), Some("Hi"));
637 assert_eq!(request.input("body").as_deref(), Some("there"));
638 }
639
640 #[test]
641 fn the_body_wins_over_the_query_string() {
642 let request = Request::builder()
643 .method(Method::POST)
644 .uri("/x?name=query")
645 .json(&json!({ "name": "body" }))
646 .build();
647 assert_eq!(request.input("name").as_deref(), Some("body"));
648 assert_eq!(request.all(), json!({ "name": "body" }));
649 }
650
651 #[test]
652 fn all_merges_query_and_body() {
653 let request = Request::builder()
654 .method(Method::POST)
655 .uri("/x?page=2")
656 .json(&json!({ "name": "ada" }))
657 .build();
658 assert_eq!(request.all(), json!({ "page": "2", "name": "ada" }));
659 }
660
661 #[test]
662 fn route_params_stay_out_of_input() {
663 let request = Request::builder().uri("/posts/7").route_param("post", "7").build();
664 assert_eq!(request.route_param("post"), Some("7"));
665 assert!(!request.has("post"), "route params must not shadow or appear as input");
666 }
667
668 #[test]
669 fn has_and_filled_differ_on_blank_values() {
670 let request = Request::builder().uri("/x?a=&b=v&c[]=").build();
671 assert!(request.has("a"));
672 assert!(!request.filled("a"));
673 assert!(request.filled("b"));
674 assert!(!request.has("missing"));
675 }
676
677 #[test]
678 fn json_reports_why_it_failed() {
679 let empty = Request::builder().build();
680 assert!(empty.json::<Value>().unwrap_err().message().contains("had none"));
681
682 let wrong_type = Request::builder().body("hello").build();
683 assert!(wrong_type.json::<Value>().unwrap_err().message().contains("Content-Type"));
684
685 let malformed =
686 Request::builder().header("content-type", "application/json").body("{oops").build();
687 let err = malformed.json::<Value>().unwrap_err();
688 assert_eq!(err.status(), 400);
689 assert!(err.message().contains("malformed JSON"));
690 }
691
692 #[test]
693 fn a_malformed_body_does_not_break_unrelated_accessors() {
694 let request = Request::builder()
695 .header("content-type", "application/json")
696 .uri("/x?page=2")
697 .body("{oops")
698 .build();
699 assert_eq!(request.input("page").as_deref(), Some("2"));
701 assert_eq!(request.body_input(), &json!({}));
702 }
703
704 #[test]
705 fn detects_json_content_types_including_suffixes() {
706 let plain =
707 Request::builder().header("content-type", "application/json; charset=utf-8").build();
708 assert!(plain.is_json());
709 assert_eq!(plain.content_type().as_deref(), Some("application/json"));
710
711 let suffixed = Request::builder().header("content-type", "application/ld+json").build();
712 assert!(suffixed.is_json());
713
714 let html = Request::builder().header("content-type", "text/html").build();
715 assert!(!html.is_json());
716 }
717
718 #[test]
719 fn expects_json_reads_the_accept_header() {
720 let asked = Request::builder().header("accept", "application/json").build();
721 assert!(asked.expects_json());
722
723 let xhr = Request::builder().header("x-requested-with", "XMLHttpRequest").build();
724 assert!(xhr.expects_json());
725
726 let browser = Request::builder()
727 .header("accept", "text/html,application/xhtml+xml,*/*;q=0.8")
728 .build();
729 assert!(!browser.expects_json(), "`*/*` must not count as wanting JSON");
730
731 assert!(!Request::builder().build().expects_json());
732 }
733
734 #[test]
735 fn reads_cookies() {
736 let request = Request::builder().header("cookie", "session=abc; theme=dark").build();
737 assert_eq!(request.cookie("session"), Some("abc"));
738 assert_eq!(request.cookie("nope"), None);
739 assert_eq!(request.cookies().len(), 2);
740 }
741
742 #[test]
743 fn reads_a_bearer_token() {
744 let request = Request::builder().header("authorization", "Bearer tok_123").build();
745 assert_eq!(request.bearer_token(), Some("tok_123"));
746
747 let lower = Request::builder().header("authorization", "bearer tok_123").build();
749 assert_eq!(lower.bearer_token(), Some("tok_123"));
750
751 let basic = Request::builder().header("authorization", "Basic abc").build();
752 assert_eq!(basic.bearer_token(), None);
753 assert_eq!(Request::builder().build().bearer_token(), None);
754 }
755
756 #[test]
757 fn extensions_carry_typed_attributes() {
758 #[derive(Debug, PartialEq)]
759 struct CurrentUser(u64);
760
761 let request = Request::builder().build().with_extension(CurrentUser(7));
762 assert_eq!(request.extension::<CurrentUser>(), Some(&CurrentUser(7)));
763 assert_eq!(request.extension::<ClientIp>(), None);
764 }
765
766 #[test]
767 fn deserialises_the_json_body_into_a_struct() {
768 #[derive(serde::Deserialize, PartialEq, Debug)]
769 struct NewPost {
770 title: String,
771 draft: bool,
772 }
773
774 let request = Request::builder()
775 .method(Method::POST)
776 .json(&json!({ "title": "Hi", "draft": true }))
777 .build();
778 assert_eq!(request.json::<NewPost>().unwrap(), NewPost { title: "Hi".into(), draft: true });
779 }
780
781 #[test]
782 fn parses_uploads_from_a_multipart_body() {
783 let boundary = "X";
784 let body = format!(
785 "--{boundary}\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\nHi\r\n\
786 --{boundary}\r\nContent-Disposition: form-data; name=\"doc\"; filename=\"a.txt\"\r\n\r\nDATA\r\n\
787 --{boundary}--\r\n"
788 );
789 let request = Request::builder()
790 .method(Method::POST)
791 .header("content-type", &format!("multipart/form-data; boundary={boundary}"))
792 .body(body)
793 .build();
794
795 assert_eq!(request.input("title").as_deref(), Some("Hi"));
796 assert!(request.has_file("doc"));
797 assert_eq!(request.file("doc").unwrap().bytes().as_ref(), b"DATA");
798 assert!(!request.has_file("missing"));
799 assert!(request.files("missing").is_empty());
800 }
801
802 #[test]
803 fn parsing_is_cached_across_calls() {
804 let request = Request::builder().uri("/x?a=1").build();
805 let first = request.query() as *const Value;
806 let second = request.query() as *const Value;
807 assert_eq!(first, second, "the query string must be parsed once");
808 }
809
810 #[test]
811 fn adopts_an_http_request() {
812 let inner = http::Request::builder()
813 .method(Method::PUT)
814 .uri("/things/1")
815 .header("x-test", "yes")
816 .body(Bytes::from("payload"))
817 .unwrap();
818
819 let request = Request::from_http(inner);
820 assert_eq!(request.method(), Method::PUT);
821 assert_eq!(request.path(), "/things/1");
822 assert_eq!(request.header("x-test"), Some("yes"));
823 assert_eq!(request.body_string(), "payload");
824 }
825}