1use super::header::{
2 accepts_event_stream_response, accepts_json_response, get_header, is_json_media_type,
3 matches_media_type, normalize_header_name, normalize_headers, parse_content_length,
4 parse_cookie_header_values, strict_content_length_values, validate_header_name,
5 validate_header_value,
6};
7use super::method::HttpMethod;
8use super::query::{parse_query, parse_query_pairs, split_path_query};
9use crate::percent::validate_percent_encoding;
10use crate::routing::host::normalize_host_header;
11#[cfg(feature = "auth")]
12use crate::AuthPrincipal;
13use crate::{validate_value, BootError, ModuleRef, ProviderToken, Result, Validate};
14use serde::{de::DeserializeOwned, Serialize};
15use std::collections::BTreeMap;
16use std::fmt;
17use std::str::FromStr;
18use std::sync::Arc;
19#[cfg(feature = "auth")]
20use std::sync::RwLock;
21
22#[derive(Debug, Clone)]
24pub struct BootRequest {
25 pub method: HttpMethod,
26 pub path: String,
27 pub query_string: Option<String>,
28 pub query: BTreeMap<String, String>,
29 pub params: BTreeMap<String, String>,
30 pub host_params: BTreeMap<String, String>,
31 pub headers: BTreeMap<String, String>,
32 pub appended_headers: Vec<(String, String)>,
33 pub body: Vec<u8>,
34 module_ref: Option<ModuleRef>,
35 #[cfg(feature = "auth")]
36 auth_principal: Arc<RwLock<Option<AuthPrincipal>>>,
37}
38
39impl PartialEq for BootRequest {
40 fn eq(&self, other: &Self) -> bool {
41 self.method == other.method
42 && self.path == other.path
43 && self.query_string == other.query_string
44 && self.query == other.query
45 && self.params == other.params
46 && self.host_params == other.host_params
47 && self.headers == other.headers
48 && self.appended_headers == other.appended_headers
49 && self.body == other.body
50 }
51}
52
53impl Eq for BootRequest {}
54
55impl BootRequest {
56 pub fn new(method: HttpMethod, path: impl Into<String>) -> Self {
57 let (path, query_string, query) = split_path_query(path.into());
58 Self {
59 method,
60 path,
61 query_string,
62 query,
63 params: BTreeMap::new(),
64 host_params: BTreeMap::new(),
65 headers: BTreeMap::new(),
66 appended_headers: Vec::new(),
67 body: Vec::new(),
68 module_ref: None,
69 #[cfg(feature = "auth")]
70 auth_principal: Arc::new(RwLock::new(None)),
71 }
72 }
73
74 pub fn method(&self) -> HttpMethod {
75 self.method
76 }
77
78 pub fn path(&self) -> &str {
79 &self.path
80 }
81
82 pub fn query_string(&self) -> Option<&str> {
83 self.query_string.as_deref()
84 }
85
86 pub fn with_query_string(mut self, query_string: impl Into<String>) -> Self {
87 let query_string = query_string.into();
88 self.query = parse_query(&query_string);
89 self.query_string = Some(query_string);
90 self
91 }
92
93 pub(crate) fn with_matched_path(mut self, path: impl Into<String>) -> Self {
94 self.path = path.into();
95 self
96 }
97
98 pub(crate) fn with_module_ref(mut self, module_ref: ModuleRef) -> Self {
99 self.module_ref = Some(module_ref);
100 self
101 }
102
103 pub fn module_ref(&self) -> Option<&ModuleRef> {
104 self.module_ref.as_ref()
105 }
106
107 pub fn get<T>(&self) -> Result<Arc<T>>
108 where
109 T: Send + Sync + 'static,
110 {
111 self.module_ref
112 .as_ref()
113 .ok_or_else(|| BootError::MissingProvider(ProviderToken::of::<T>().to_string()))?
114 .get::<T>()
115 }
116
117 pub fn get_named<T>(&self, token: &str) -> Result<Arc<T>>
118 where
119 T: Send + Sync + 'static,
120 {
121 self.module_ref
122 .as_ref()
123 .ok_or_else(|| BootError::MissingProvider(ProviderToken::named(token).to_string()))?
124 .get_named::<T>(token)
125 }
126
127 pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>>
128 where
129 T: Send + Sync + 'static,
130 {
131 match &self.module_ref {
132 Some(module_ref) => module_ref.get_optional::<T>(),
133 None => Ok(None),
134 }
135 }
136
137 pub fn get_optional_named<T>(&self, token: &str) -> Result<Option<Arc<T>>>
138 where
139 T: Send + Sync + 'static,
140 {
141 match &self.module_ref {
142 Some(module_ref) => module_ref.get_optional_named::<T>(token),
143 None => Ok(None),
144 }
145 }
146
147 #[cfg(feature = "auth")]
148 pub fn with_auth_principal(mut self, principal: AuthPrincipal) -> Self {
149 self.auth_principal = Arc::new(RwLock::new(Some(principal)));
150 self
151 }
152
153 #[cfg(feature = "auth")]
154 pub fn set_auth_principal(&self, principal: AuthPrincipal) -> Result<()> {
155 *self
156 .auth_principal
157 .write()
158 .map_err(|_| BootError::Internal("auth principal lock is poisoned".to_string()))? =
159 Some(principal);
160 Ok(())
161 }
162
163 #[cfg(feature = "auth")]
164 pub fn clear_auth_principal(&self) -> Result<()> {
165 *self
166 .auth_principal
167 .write()
168 .map_err(|_| BootError::Internal("auth principal lock is poisoned".to_string()))? =
169 None;
170 Ok(())
171 }
172
173 #[cfg(feature = "auth")]
174 pub fn auth_principal(&self) -> Result<Option<AuthPrincipal>> {
175 Ok(self
176 .auth_principal
177 .read()
178 .map_err(|_| BootError::Internal("auth principal lock is poisoned".to_string()))?
179 .clone())
180 }
181
182 #[cfg(feature = "auth")]
183 pub fn require_auth_principal(&self) -> Result<AuthPrincipal> {
184 self.auth_principal()?
185 .ok_or_else(|| BootError::Unauthorized("missing authenticated principal".to_string()))
186 }
187
188 pub fn with_path_params(mut self, params: BTreeMap<String, String>) -> Self {
189 self.params = params;
190 self
191 }
192
193 pub fn with_host_params(mut self, params: BTreeMap<String, String>) -> Self {
194 self.host_params = params;
195 self
196 }
197
198 pub fn with_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
199 self.params.insert(name.into(), value.into());
200 self
201 }
202
203 pub fn with_host_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
204 self.host_params.insert(name.into(), value.into());
205 self
206 }
207
208 pub fn with_query_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
209 self.query.insert(name.into(), value.into());
210 self.query_string = None;
211 self
212 }
213
214 pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
215 self.body = body.into();
216 self
217 }
218
219 pub fn body(&self) -> &[u8] {
220 &self.body
221 }
222
223 pub fn into_body(self) -> Vec<u8> {
224 self.body
225 }
226
227 pub fn with_text(self, body: impl Into<String>) -> Self {
228 self.with_body(body.into())
229 .with_header("content-type", "text/plain; charset=utf-8")
230 }
231
232 pub fn with_json<T>(self, body: &T) -> Result<Self>
233 where
234 T: Serialize,
235 {
236 let body = serde_json::to_vec(body).map_err(|err| BootError::Internal(err.to_string()))?;
237 Ok(self
238 .with_body(body)
239 .with_header("content-type", "application/json"))
240 }
241
242 pub fn with_content_type(self, content_type: impl Into<String>) -> Self {
243 self.with_header("content-type", content_type)
244 }
245
246 pub fn with_content_length(self, content_length: u64) -> Self {
247 self.with_header("content-length", content_length.to_string())
248 }
249
250 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
251 self.headers
252 .insert(normalize_header_name(name), value.into());
253 self
254 }
255
256 pub fn with_headers(mut self, headers: BTreeMap<String, String>) -> Self {
257 self.headers = normalize_headers(headers);
258 self
259 }
260
261 pub fn append_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
262 self.appended_headers
263 .push((normalize_header_name(name), value.into()));
264 self
265 }
266
267 pub fn header_entries(&self) -> impl Iterator<Item = (&str, &str)> {
268 self.headers
269 .iter()
270 .map(|(name, value)| (name.as_str(), value.as_str()))
271 .chain(
272 self.appended_headers
273 .iter()
274 .map(|(name, value)| (name.as_str(), value.as_str())),
275 )
276 }
277
278 pub fn validate_headers(&self) -> Result<()> {
279 for (name, value) in self.header_entries() {
280 validate_request_header(name, value)?;
281 }
282
283 Ok(())
284 }
285
286 pub fn text(&self) -> Result<String> {
287 String::from_utf8(self.body.clone()).map_err(|err| BootError::BadRequest(err.to_string()))
288 }
289
290 pub fn param(&self, name: &str) -> Option<&str> {
291 self.params.get(name).map(String::as_str)
292 }
293
294 pub fn param_as<T>(&self, name: &str) -> Result<T>
295 where
296 T: FromStr,
297 T::Err: fmt::Display,
298 {
299 parse_required_value(
300 self.param(name).map(ToString::to_string),
301 "path parameter",
302 name,
303 )
304 }
305
306 pub fn optional_param_as<T>(&self, name: &str) -> Result<Option<T>>
307 where
308 T: FromStr,
309 T::Err: fmt::Display,
310 {
311 parse_optional_value(
312 self.param(name).map(ToString::to_string),
313 "path parameter",
314 name,
315 )
316 }
317
318 pub fn host_param(&self, name: &str) -> Option<&str> {
319 self.host_params.get(name).map(String::as_str)
320 }
321
322 pub fn host_param_as<T>(&self, name: &str) -> Result<T>
323 where
324 T: FromStr,
325 T::Err: fmt::Display,
326 {
327 parse_required_value(
328 self.host_param(name).map(ToString::to_string),
329 "host parameter",
330 name,
331 )
332 }
333
334 pub fn optional_host_param_as<T>(&self, name: &str) -> Result<Option<T>>
335 where
336 T: FromStr,
337 T::Err: fmt::Display,
338 {
339 parse_optional_value(
340 self.host_param(name).map(ToString::to_string),
341 "host parameter",
342 name,
343 )
344 }
345
346 pub fn params<T>(&self) -> Result<T>
347 where
348 T: DeserializeOwned,
349 {
350 let params = serde_urlencoded::to_string(&self.params)
351 .map_err(|err| BootError::BadRequest(err.to_string()))?;
352 serde_urlencoded::from_str(¶ms).map_err(|err| BootError::BadRequest(err.to_string()))
353 }
354
355 pub fn validated_params<T>(&self) -> Result<T>
356 where
357 T: DeserializeOwned + Validate,
358 {
359 let value = self.params()?;
360 validate_value(value)
361 }
362
363 pub fn host_params<T>(&self) -> Result<T>
364 where
365 T: DeserializeOwned,
366 {
367 let params = serde_urlencoded::to_string(&self.host_params)
368 .map_err(|err| BootError::BadRequest(err.to_string()))?;
369 serde_urlencoded::from_str(¶ms).map_err(|err| BootError::BadRequest(err.to_string()))
370 }
371
372 pub fn validated_host_params<T>(&self) -> Result<T>
373 where
374 T: DeserializeOwned + Validate,
375 {
376 let value = self.host_params()?;
377 validate_value(value)
378 }
379
380 pub fn query_param(&self, name: &str) -> Option<&str> {
381 self.query.get(name).map(String::as_str)
382 }
383
384 pub fn query_value(&self, name: &str) -> Result<Option<String>> {
385 Ok(self
386 .query_pairs()?
387 .into_iter()
388 .find_map(|(key, value)| (key == name).then_some(value)))
389 }
390
391 pub fn query_value_as<T>(&self, name: &str) -> Result<T>
392 where
393 T: FromStr,
394 T::Err: fmt::Display,
395 {
396 parse_required_value(self.query_value(name)?, "query parameter", name)
397 }
398
399 pub fn optional_query_value_as<T>(&self, name: &str) -> Result<Option<T>>
400 where
401 T: FromStr,
402 T::Err: fmt::Display,
403 {
404 parse_optional_value(self.query_value(name)?, "query parameter", name)
405 }
406
407 pub fn query_values(&self, name: &str) -> Result<Vec<String>> {
408 Ok(self
409 .query_pairs()?
410 .into_iter()
411 .filter_map(|(key, value)| (key == name).then_some(value))
412 .collect())
413 }
414
415 pub fn query_values_as<T>(&self, name: &str) -> Result<Vec<T>>
416 where
417 T: FromStr,
418 T::Err: fmt::Display,
419 {
420 self.query_values(name)?
421 .into_iter()
422 .map(|value| parse_value(value, "query parameter", name))
423 .collect()
424 }
425
426 pub fn query_pairs(&self) -> Result<Vec<(String, String)>> {
427 match self.query_string.as_deref() {
428 Some(query) => parse_query_pairs(query),
429 None => Ok(self
430 .query
431 .iter()
432 .map(|(key, value)| (key.clone(), value.clone()))
433 .collect()),
434 }
435 }
436
437 pub fn header(&self, name: &str) -> Option<&str> {
438 get_header(&self.headers, name)
439 }
440
441 pub fn header_as<T>(&self, name: &str) -> Result<T>
442 where
443 T: FromStr,
444 T::Err: fmt::Display,
445 {
446 parse_required_value(self.header(name).map(ToString::to_string), "header", name)
447 }
448
449 pub fn optional_header_as<T>(&self, name: &str) -> Result<Option<T>>
450 where
451 T: FromStr,
452 T::Err: fmt::Display,
453 {
454 parse_optional_value(self.header(name).map(ToString::to_string), "header", name)
455 }
456
457 pub fn host(&self) -> Option<&str> {
458 self.header("host").and_then(normalize_host_header)
459 }
460
461 pub fn ip(&self) -> Option<String> {
462 self.forwarded_for_ip()
463 .or_else(|| self.forwarded_header_ip("x-forwarded-for"))
464 .or_else(|| self.forwarded_header_ip("x-real-ip"))
465 }
466
467 pub fn ip_as<T>(&self) -> Result<T>
468 where
469 T: FromStr,
470 T::Err: fmt::Display,
471 {
472 parse_required_value(self.ip(), "IP address", "ip")
473 }
474
475 pub fn optional_ip_as<T>(&self) -> Result<Option<T>>
476 where
477 T: FromStr,
478 T::Err: fmt::Display,
479 {
480 parse_optional_value(self.ip(), "IP address", "ip")
481 }
482
483 pub fn header_values(&self, name: &str) -> Vec<&str> {
484 let mut values = self.header(name).into_iter().collect::<Vec<_>>();
485 values.extend(
486 self.appended_headers
487 .iter()
488 .filter(|(key, _)| key.eq_ignore_ascii_case(name))
489 .map(|(_, value)| value.as_str()),
490 );
491 values
492 }
493
494 pub fn authorization(&self) -> Option<&str> {
495 self.header_values("authorization").into_iter().next()
496 }
497
498 fn forwarded_header_ip(&self, name: &str) -> Option<String> {
499 self.header_values(name)
500 .into_iter()
501 .flat_map(|value| value.split(','))
502 .map(str::trim)
503 .find(|value| !value.is_empty())
504 .map(ToString::to_string)
505 }
506
507 fn forwarded_for_ip(&self) -> Option<String> {
508 self.header_values("forwarded")
509 .into_iter()
510 .flat_map(|value| value.split(','))
511 .flat_map(|entry| entry.split(';'))
512 .filter_map(|part| part.trim().split_once('='))
513 .find_map(|(key, value)| {
514 key.trim().eq_ignore_ascii_case("for").then(|| {
515 value
516 .trim()
517 .trim_matches('"')
518 .trim_matches(['[', ']'])
519 .to_string()
520 })
521 })
522 .filter(|value| !value.is_empty())
523 }
524
525 pub fn bearer_token(&self) -> Option<&str> {
526 let authorization = self.authorization()?.trim();
527 let mut parts = authorization.splitn(2, char::is_whitespace);
528 let scheme = parts.next()?;
529 let token = parts.next()?.trim();
530
531 if scheme.eq_ignore_ascii_case("bearer") && !token.is_empty() {
532 Some(token)
533 } else {
534 None
535 }
536 }
537
538 pub fn require_bearer_token(&self) -> Result<&str> {
539 self.bearer_token()
540 .ok_or_else(|| BootError::Unauthorized("missing bearer token".to_string()))
541 }
542
543 pub fn cookie_pairs(&self) -> Result<Vec<(String, String)>> {
544 parse_cookie_header_values(&self.header_values("cookie"))
545 }
546
547 pub fn cookie(&self, name: &str) -> Result<Option<String>> {
548 Ok(self
549 .cookie_pairs()?
550 .into_iter()
551 .find_map(|(key, value)| (key == name).then_some(value)))
552 }
553
554 pub fn require_cookie(&self, name: &str) -> Result<String> {
555 self.cookie(name)?
556 .ok_or_else(|| BootError::Unauthorized(format!("missing cookie: {name}")))
557 }
558
559 pub fn cookie_values(&self, name: &str) -> Result<Vec<String>> {
560 Ok(self
561 .cookie_pairs()?
562 .into_iter()
563 .filter_map(|(key, value)| (key == name).then_some(value))
564 .collect())
565 }
566
567 pub fn cookies(&self) -> Result<BTreeMap<String, String>> {
568 let mut cookies = BTreeMap::new();
569 for (name, value) in self.cookie_pairs()? {
570 cookies.entry(name).or_insert(value);
571 }
572 Ok(cookies)
573 }
574
575 pub fn content_type(&self) -> Option<&str> {
576 self.header_values("content-type").into_iter().next()
577 }
578
579 pub fn content_length(&self) -> Result<Option<u64>> {
580 let Some(content_length) = self.header_values("content-length").into_iter().next() else {
581 return Ok(None);
582 };
583
584 parse_content_length(content_length)
585 .map(Some)
586 .ok_or_else(|| {
587 BootError::BadRequest(format!("invalid content-length header: {content_length}"))
588 })
589 }
590
591 pub fn strict_content_length(&self) -> Result<Option<u64>> {
592 strict_content_length_values(
593 self.header_values("content-length"),
594 |content_length| {
595 BootError::BadRequest(format!("invalid content-length header: {content_length}"))
596 },
597 |expected_content_length, content_length| {
598 BootError::BadRequest(format!(
599 "conflicting content-length headers: {expected_content_length} != {content_length}"
600 ))
601 },
602 )
603 }
604
605 pub fn validate_content_length(&self) -> Result<()> {
606 let Some(content_length) = self.strict_content_length()? else {
607 return Ok(());
608 };
609 let actual_body_length = self.body.len() as u64;
610 if actual_body_length == content_length {
611 return Ok(());
612 }
613
614 Err(BootError::BadRequest(format!(
615 "content-length header does not match request body length: expected {content_length}, got {actual_body_length}"
616 )))
617 }
618
619 pub fn validate_body_limit(&self, body_limit: usize) -> Result<()> {
620 if self
621 .strict_content_length()?
622 .is_some_and(|content_length| content_length > body_limit as u64)
623 || self.body.len() > body_limit
624 {
625 return Err(BootError::PayloadTooLarge(format!(
626 "request body exceeds {body_limit} bytes"
627 )));
628 }
629
630 Ok(())
631 }
632
633 pub fn validate(&self) -> Result<()> {
634 self.validate_headers()?;
635 self.validate_content_length()
636 }
637
638 pub fn validate_with_body_limit(&self, body_limit: usize) -> Result<()> {
639 self.validate_headers()?;
640 self.validate_body_limit(body_limit)?;
641 self.validate_content_length()
642 }
643
644 pub fn is_content_type(&self, media_type: &str) -> bool {
645 self.content_type()
646 .is_some_and(|content_type| matches_media_type(content_type, media_type))
647 }
648
649 pub fn is_json_content_type(&self) -> bool {
650 self.content_type().is_some_and(is_json_media_type)
651 }
652
653 pub fn require_json_content_type(&self) -> Result<()> {
654 if self.is_json_content_type() {
655 return Ok(());
656 }
657
658 let message = match self.content_type() {
659 Some(content_type) => format!("expected JSON content type, got {content_type}"),
660 None => "expected JSON content type".to_string(),
661 };
662 Err(BootError::UnsupportedMediaType(message))
663 }
664
665 pub fn accepts_json(&self) -> bool {
666 accepts_json_response(&self.header_values("accept"))
667 }
668
669 pub fn require_accepts_json(&self) -> Result<()> {
670 if self.accepts_json() {
671 return Ok(());
672 }
673
674 Err(BootError::NotAcceptable(
675 "expected client to accept JSON response".to_string(),
676 ))
677 }
678
679 pub fn accepts_event_stream(&self) -> bool {
680 accepts_event_stream_response(&self.header_values("accept"))
681 }
682
683 pub fn require_accepts_event_stream(&self) -> Result<()> {
684 if self.accepts_event_stream() {
685 return Ok(());
686 }
687
688 Err(BootError::NotAcceptable(
689 "expected client to accept text/event-stream response".to_string(),
690 ))
691 }
692
693 pub fn json<T>(&self) -> Result<T>
694 where
695 T: DeserializeOwned,
696 {
697 serde_json::from_slice(&self.body).map_err(|err| BootError::BadRequest(err.to_string()))
698 }
699
700 pub fn validated_json<T>(&self) -> Result<T>
701 where
702 T: DeserializeOwned + Validate,
703 {
704 let value = self.json()?;
705 validate_value(value)
706 }
707
708 pub fn json_with_content_type<T>(&self) -> Result<T>
709 where
710 T: DeserializeOwned,
711 {
712 self.require_json_content_type()?;
713 self.json()
714 }
715
716 pub fn validated_json_with_content_type<T>(&self) -> Result<T>
717 where
718 T: DeserializeOwned + Validate,
719 {
720 self.require_json_content_type()?;
721 self.validated_json()
722 }
723
724 pub fn query<T>(&self) -> Result<T>
725 where
726 T: DeserializeOwned,
727 {
728 let query = match self.query_string.as_deref() {
729 Some(query) => {
730 validate_percent_encoding(query)?;
731 query.to_string()
732 }
733 None => serde_urlencoded::to_string(&self.query)
734 .map_err(|err| BootError::BadRequest(err.to_string()))?,
735 };
736 serde_urlencoded::from_str(&query).map_err(|err| BootError::BadRequest(err.to_string()))
737 }
738
739 pub fn validated_query<T>(&self) -> Result<T>
740 where
741 T: DeserializeOwned + Validate,
742 {
743 let value = self.query()?;
744 validate_value(value)
745 }
746}
747
748fn parse_required_value<T>(value: Option<String>, label: &str, name: &str) -> Result<T>
749where
750 T: FromStr,
751 T::Err: fmt::Display,
752{
753 let Some(value) = value else {
754 return Err(BootError::BadRequest(format!("missing {label}: {name}")));
755 };
756 parse_value(value, label, name)
757}
758
759fn parse_optional_value<T>(value: Option<String>, label: &str, name: &str) -> Result<Option<T>>
760where
761 T: FromStr,
762 T::Err: fmt::Display,
763{
764 value
765 .map(|value| parse_value(value, label, name))
766 .transpose()
767}
768
769fn parse_value<T>(value: String, label: &str, name: &str) -> Result<T>
770where
771 T: FromStr,
772 T::Err: fmt::Display,
773{
774 value
775 .parse::<T>()
776 .map_err(|error| BootError::BadRequest(format!("invalid {label} {name}: {error}")))
777}
778
779fn validate_request_header(name: &str, value: &str) -> Result<()> {
780 validate_header_name(name).map_err(|message| {
781 BootError::BadRequest(format!("invalid request header name {name:?}: {message}"))
782 })?;
783 validate_header_value(value).map_err(|message| {
784 BootError::BadRequest(format!(
785 "invalid request header value for {name:?}: {message}"
786 ))
787 })
788}