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